diff --git a/README.md b/README.md index 6900a17..0a5479c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This project is very simple and I did my best to add comments for almost every s - [x] Dark/Light Theme(Rxdart). - [x] Dynamic Theme based on the weather condition(Rxdart). - [x] Handle exceptions.(a very simple one) +- [ ] Add a history page for forecasts on previous dates. # Build diff --git a/lib/src/blocs/preference_bloc.dart b/lib/src/blocs/preference_bloc.dart index caa9814..0eba6ff 100644 --- a/lib/src/blocs/preference_bloc.dart +++ b/lib/src/blocs/preference_bloc.dart @@ -161,6 +161,7 @@ class PreferencesBloc extends Bloc { /// `current` is the current location key. /// /// `places` is the other places key. + //TODO: Isn't Used, delete it if is useless get currentLocationPlacesStream => Rx.combineLatest2, String, Map>( _places.stream, diff --git a/lib/src/my_app.dart b/lib/src/my_app.dart index 7edb9f7..49dbc2e 100644 --- a/lib/src/my_app.dart +++ b/lib/src/my_app.dart @@ -19,27 +19,19 @@ class MyApp extends StatelessWidget { ..fetchTemperature() ..fetchDistance() ..fetchPressure() - ..fetchWind(); + ..fetchWind() + ..fetchPlaces(); ForecastBloc forecastBloc = ForecastBloc(preferencesBloc: prefBloc); return BlocProvider( bloc: prefBloc, child: BlocProvider( bloc: forecastBloc, - child: StreamBuilder( - stream: forecastBloc.climateColorStream, - builder: (context, colorSnapshot) { - return StreamBuilder( - stream: prefBloc.themeStream, - builder: (context, snapshot) { - ThemeData theme = snapshot.hasData - ? snapshot.data ? darkTheme : lightTheme - : lightTheme; - return MaterialApp( - // Color of accentColor matches the weather condition - theme: theme.copyWith(accentColor: colorSnapshot.data), - onGenerateRoute: _onGenerateRoute, - ); - }, + child: StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + return MaterialApp( + theme: themeSnapshot.data ?? lightTheme, + onGenerateRoute: _onGenerateRoute, ); }, ), @@ -60,55 +52,55 @@ class MyApp extends StatelessWidget { // SettingsPage case SettingsPage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => SettingsPage(), + builder: (BuildContext context) => const SettingsPage(), ); break; // LocationPage case LocationPage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => LocationPage(), + builder: (BuildContext context) => const LocationPage(), ); break; // LocationConfigPage case LocationConfigPage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => LocationConfigPage(), + builder: (BuildContext context) => const LocationConfigPage(), ); break; // TempPage case TempPage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => TempPage(), + builder: (BuildContext context) => const TempPage(), ); break; // WindPage case WindPage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => WindPage(), + builder: (BuildContext context) => const WindPage(), ); break; // PressurePage case PressurePage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => PressurePage(), + builder: (BuildContext context) => const PressurePage(), ); break; // DistancePage case DistancePage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => DistancePage(), + builder: (BuildContext context) => const DistancePage(), ); break; // TimezonePage case TimezonePage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => TimezonePage(), + builder: (BuildContext context) => const TimezonePage(), ); break; // EmptyPage case EmptyPage.routeName: return MaterialPageRoute( - builder: (BuildContext context) => EmptyPage(), + builder: (BuildContext context) => const EmptyPage(), ); break; // CustomLicensePage @@ -135,7 +127,7 @@ class MyApp extends StatelessWidget { case SplashPage.routeName: default: return MaterialPageRoute( - builder: (BuildContext context) => SplashPage(), + builder: (BuildContext context) => const SplashPage(), ); } } diff --git a/lib/src/pages/home_page.dart b/lib/src/pages/home_page.dart index 73be57b..5d1c486 100644 --- a/lib/src/pages/home_page.dart +++ b/lib/src/pages/home_page.dart @@ -14,63 +14,107 @@ class HomePage extends StatelessWidget { Widget build(BuildContext context) { ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: - (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasError) { - if (snapshot.error is EmptyLocationException) return EmptyPage(); - if (snapshot.error is NoInternetException) - return ExceptionPage(exception: snapshot.error); - return ExceptionPage( - exception: Exception('Sorry something went wrong o _ O'), - ); - } - return Scaffold( - drawer: const CustomDrawer(), - body: CustomScrollView( - slivers: [ - SliverPersistentHeader( - floating: false, - delegate: - PersistHeader(height: MediaQuery.of(context).size.height), - pinned: true, - ), - SliverToBoxAdapter(child: const SizedBox(height: 16.0)), - SliverToBoxAdapter(child: const WeatherStatus()), - SliverToBoxAdapter(child: const SizedBox(height: 16.0)), - SliverToBoxAdapter(child: const WeekForecast()), - SliverToBoxAdapter(child: const SizedBox(height: 48.0)), - SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - 'DETAILS', - style: Theme.of(context) - .textTheme - .headline4 - .copyWith(fontWeight: FontWeight.w700, fontSize: 30), + return Scaffold( + drawer: const CustomDrawer(), + body: RefreshIndicator( + onRefresh: () async => Future.delayed(Duration(milliseconds: 200)) + .then((value) => bloc.fetchForecast()), + child: StreamBuilder( + stream: bloc.todayForecastStream, + builder: (BuildContext context, + AsyncSnapshot snapshot) { + if (snapshot.hasError) { + if (snapshot.error is EmptyLocationException) + return const EmptyPage(); + if (snapshot.error is NoInternetException) + return ExceptionPage(exception: snapshot.error); + return ExceptionPage( + exception: Exception('Sorry something went wrong o _ O'), + ); + } + return CustomScrollView( + slivers: [ + SliverPersistentHeader( + floating: false, + delegate: PersistHeader( + height: MediaQuery.of(context).size.height, ), + pinned: true, ), + SliverToBoxAdapter(child: const SizedBox(height: 16.0)), + SliverToBoxAdapter(child: const WeatherStatus()), + SliverToBoxAdapter(child: const SizedBox(height: 16.0)), + SliverToBoxAdapter(child: const WeekForecast()), + SliverToBoxAdapter(child: const SizedBox(height: 48.0)), + SliverToBoxAdapter(child: const DetailsHeader()), + SliverToBoxAdapter(child: const ForecastDetails()), + SliverToBoxAdapter(child: const SizedBox(height: 36.0)), + SliverToBoxAdapter(child: const SourcesHeader()), + SliverToBoxAdapter(child: const Sources()), + ], + ); + }, + ), + ), + ); + } +} + +// --------------------------------- DetailsHeader --------------------------------- +class DetailsHeader extends StatelessWidget { + const DetailsHeader(); + @override + Widget build(BuildContext context) { + // print('DetailsHeader'); + PreferencesBloc prefBloc = BlocProvider.of(context); + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + 'DETAILS', + style: themeSnapshot.data.textTheme.headline4 + .copyWith(fontWeight: FontWeight.w700, fontSize: 30), ), - SliverToBoxAdapter(child: const ForecastDetails()), - SliverToBoxAdapter(child: const SizedBox(height: 36.0)), - SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - 'Sources', - style: Theme.of(context) - .textTheme - .headline4 - .copyWith(fontWeight: FontWeight.w700, fontSize: 30), - ), - ), + ); + break; + default: + return Container(); + } + }, + ); + } +} + +// --------------------------------- SourcesHeader --------------------------------- +class SourcesHeader extends StatelessWidget { + const SourcesHeader(); + @override + Widget build(BuildContext context) { + // print('SourcesHeader'); + PreferencesBloc prefBloc = BlocProvider.of(context); + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: Text( + 'Sources', + style: themeSnapshot.data.textTheme.headline4 + .copyWith(fontWeight: FontWeight.w700, fontSize: 30), ), - SliverToBoxAdapter(child: const Sources()), - ], - ), - ); + ); + break; + default: + return Container(); + } }, ); } diff --git a/lib/src/pages/location_config_page.dart b/lib/src/pages/location_config_page.dart index 55d2a13..d5f03fb 100644 --- a/lib/src/pages/location_config_page.dart +++ b/lib/src/pages/location_config_page.dart @@ -3,116 +3,135 @@ import 'package:climate/src/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -class LocationConfigPage extends StatefulWidget { +class LocationConfigPage extends StatelessWidget { static const String routeName = "/LocationConfigPage"; - @override - _LocationConfigPageState createState() => _LocationConfigPageState(); -} - -class _LocationConfigPageState extends State { - GlobalKey _globalKey; - ForecastBloc _forecastBloc; - TextEditingController _searchCtrl; - - @override - void initState() { - super.initState(); - _globalKey = GlobalKey(); - _forecastBloc = BlocProvider.of(context); - _searchCtrl = TextEditingController(text: ''); - } - + const LocationConfigPage(); @override Widget build(BuildContext context) { - print('LocationConfigPage'); - return Scaffold( - key: _globalKey, body: CustomScrollView( slivers: [ SliverToBoxAdapter( child: const CustomAppBar(title: 'Config Locations'), ), - SliverToBoxAdapter(child: SizedBox(height: 24.0)), - SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: TextField( - controller: _searchCtrl, - decoration: InputDecoration( - labelText: 'Search for a location', - hintText: 'like: london', - ), - onChanged: (query) async => - await Future.delayed(Duration(milliseconds: 200)) - .then((value) async { - if (_searchCtrl.text.isNotEmpty) - _forecastBloc.locationSearch(query: _searchCtrl.text); - }), - // onSubmitted: (value) async { - // if (value.isNotEmpty) { - // await _forecastBloc.locationSearch(query: _searchCtrl.text); - // } - // }, - ), - ), - ), - SliverToBoxAdapter( - child: StreamBuilder( - stream: _forecastBloc.climateColorStream, - builder: (context, AsyncSnapshot colorSnapshot) { + SliverToBoxAdapter(child: const SizedBox(height: 24.0)), + SliverToBoxAdapter(child: const SearchField()), + SliverToBoxAdapter(child: const SearchResult()), + ], + ), + ); + } +} + +// ---------------------------------- SearchField ---------------------------------- +class SearchField extends StatelessWidget { + const SearchField(); + @override + Widget build(BuildContext context) { + // print('SearchField'); + TextEditingController _searchCtrl = TextEditingController(text: ''); + ForecastBloc bloc = BlocProvider.of(context)..clearSearch(); + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: TextField( + controller: _searchCtrl, + decoration: InputDecoration( + labelText: 'Search for a location', + hintText: 'like: london', + ), + onChanged: (query) async => + await Future.delayed(Duration(milliseconds: 200)) + .then((value) async { + if (_searchCtrl.text.isNotEmpty) + bloc.locationSearch(query: _searchCtrl.text); + }), + // onSubmitted: (value) async { + // if (value.isNotEmpty) { + // await _forecastBloc.locationSearch(query: _searchCtrl.text); + // } + // }, + ), + ); + } +} + +// ---------------------------------- SearchResult ---------------------------------- +class SearchResult extends StatelessWidget { + const SearchResult(); + @override + Widget build(BuildContext context) { + // print('SearchResult'); + ForecastBloc forecastBloc = BlocProvider.of(context) + ..clearSearch(); + PreferencesBloc prefBloc = BlocProvider.of(context); + List names = []; + Map placesMap; + return StreamBuilder( + stream: prefBloc.locationStream, + builder: (context, locationSnapshot) { + switch (locationSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return StreamBuilder( + stream: forecastBloc.climateColorStream, + builder: (context, colorSnapshot) { switch (colorSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: return StreamBuilder>( initialData: {}, - stream: _forecastBloc.searchedLocationsStream, - builder: ( - BuildContext context, - AsyncSnapshot> snapshot, - ) { + stream: forecastBloc.searchedLocationsStream, + builder: (BuildContext context, searchSnapshot) { // When there is an error show some notes - //TODO: better handling is needed! - if (snapshot.hasError) { + if (searchSnapshot.hasError) { SchedulerBinding.instance .addPostFrameCallback((timeStamp) { - _globalKey.currentState + Scaffold.of(context) ..hideCurrentSnackBar() ..showSnackBar( SnackBar( - content: Text(snapshot.error.toString()), + content: Text( + searchSnapshot.error.toString(), + ), ), ); }); } - switch (snapshot.connectionState) { + switch (searchSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - Map placesMap = snapshot.data ?? {}; - List names = placesMap.keys.toList(); - + placesMap = searchSnapshot.data ?? {}; + names = placesMap.keys.toList(); return ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: placesMap?.length, - itemBuilder: (BuildContext context, int index) { - return CheckboxListTile( - value: placesMap[names[index]], - title: Text(names[index]), - activeColor: colorSnapshot.data, - onChanged: (value) async => - await _forecastBloc.changePlaceStatus( - title: names[index], - chosen: value, - ), - ); - }, + itemBuilder: (BuildContext context, int index) => + CheckboxListTile( + value: placesMap[names[index]], + title: Text(names[index]), + activeColor: colorSnapshot.data, + // Avoid user change current location status + onChanged: (names[index] + .compareTo(locationSnapshot.data) == + 0) + ? null + : (value) async => await forecastBloc + .changePlaceStatus( + title: names[index], + chosen: value, + ) + .then( + (_) async => await Future.delayed( + Duration(milliseconds: 500), + ), + ), + ), separatorBuilder: (context, index) => const Divider(), ); break; - case ConnectionState.waiting: - case ConnectionState.none: default: return Container(); } @@ -123,17 +142,12 @@ class _LocationConfigPageState extends State { return Container(); } }, - ), - ), - ], - ), + ); + break; + default: + return Container(); + } + }, ); } - - @override - void dispose() { - super.dispose(); - // clear search results - _forecastBloc.clearSearch(); - } } diff --git a/lib/src/pages/location_page.dart b/lib/src/pages/location_page.dart index 5cac599..ab5a005 100644 --- a/lib/src/pages/location_page.dart +++ b/lib/src/pages/location_page.dart @@ -3,31 +3,14 @@ import 'package:climate/src/pages/pages.dart'; import 'package:climate/src/widgets/widgets.dart'; import 'package:flutter/material.dart'; -class LocationPage extends StatefulWidget { +class LocationPage extends StatelessWidget { static const String routeName = "/LocationPage"; const LocationPage({Key key}) : super(key: key); - @override - _LocationPageState createState() => _LocationPageState(); -} - -class _LocationPageState extends State { - PreferencesBloc _preferencesBloc; - List _places; - GlobalKey _scaffoldKey; - - @override - void initState() { - _preferencesBloc = BlocProvider.of(context)..fetchPlaces(); - _scaffoldKey = GlobalKey(); - _places = []; - super.initState(); - } @override Widget build(BuildContext context) { - print('location page'); + // print('LocationPage'); return Scaffold( - key: _scaffoldKey, body: CustomScrollView( slivers: [ SliverToBoxAdapter( @@ -37,7 +20,7 @@ class _LocationPageState extends State { Tooltip( message: 'Add new locations', child: IconButton( - icon: Icon(Icons.add, color: Colors.white), + icon: const Icon(Icons.add, color: Colors.white), onPressed: () => Navigator.of(context) .pushNamed(LocationConfigPage.routeName), ), @@ -58,112 +41,135 @@ class _LocationPageState extends State { ), ), ), - SliverToBoxAdapter(child: SizedBox(height: 24.0)), - _locationList(), + const SliverToBoxAdapter(child: const SizedBox(height: 24.0)), + const SliverToBoxAdapter(child: const LocationPagePlaces()), ], ), ); } +} + +// ------------------------------- LocationPagePlaces ------------------------------- +class LocationPagePlaces extends StatelessWidget { + const LocationPagePlaces(); + @override + Widget build(BuildContext context) { + // print('LocationPagePlaces'); + PreferencesBloc prefBloc = BlocProvider.of(context); + List places = []; - Widget _locationList() { return StreamBuilder>( - stream: _preferencesBloc.placesStream, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - switch (snapshot.connectionState) { + stream: prefBloc.placesStream, + builder: (BuildContext context, placesSnapshot) { + switch (placesSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - _places = snapshot.data; - return SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) => GestureDetector( - onTap: () async => - // Change user fav location to the tapped one - // then show a snackbar. - await _preferencesBloc - .saveLocation(location: _places[index]) - .then( - (value) => _scaffoldKey.currentState - ..hideCurrentSnackBar() - ..showSnackBar(_snackBar(_places[index])), - ), - child: DismissiblePlace(title: _places[index]), - ), - childCount: _places.length), + places = placesSnapshot.data; + return ListView.builder( + shrinkWrap: true, + itemCount: places.length, + itemBuilder: (context, index) { + return LocationPageTile(place: places[index]); + }, ); break; - case ConnectionState.waiting: - case ConnectionState.none: default: - return SliverToBoxAdapter(); + return Container(); } }, ); } - - Widget _snackBar(String location) { - return SnackBar( - content: RichText( - text: TextSpan( - style: Theme.of(context).textTheme.button.copyWith( - fontSize: 12.0, - fontWeight: FontWeight.w200, - ), - text: 'Set ', - children: [ - TextSpan( - text: '$location ', - style: TextStyle( - color: Color(0xffe4ce0f), - fontWeight: FontWeight.w600, - ), - ), - TextSpan(text: 'as current location!') - ], - ), - ), - ); - } } -class DismissiblePlace extends StatelessWidget { - final String title; - const DismissiblePlace({Key key, this.title}) : super(key: key); +// -------------------------------- LocationPageTile -------------------------------- +class LocationPageTile extends StatelessWidget { + final String place; + + const LocationPageTile({Key key, this.place}) : super(key: key); @override Widget build(BuildContext context) { - print('DismissiblePlace'); - PreferencesBloc bloc = BlocProvider.of(context); - String currentLocation; - return StreamBuilder( - stream: bloc.locationStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('LocationPageTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + + return StreamBuilder( + stream: prefBloc.locationStream, + builder: (context, locationSnapshot) { + switch (locationSnapshot.connectionState) { case ConnectionState.done: case ConnectionState.active: - currentLocation = snapshot.data; - return Dismissible( - direction: DismissDirection.endToStart, - background: Container( - alignment: Alignment.centerRight, - color: Colors.red, - child: Icon(Icons.delete_sweep), - ), - key: ObjectKey(title), - child: ListTile( - contentPadding: EdgeInsets.symmetric(horizontal: 32.0), - title: Text(title), - trailing: - // If the current saved location is same as title, - // show a location icon beside the title. - currentLocation.compareTo(title) == 0 - ? Icon( - Icons.my_location, - color: Theme.of(context).accentColor, - ) - : null, - ), - onDismissed: (direction) async => - // If dismissed an location, remove it from saved places. - await bloc.delPlace(title: title), + return StreamBuilder( + stream: forecastBloc.climateColorStream, + builder: (context, colorSnapshot) { + switch (colorSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return Dismissible( + direction: DismissDirection.endToStart, + background: Container( + alignment: Alignment.centerRight, + color: Colors.red, + child: Padding( + padding: EdgeInsets.only(right: 16.0), + child: Icon(Icons.delete_sweep), + ), + ), + key: Key(place), + child: ListTile( + contentPadding: EdgeInsets.symmetric(horizontal: 32.0), + title: Text(place), + trailing: + // If the current saved location is same as title, + // show a location icon beside the title. + locationSnapshot.data.compareTo(place) == 0 + ? Icon( + Icons.my_location, + color: colorSnapshot.data, + ) + : null, + onTap: () async => + await prefBloc.saveLocation(location: place).then( + (value) => Scaffold.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: RichText( + text: TextSpan( + style: Theme.of(context) + .textTheme + .button + .copyWith( + fontSize: 12.0, + fontWeight: FontWeight.w200, + ), + text: 'Set ', + children: [ + TextSpan( + text: '$place ', + style: TextStyle( + color: Color(0xffe4ce0f), + fontWeight: FontWeight.w600, + ), + ), + const TextSpan( + text: 'as current location!', + ) + ], + ), + ), + ), + ), + ), + ), + onDismissed: (direction) async => + // If dismissed an location, remove it from saved places. + await prefBloc.delPlace(title: place), + ); + break; + default: + return Container(); + } + }, ); break; default: diff --git a/lib/src/pages/pressure_page.dart b/lib/src/pages/pressure_page.dart index 1525f3d..dc9341d 100644 --- a/lib/src/pages/pressure_page.dart +++ b/lib/src/pages/pressure_page.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; class PressurePage extends StatelessWidget { static const String routeName = "/PressurePage"; + const PressurePage(); @override Widget build(BuildContext context) { @@ -12,7 +13,7 @@ class PressurePage extends StatelessWidget { return Scaffold( body: CustomScrollView( slivers: [ - SliverToBoxAdapter(child:const CustomAppBar(title: 'Pressure')), + SliverToBoxAdapter(child: const CustomAppBar(title: 'Pressure')), SliverToBoxAdapter( child: ListView( shrinkWrap: true, diff --git a/lib/src/pages/settings_page.dart b/lib/src/pages/settings_page.dart index 5fe7dab..353caae 100644 --- a/lib/src/pages/settings_page.dart +++ b/lib/src/pages/settings_page.dart @@ -11,7 +11,6 @@ class SettingsPage extends StatelessWidget { const SettingsPage(); @override Widget build(BuildContext context) { - print('SettingsPage'); return Scaffold( body: CustomScrollView( slivers: [ @@ -38,39 +37,52 @@ class SettingsPage extends StatelessWidget { // ----------------------------------- ThemeTile ----------------------------------- class ThemeTile extends StatelessWidget { const ThemeTile(); - @override Widget build(BuildContext context) { - print('ThemeTile'); - PreferencesBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.themeStream, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - switch (snapshot.connectionState) { - case ConnectionState.done: + // print('ThemeTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + bool isDark; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: - return ListTileTheme( - child: SwitchListTile( - activeColor: Theme.of(context).accentColor, - title: Text('Theme'), - secondary: snapshot.data - ? Icon(FontAwesomeIcons.moon) - : Icon(FontAwesomeIcons.solidSun), - subtitle: snapshot.data ? Text('Dark') : Text('Light'), - value: snapshot.data, - onChanged: (value) async { - await bloc.saveTheme(isDark: value); - }, - ), - iconColor: Theme.of(context).iconTheme.color, + case ConnectionState.done: + theme = themeSnapshot.data; + isDark = theme.brightness == Brightness.dark; + return StreamBuilder( + stream: forecastBloc.climateColorStream, + builder: (context, colorSnapshot) { + switch (colorSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: SwitchListTile( + activeColor: colorSnapshot.data, + title: Text('Theme'), + secondary: isDark + ? Icon(FontAwesomeIcons.moon) + : Icon(FontAwesomeIcons.solidSun), + subtitle: isDark ? Text('Dark') : Text('Light'), + value: isDark, + onChanged: (value) async { + await prefBloc.saveTheme(isDark: value); + }, + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; - case ConnectionState.waiting: default: - return Center(child: CircularProgressIndicator()); + return Container(); } }, ); @@ -82,35 +94,48 @@ class LocationTile extends StatelessWidget { const LocationTile(); @override Widget build(BuildContext context) { - print('LocationTile'); - PreferencesBloc bloc = BlocProvider.of(context); + // print('LocationTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); String location; - return StreamBuilder( - stream: bloc.locationStream, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - switch (snapshot.connectionState) { - case ConnectionState.done: + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: - location = snapshot.data; - return ListTileTheme( - child: ListTile( - title: Text('Location'), - leading: location.isEmpty - ? Icon(Icons.location_searching) - : Icon(Icons.my_location), - subtitle: Text(location.isEmpty ? 'No Location' : location), - onTap: () => - Navigator.of(context).pushNamed(LocationPage.routeName), - ), - iconColor: Theme.of(context).iconTheme.color, + case ConnectionState.done: + theme = themeSnapshot.data; + return StreamBuilder( + stream: prefBloc.locationStream, + builder: (BuildContext context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { + case ConnectionState.done: + case ConnectionState.active: + location = forecastSnapshot.data; + return ListTileTheme( + child: ListTile( + title: Text('Location'), + leading: location.isEmpty + ? Icon(Icons.location_searching) + : Icon(Icons.my_location), + subtitle: + Text(location.isEmpty ? 'No Location' : location), + onTap: () => Navigator.of(context) + .pushNamed(LocationPage.routeName), + ), + iconColor: theme.iconTheme.color, + ); + break; + case ConnectionState.waiting: + default: + return Center(child: CircularProgressIndicator()); + } + }, ); break; - case ConnectionState.waiting: default: - return Center(child: CircularProgressIndicator()); + return Container(); } }, ); @@ -122,29 +147,34 @@ class TimezoneTile extends StatelessWidget { const TimezoneTile(); @override Widget build(BuildContext context) { - print('TimezoneTile'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.timezoneNameStream, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - switch (snapshot.connectionState) { - case ConnectionState.done: + // print('TimezoneTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: - return ListTileTheme( - child: ListTile( - title: Text('Timezone'), - leading: Icon(FontAwesomeIcons.globe), - subtitle: Text(snapshot.data), - onTap: () => - Navigator.of(context).pushNamed(TimezonePage.routeName), - ), - iconColor: Theme.of(context).iconTheme.color, + case ConnectionState.done: + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.timezoneNameStream, + builder: (BuildContext context, snapshot) { + return ListTileTheme( + child: ListTile( + title: Text('Timezone'), + leading: Icon(FontAwesomeIcons.globe), + subtitle: Text(snapshot.data ?? ''), + onTap: () => + Navigator.of(context).pushNamed(TimezonePage.routeName), + ), + iconColor: theme.iconTheme.color, + ); + }, ); break; - case ConnectionState.waiting: default: return Container(); } @@ -158,37 +188,50 @@ class TempTile extends StatelessWidget { const TempTile(); @override Widget build(BuildContext context) { - print('TempTile'); - PreferencesBloc bloc = BlocProvider.of(context); + // print('TempTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ThemeData theme; String unit; - return StreamBuilder( - stream: bloc.tempStream, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - switch (snapshot.connectionState) { - case ConnectionState.done: + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: - unit = UnitConverter.strUnit(snapshot.data).toUpperCase(); - return ListTileTheme( - child: ListTile( - title: Text('Temperature'), - leading: Icon( - WeatherIcons.thermometer, - ), - subtitle: snapshot.data == TempUnit.K - ? Text('$unit') - : Text('°$unit'), - onTap: () async => - await Navigator.of(context).pushNamed(TempPage.routeName), - ), - iconColor: Theme.of(context).iconTheme.color, + case ConnectionState.done: + theme = themeSnapshot.data; + return StreamBuilder( + stream: prefBloc.tempStream, + builder: (BuildContext context, tempSnapshot) { + switch (tempSnapshot.connectionState) { + case ConnectionState.done: + case ConnectionState.active: + unit = + UnitConverter.strUnit(tempSnapshot.data).toUpperCase(); + return ListTileTheme( + child: ListTile( + title: Text('Temperature'), + leading: Icon( + WeatherIcons.thermometer, + ), + subtitle: tempSnapshot.data == TempUnit.K + ? Text(unit) + : Text('°$unit'), + onTap: () async => await Navigator.of(context) + .pushNamed(TempPage.routeName), + ), + iconColor: theme.iconTheme.color, + ); + break; + case ConnectionState.waiting: + default: + return Center(child: CircularProgressIndicator()); + } + }, ); break; - case ConnectionState.waiting: default: - return Center(child: CircularProgressIndicator()); + return Container(); } }, ); @@ -200,33 +243,45 @@ class WindTile extends StatelessWidget { const WindTile(); @override Widget build(BuildContext context) { - print('WindTile'); - PreferencesBloc bloc = BlocProvider.of(context); - WindUnit unit; - return StreamBuilder( - stream: bloc.windStream, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - switch (snapshot.connectionState) { - case ConnectionState.done: + // print('WindTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ThemeData theme; + String unit; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: - unit = snapshot.data; - return ListTileTheme( - child: ListTile( - title: Text('Wind Speed'), - leading: Icon(WeatherIcons.strong_wind), - subtitle: Text('${UnitConverter.strUnit(unit)}'), - onTap: () => - Navigator.of(context).pushNamed(WindPage.routeName), - ), - iconColor: Theme.of(context).iconTheme.color, + case ConnectionState.done: + theme = themeSnapshot.data; + return StreamBuilder( + stream: prefBloc.windStream, + builder: (BuildContext context, windSnapshot) { + switch (windSnapshot.connectionState) { + case ConnectionState.done: + case ConnectionState.active: + unit = UnitConverter.strUnit(windSnapshot.data); + return ListTileTheme( + child: ListTile( + title: Text('Wind Speed'), + leading: Icon(WeatherIcons.strong_wind), + subtitle: Text(unit), + onTap: () => + Navigator.of(context).pushNamed(WindPage.routeName), + ), + iconColor: theme.iconTheme.color, + ); + break; + case ConnectionState.waiting: + default: + return Center(child: CircularProgressIndicator()); + } + }, ); break; - case ConnectionState.waiting: default: - return Center(child: CircularProgressIndicator()); + return Container(); } }, ); @@ -238,33 +293,45 @@ class PressureTile extends StatelessWidget { const PressureTile(); @override Widget build(BuildContext context) { - print('PressureTile'); - PreferencesBloc bloc = BlocProvider.of(context); - PressureUnit unit; - return StreamBuilder( - stream: bloc.pressureStream, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - switch (snapshot.connectionState) { - case ConnectionState.done: + // print('PressureTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ThemeData theme; + String unit; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: - unit = snapshot.data; - return ListTileTheme( - child: ListTile( - title: Text('Pressure'), - leading: Icon(WeatherIcons.barometer), - subtitle: Text('${UnitConverter.strUnit(unit)}'), - onTap: () => - Navigator.of(context).pushNamed(PressurePage.routeName), - ), - iconColor: Theme.of(context).iconTheme.color, + case ConnectionState.done: + theme = themeSnapshot.data; + return StreamBuilder( + stream: prefBloc.pressureStream, + builder: (BuildContext context, pressureSnapshot) { + switch (pressureSnapshot.connectionState) { + case ConnectionState.done: + case ConnectionState.active: + unit = UnitConverter.strUnit(pressureSnapshot.data); + return ListTileTheme( + child: ListTile( + title: Text('Pressure'), + leading: Icon(WeatherIcons.barometer), + subtitle: Text(unit), + onTap: () => Navigator.of(context) + .pushNamed(PressurePage.routeName), + ), + iconColor: theme.iconTheme.color, + ); + break; + case ConnectionState.waiting: + default: + return Center(child: CircularProgressIndicator()); + } + }, ); break; - case ConnectionState.waiting: default: - return Center(child: CircularProgressIndicator()); + return Container(); } }, ); @@ -276,33 +343,45 @@ class DistanceTile extends StatelessWidget { const DistanceTile(); @override Widget build(BuildContext context) { - print('DistanceTile'); - PreferencesBloc bloc = BlocProvider.of(context); - DistanceUnit unit; - return StreamBuilder( - stream: bloc.distanceStream, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - switch (snapshot.connectionState) { - case ConnectionState.done: + // print('LocationTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ThemeData theme; + String unit; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: - unit = snapshot.data; - return ListTileTheme( - child: ListTile( - title: Text('Distance'), - leading: Icon(FontAwesomeIcons.route), - subtitle: Text('${UnitConverter.strUnit(unit)}'), - onTap: () => - Navigator.of(context).pushNamed(DistancePage.routeName), - ), - iconColor: Theme.of(context).iconTheme.color, + case ConnectionState.done: + theme = themeSnapshot.data; + return StreamBuilder( + stream: prefBloc.distanceStream, + builder: (BuildContext context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.done: + case ConnectionState.active: + unit = UnitConverter.strUnit(snapshot.data); + return ListTileTheme( + child: ListTile( + title: Text('Distance'), + leading: Icon(FontAwesomeIcons.route), + subtitle: Text(unit), + onTap: () => Navigator.of(context) + .pushNamed(DistancePage.routeName), + ), + iconColor: theme.iconTheme.color, + ); + break; + case ConnectionState.waiting: + default: + return Center(child: CircularProgressIndicator()); + } + }, ); break; - case ConnectionState.waiting: default: - return Center(child: CircularProgressIndicator()); + return Container(); } }, ); diff --git a/lib/src/pages/splash_page.dart b/lib/src/pages/splash_page.dart index 942081b..9d4e995 100644 --- a/lib/src/pages/splash_page.dart +++ b/lib/src/pages/splash_page.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; class SplashPage extends StatefulWidget { static const String routeName = "/SplashPage"; + const SplashPage(); @override _SplashPageState createState() => _SplashPageState(); @@ -22,7 +23,8 @@ class _SplashPageState extends State ..addStatusListener((status) async { if (status == AnimationStatus.completed) { Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (BuildContext context) => HomePage()), + MaterialPageRoute( + builder: (BuildContext context) => const HomePage()), (Route route) => false); } }); diff --git a/lib/src/pages/temp_page.dart b/lib/src/pages/temp_page.dart index bfa9817..0738ac4 100644 --- a/lib/src/pages/temp_page.dart +++ b/lib/src/pages/temp_page.dart @@ -5,14 +5,14 @@ import 'package:flutter/material.dart'; class TempPage extends StatelessWidget { static const String routeName = "/TempPage"; - + const TempPage(); @override Widget build(BuildContext context) { PreferencesBloc bloc = BlocProvider.of(context); return Scaffold( body: CustomScrollView( slivers: [ - SliverToBoxAdapter(child:const CustomAppBar(title: 'Temperature')), + SliverToBoxAdapter(child: const CustomAppBar(title: 'Temperature')), SliverToBoxAdapter( child: ListView( shrinkWrap: true, diff --git a/lib/src/pages/timezone_page.dart b/lib/src/pages/timezone_page.dart index e58d653..25716fb 100644 --- a/lib/src/pages/timezone_page.dart +++ b/lib/src/pages/timezone_page.dart @@ -9,8 +9,8 @@ class TimezonePage extends StatelessWidget { const TimezonePage(); @override Widget build(BuildContext context) { - print('TimezonePage'); PreferencesBloc bloc = BlocProvider.of(context); + return Scaffold( body: CustomScrollView( slivers: [ @@ -59,7 +59,7 @@ class LocationTimezoneTile extends StatelessWidget { return StreamBuilder( stream: bloc.forecastStream, - builder: (context, AsyncSnapshot snapshot) { + builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.done: case ConnectionState.active: diff --git a/lib/src/pages/wind_page.dart b/lib/src/pages/wind_page.dart index 2bfc8f9..7378725 100644 --- a/lib/src/pages/wind_page.dart +++ b/lib/src/pages/wind_page.dart @@ -5,10 +5,11 @@ import 'package:flutter/material.dart'; class WindPage extends StatelessWidget { static const String routeName = "/WindPage"; - + const WindPage(); @override Widget build(BuildContext context) { PreferencesBloc bloc = BlocProvider.of(context); + return Scaffold( body: CustomScrollView( slivers: [ diff --git a/lib/src/widgets/custom_appbar.dart b/lib/src/widgets/custom_appbar.dart index db7c29c..2c3f3e9 100644 --- a/lib/src/widgets/custom_appbar.dart +++ b/lib/src/widgets/custom_appbar.dart @@ -1,5 +1,6 @@ import 'package:climate/src/blocs/blocs.dart'; import 'package:climate/src/utils/utils.dart'; +import 'package:climate/src/widgets/widgets.dart'; import 'package:flutter/material.dart'; class CustomAppBar extends StatelessWidget { @@ -12,64 +13,83 @@ class CustomAppBar extends StatelessWidget { }) : super(key: key); @override Widget build(BuildContext context) { - print('CustomAppBar'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.climateColorStream, - builder: (context, snapshot) { - return SizedBox( - height: 132.0, - width: MediaQuery.of(context).size.width, - child: Stack( - children: [ - Positioned.fill( - child: ClipPath( - clipper: Clipper(), - child: Container( - padding: EdgeInsets.only(left: 16.0, bottom: 48.0), - width: double.infinity, - color: snapshot.data, - ), - ), - ), - Positioned( - top: MediaQuery.of(context).padding.top + 16.0, - left: 16.0, - child: Navigator.canPop(context) - ? BackButton(color: Colors.white) - : Container(), - ), - Positioned( - top: MediaQuery.of(context).padding.top + 24.0, - left: 72.0, - child: Text( - title, - style: Theme.of(context) - .textTheme - .headline6 - .copyWith(color: Colors.white), - ), - ), - Positioned( - top: MediaQuery.of(context).padding.top + 16.0, - right: 16.0, - child: (actions != null) - ? Row( - children: actions.map( - (e) { - return Padding( - padding: EdgeInsets.only(left: 16.0), - child: e, - ); - }, - ).toList(), - ) - : Container(), + // print('CustomAppBar'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + + return SizedBox( + height: 132.0, + width: MediaQuery.of(context).size.width, + child: Stack( + children: [ + Positioned.fill( + child: ClipPath( + clipper: Clipper(), + child: StreamBuilder( + stream: forecastBloc.climateColorStream, + builder: (context, colorSnapshot) { + switch (colorSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return Container( + padding: EdgeInsets.only(left: 16.0, bottom: 48.0), + width: double.infinity, + color: colorSnapshot.data, + ); + break; + default: + return Container(); + } + }, ), - ], + ), + ), + Positioned( + top: MediaQuery.of(context).padding.top + 16.0, + left: 16.0, + child: Navigator.canPop(context) + ? BackButton(color: Colors.white) + : Container(), + ), + Positioned( + top: MediaQuery.of(context).padding.top + 24.0, + left: 72.0, + child: StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return Text( + title, + style: themeSnapshot.data.textTheme.headline6 + .copyWith(color: Colors.white), + ); + break; + default: + return const Skeleton(); + } + }, + ), + ), + Positioned( + top: MediaQuery.of(context).padding.top + 16.0, + right: 16.0, + child: (actions != null) + ? Row( + children: actions.map( + (e) { + return Padding( + padding: EdgeInsets.only(left: 16.0), + child: e, + ); + }, + ).toList(), + ) + : Container(), ), - ); - }, + ], + ), ); } } diff --git a/lib/src/widgets/custom_drawer.dart b/lib/src/widgets/custom_drawer.dart index 10b52e8..a9f0285 100644 --- a/lib/src/widgets/custom_drawer.dart +++ b/lib/src/widgets/custom_drawer.dart @@ -8,7 +8,7 @@ class CustomDrawer extends StatelessWidget { const CustomDrawer(); @override Widget build(BuildContext context) { - print('CustomDrawer'); + // print('CustomDrawer'); return Drawer( child: ListView( shrinkWrap: true, @@ -17,30 +17,8 @@ class CustomDrawer extends StatelessWidget { const DrawerThemeTile(), const DrawerExpansionList(), const Divider(), - ListTileTheme( - child: ListTile( - title: Text('Setting'), - leading: Icon(FontAwesomeIcons.cog), - onTap: () => - Navigator.of(context).pushNamed(SettingsPage.routeName), - ), - iconColor: Theme.of(context).iconTheme.color, - ), - ListTileTheme( - child: CustomAboutDialog( - applicationIcon: SvgPicture.asset( - 'assets/img/logo/daylight_logo.svg', - width: 50.0, - height: 50.0, - ), - applicationName: 'Climate', - applicationVersion: '1.0.0', - child: Text('About'), - icon: Icon(FontAwesomeIcons.infoCircle), - applicationLegalese: 'Climate is a simple weather app!', - ), - iconColor: Theme.of(context).iconTheme.color, - ), + const DrawerSettingTile(), + const DrawerAboutTile(), ], ), ); @@ -52,23 +30,90 @@ class CustomDrawerHeader extends StatelessWidget { const CustomDrawerHeader(); @override Widget build(BuildContext context) { - print('CustomDrawerHeader'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.climateColorStream, - builder: (BuildContext context, AsyncSnapshot snapshot) { - switch (snapshot.connectionState) { + // print('CustomDrawerHeader'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return DrawerHeader( - decoration: BoxDecoration(color: snapshot.data), - child: SvgPicture.asset( - Theme.of(context).brightness == Brightness.dark - ? 'assets/img/logo/night_logo.svg' - : 'assets/img/logo/daylight_logo.svg', - width: 100, - height: 100, - ), + return StreamBuilder( + stream: forecastBloc.climateColorStream, + builder: (context, colorSnapshot) { + switch (colorSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return DrawerHeader( + decoration: BoxDecoration(color: colorSnapshot.data), + child: SvgPicture.asset( + themeSnapshot.data.brightness == Brightness.dark + ? 'assets/img/logo/night_logo.svg' + : 'assets/img/logo/daylight_logo.svg', + width: 100, + height: 100, + ), + ); + break; + default: + return Container(); + } + }, + ); + break; + default: + return Container(); + } + }, + ); + } +} + +// -------------------------------- DrawerThemeTile -------------------------------- +class DrawerThemeTile extends StatelessWidget { + const DrawerThemeTile(); + + @override + Widget build(BuildContext context) { + // print('DrawerThemeTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + bool isDark; + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + theme = themeSnapshot.data; + isDark = theme.brightness == Brightness.dark; + return StreamBuilder( + stream: forecastBloc.climateColorStream, + builder: (context, colorSnapshot) { + switch (colorSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: SwitchListTile( + activeColor: colorSnapshot.data, + title: Text('Theme'), + secondary: isDark + ? Icon(FontAwesomeIcons.moon) + : Icon(FontAwesomeIcons.solidSun), + value: isDark, + onChanged: (value) async { + await prefBloc.saveTheme(isDark: value); + }, + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -84,38 +129,56 @@ class DrawerExpansionList extends StatelessWidget { const DrawerExpansionList(); @override Widget build(BuildContext context) { - print('DrawerExpansionList'); - PreferencesBloc bloc = BlocProvider.of(context) - ..fetchPlaces(); + // print('DrawerExpansionList'); + PreferencesBloc bloc = BlocProvider.of(context); + List places = []; return StreamBuilder>( stream: bloc.placesStream, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - switch (snapshot.connectionState) { + builder: (BuildContext context, placesSnapshot) { + switch (placesSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - List places = snapshot.data; + places = placesSnapshot.data; + return places.isEmpty + ? const DrawerEmptyLocation() + : DrawerPlacesTile(places: places); + break; + default: + return Container(); + } + }, + ); + } +} +// ------------------------------ DrawerEmptyLocation ------------------------------ +class DrawerEmptyLocation extends StatelessWidget { + const DrawerEmptyLocation(); + @override + Widget build(BuildContext context) { + // print('DrawerEmptyLocation'); + PreferencesBloc bloc = BlocProvider.of(context); + return StreamBuilder( + stream: bloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: return ExpansionTile( leading: Icon(FontAwesomeIcons.city), maintainState: true, title: Text('Locations'), - children: places.isEmpty - ? [ - ListTileTheme( - child: ListTile( - title: Text('Add New Location'), - leading: Icon(FontAwesomeIcons.plus), - onTap: () => Navigator.of(context) - .pushNamed(LocationPage.routeName), - ), - iconColor: Theme.of(context).iconTheme.color, - ) - ] - : places - .map( - (place) => DrawerPlaceTile(place: place), - ) - .toList(), + children: [ + ListTileTheme( + child: ListTile( + title: Text('Add New Location'), + leading: Icon(FontAwesomeIcons.plus), + onTap: () => + Navigator.of(context).pushNamed(LocationPage.routeName), + ), + iconColor: themeSnapshot.data.iconTheme.color, + ) + ], ); break; default: @@ -126,30 +189,114 @@ class DrawerExpansionList extends StatelessWidget { } } -// -------------------------------- DrawerPlaceTile -------------------------------- -class DrawerPlaceTile extends StatelessWidget { - final String place; +// ------------------------------- DrawerPlacesTile -------------------------------- +class DrawerPlacesTile extends StatelessWidget { + final List places; + const DrawerPlacesTile({Key key, @required this.places}) : super(key: key); + @override + Widget build(BuildContext context) { + // print('DrawerPlacesTile'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + Color color; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + theme = themeSnapshot.data; + return StreamBuilder( + stream: prefBloc.locationStream, + builder: (context, locationSnapshot) { + switch (locationSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return StreamBuilder( + stream: forecastBloc.climateColorStream, + builder: (context, colorSnapshot) { + switch (colorSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + color = colorSnapshot.data; + //TODO: Nothing yet? + // Couldn't find any better solution to change + // ExpansionTile's active color, + // It seems like it only gets color from + // "accentColor" + return Theme( + data: theme.copyWith(accentColor: color), + child: ExpansionTile( + leading: Icon(FontAwesomeIcons.city), + maintainState: true, + title: Text('Locations'), + children: places + .map( + (place) => ListTile( + title: Text( + place, + style: theme.textTheme.subtitle1, + ), + trailing: locationSnapshot.data + .compareTo(place) == + 0 + ? Icon( + Icons.my_location, + color: color, + ) + : null, + onTap: () async => await prefBloc + .saveLocation(location: place), + ), + ) + .toList(), + ), + ); + break; + default: + return Container(); + } + }, + ); + break; + default: + return Container(); + } + }, + ); + break; + default: + return Container(); + } + }, + ); + } +} - const DrawerPlaceTile({Key key, @required this.place}) : super(key: key); +// ------------------------------- DrawerSettingTile ------------------------------- +class DrawerSettingTile extends StatelessWidget { + const DrawerSettingTile(); @override Widget build(BuildContext context) { + // print('DrawerSettingTile'); PreferencesBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.locationStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + return StreamBuilder( + stream: bloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTile( - title: Text(place, style: Theme.of(context).textTheme.subtitle1), - trailing: snapshot.data.compareTo(place) == 0 - ? Icon( - Icons.my_location, - color: Theme.of(context).accentColor, - ) - : null, - onTap: () async => await bloc.saveLocation(location: place), + return ListTileTheme( + child: ListTile( + title: Text('Setting'), + leading: Icon(FontAwesomeIcons.cog), + onTap: () => + Navigator.of(context).pushNamed(SettingsPage.routeName), + ), + iconColor: themeSnapshot.data.iconTheme.color, ); break; default: @@ -160,41 +307,38 @@ class DrawerPlaceTile extends StatelessWidget { } } -// -------------------------------- DrawerThemeTile -------------------------------- -class DrawerThemeTile extends StatelessWidget { - const DrawerThemeTile(); - +// -------------------------------- DrawerAboutTile -------------------------------- +class DrawerAboutTile extends StatelessWidget { + const DrawerAboutTile(); @override Widget build(BuildContext context) { - print('DrawerThemeTile'); + // print('DrawerAboutTile'); PreferencesBloc bloc = BlocProvider.of(context); - return StreamBuilder( + + return StreamBuilder( stream: bloc.themeStream, - builder: ( - BuildContext context, - AsyncSnapshot snapshot, - ) { - switch (snapshot.connectionState) { - case ConnectionState.done: + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: + case ConnectionState.done: return ListTileTheme( - child: SwitchListTile( - activeColor: Theme.of(context).accentColor, - title: Text('Theme'), - secondary: snapshot.data - ? Icon(FontAwesomeIcons.moon) - : Icon(FontAwesomeIcons.solidSun), - value: snapshot.data, - onChanged: (value) async { - await bloc.saveTheme(isDark: value); - }, + child: CustomAboutDialog( + applicationIcon: SvgPicture.asset( + 'assets/img/logo/daylight_logo.svg', + width: 50.0, + height: 50.0, + ), + applicationName: 'Climate', + applicationVersion: '1.0.0', + child: Text('About'), + icon: Icon(FontAwesomeIcons.infoCircle), + applicationLegalese: 'Climate is a simple weather app!', ), - iconColor: Theme.of(context).iconTheme.color, + iconColor: themeSnapshot.data.iconTheme.color, ); break; - case ConnectionState.waiting: default: - return Center(child: CircularProgressIndicator()); + return Container(); } }, ); @@ -222,7 +366,9 @@ class CustomAboutDialog extends StatelessWidget { @override Widget build(BuildContext context) { - print('About'); + // print('CustomAboutDialog'); + PreferencesBloc bloc = BlocProvider.of(context); + return ListTile( dense: dense, title: child ?? Text('About'), @@ -263,19 +409,31 @@ class CustomAboutDialog extends StatelessWidget { children: [ applicationIcon ?? Container(), SizedBox(width: 24.0), - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - applicationName ?? '', - style: Theme.of(context).textTheme.headline5, - ), - Text( - applicationVersion ?? '', - style: Theme.of(context).textTheme.bodyText2, - ) - ], + StreamBuilder( + stream: bloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + applicationName ?? '', + style: themeSnapshot.data.textTheme.headline5, + ), + Text( + applicationVersion ?? '', + style: themeSnapshot.data.textTheme.bodyText2, + ) + ], + ); + break; + default: + return Container(); + } + }, ), ], ), diff --git a/lib/src/widgets/custom_expansion.dart b/lib/src/widgets/custom_expansion.dart index 67a23b1..57a1c44 100644 --- a/lib/src/widgets/custom_expansion.dart +++ b/lib/src/widgets/custom_expansion.dart @@ -23,21 +23,21 @@ class _CustomExpansionState extends State vsync: this, duration: Duration(milliseconds: 500), ); - // At least 3/4 child be presented then by clicking the + // At least 3 child should be presented then by clicking the // button it shows all the items. _size = Tween( - // At least 3/4 - begin: 4 * widget.itemExtent, + // At least 3 + begin: 3 * widget.itemExtent, // Add some spaces between button and last item - // +2 is for that reason. - end: widget.itemExtent * (widget.children.length + 2)) + // +1 is for that reason. + end: widget.itemExtent * (widget.children.length + 1)) .animate(_controller); super.initState(); } @override Widget build(BuildContext context) { - print('CustomExpansion'); + // return AnimatedBuilder( animation: _controller, child: Stack( @@ -46,6 +46,7 @@ class _CustomExpansionState extends State left: 16.0, right: 16.0, child: ListView.builder( + padding: EdgeInsets.zero, itemExtent: widget.itemExtent, physics: NeverScrollableScrollPhysics(), scrollDirection: Axis.vertical, @@ -58,33 +59,36 @@ class _CustomExpansionState extends State left: 0.0, right: 0.0, bottom: 0.0, - child: StreamBuilder( + child: StreamBuilder( stream: _bloc.themeStream, builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return FlatButton( - hoverColor: Colors.grey.shade200.withOpacity(.3), - color: snapshot.data - ? Colors.black26 - : Colors.grey.shade200.withOpacity(.35), - onPressed: () { - if (_controller.status == AnimationStatus.completed) { - _isExpanded = false; - _controller.reverse(); - } else if (_controller.status == - AnimationStatus.dismissed) { - _isExpanded = true; - _controller.forward(); - } - setState(() {}); - }, - child: Icon( - _isExpanded - ? Icons.keyboard_arrow_up - : Icons.keyboard_arrow_down, - color: Colors.grey, + return ButtonTheme( + height: widget.itemExtent, + child: FlatButton( + hoverColor: Colors.grey.shade200.withOpacity(.3), + color: snapshot.data.brightness == Brightness.dark + ? Colors.black26 + : Colors.grey.shade200.withOpacity(.35), + onPressed: () { + if (_controller.status == AnimationStatus.completed) { + _isExpanded = false; + _controller.reverse(); + } else if (_controller.status == + AnimationStatus.dismissed) { + _isExpanded = true; + _controller.forward(); + } + setState(() {}); + }, + child: Icon( + _isExpanded + ? Icons.keyboard_arrow_up + : Icons.keyboard_arrow_down, + color: Colors.grey, + ), ), ); break; diff --git a/lib/src/widgets/forecast_details.dart b/lib/src/widgets/forecast_details.dart index 6be2945..2bcce98 100644 --- a/lib/src/widgets/forecast_details.dart +++ b/lib/src/widgets/forecast_details.dart @@ -11,8 +11,7 @@ class ForecastDetails extends StatelessWidget { const ForecastDetails(); @override Widget build(BuildContext context) { - print('ForecastDetails'); - + // print('ForecastDetails'); return ListView( physics: NeverScrollableScrollPhysics(), shrinkWrap: true, @@ -41,33 +40,44 @@ class ForecastDetailLocation extends StatelessWidget { const ForecastDetailLocation(); @override Widget build(BuildContext context) { - print('ForecastDetailLocation'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.forecastStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailLocation'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTileTheme( - child: ListTile( - title: Text( - 'Location Type', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - snapshot.data.locationType, - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(FontAwesomeIcons.city), - ), - iconColor: Theme.of(context).iconTheme.color, + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.forecastStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: ListTile( + title: Text( + 'Location Type', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + snapshot.data.locationType, + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, fontSize: 16.0), + ), + trailing: Icon(FontAwesomeIcons.city), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -83,33 +93,48 @@ class ForecastDetailLatLong extends StatelessWidget { const ForecastDetailLatLong(); @override Widget build(BuildContext context) { - print('ForecastDetailLatLong'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.forecastStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailLatLong'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTileTheme( - child: ListTile( - title: Text( - 'Latitude,Longitude', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - snapshot.data.lattLong, - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(FontAwesomeIcons.directions, size: 26.0), - ), - iconColor: Theme.of(context).iconTheme.color, + theme = themeSnapshot.data; + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.forecastStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: ListTile( + title: Text( + 'Latitude,Longitude', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + snapshot.data.lattLong, + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16.0, + ), + ), + trailing: Icon(FontAwesomeIcons.directions, size: 26.0), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -125,52 +150,71 @@ class ForecastDetailSunrise extends StatelessWidget { const ForecastDetailSunrise(); @override Widget build(BuildContext context) { - print('ForecastDetailSunrise'); - ForecastBloc bloc = BlocProvider.of(context); + // print('ForecastDetailSunrise'); PreferencesBloc prefBloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.forecastStream, - builder: (context, forecastSnapshot) { - switch (forecastSnapshot.connectionState) { + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return StreamBuilder( - stream: prefBloc.timezoneStream, - builder: - (context, AsyncSnapshot timezoneSnapshot) { - switch (timezoneSnapshot.connectionState) { + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.forecastStream, + builder: (context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - String date = DateFormat.Hms().format( - // Sunrise in user desired timezone - UnitConverter.timezoneConverter( - offset: forecastSnapshot.data.offset, - time: forecastSnapshot.data.sunRise, - timezone: timezoneSnapshot.data, - ), - ); - // Timezone name - String unit = UnitConverter.timezoneName( - climateTimezoneName: forecastSnapshot.data.timezoneName, - timezone: timezoneSnapshot.data, - ); - return ListTileTheme( - child: ListTile( - title: Text( - 'Sunrise($unit)', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - date, - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(WeatherIcons.sunrise, size: 26.0), - ), - iconColor: Theme.of(context).iconTheme.color, + return StreamBuilder( + stream: prefBloc.timezoneStream, + builder: (context, + AsyncSnapshot timezoneSnapshot) { + switch (timezoneSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + String date = + (forecastSnapshot.data.sunRise == null) + ? null + : DateFormat.Hms().format( + // Sunrise in user desired timezone + UnitConverter.timezoneConverter( + offset: forecastSnapshot.data.offset, + time: forecastSnapshot.data.sunRise, + timezone: timezoneSnapshot.data, + ), + ); + // Timezone name + String unit = UnitConverter.timezoneName( + climateTimezoneName: + forecastSnapshot.data.timezoneName, + timezone: timezoneSnapshot.data, + ); + return ListTileTheme( + child: ListTile( + title: Text( + 'Sunrise($unit)', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + date ?? 'NaN', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16.0), + ), + trailing: + Icon(WeatherIcons.sunrise, size: 26.0), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -178,7 +222,6 @@ class ForecastDetailSunrise extends StatelessWidget { } }, ); - break; default: return Container(); @@ -193,56 +236,71 @@ class ForecastDetailSunset extends StatelessWidget { const ForecastDetailSunset(); @override Widget build(BuildContext context) { - print('ForecastDetailSunset'); - ForecastBloc bloc = BlocProvider.of(context); + // print('ForecastDetailSunset'); PreferencesBloc prefBloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.forecastStream, - builder: (context, forecastSnapshot) { - switch (forecastSnapshot.connectionState) { + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return StreamBuilder( - stream: prefBloc.timezoneStream, - builder: - (context, AsyncSnapshot timezoneSnapshot) { - switch (timezoneSnapshot.connectionState) { + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.forecastStream, + builder: (context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - String date = - // Sunrise is null - (forecastSnapshot.data?.sunSet == null) - ? null - : DateFormat.Hms().format( - // Sunrise in user desired timezone - UnitConverter.timezoneConverter( - offset: forecastSnapshot.data.offset, - time: forecastSnapshot.data.sunSet, - timezone: timezoneSnapshot.data, + return StreamBuilder( + stream: prefBloc.timezoneStream, + builder: (context, + AsyncSnapshot timezoneSnapshot) { + switch (timezoneSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + String date = + // Sunrise is null + (forecastSnapshot.data?.sunSet == null) + ? null + : DateFormat.Hms().format( + // Sunrise in user desired timezone + UnitConverter.timezoneConverter( + offset: forecastSnapshot.data.offset, + time: forecastSnapshot.data.sunSet, + timezone: timezoneSnapshot.data, + ), + ); + // Timezone name + String unit = UnitConverter.timezoneName( + climateTimezoneName: + forecastSnapshot.data.timezoneName, + timezone: timezoneSnapshot.data, + ); + return ListTileTheme( + child: ListTile( + title: Text( + 'Sunset($unit)', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), ), - ); - // Timezone name - String unit = UnitConverter.timezoneName( - climateTimezoneName: forecastSnapshot.data.timezoneName, - timezone: timezoneSnapshot.data, - ); - return ListTileTheme( - child: ListTile( - title: Text( - 'Sunset($unit)', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - date ?? 'NaN', - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(WeatherIcons.sunset, size: 26.0), - ), - iconColor: Theme.of(context).iconTheme.color, + subtitle: Text( + date ?? 'NaN', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16.0), + ), + trailing: Icon(WeatherIcons.sunset, size: 26.0), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -250,7 +308,6 @@ class ForecastDetailSunset extends StatelessWidget { } }, ); - break; default: return Container(); @@ -265,33 +322,45 @@ class ForecastDetailTimezone extends StatelessWidget { const ForecastDetailTimezone(); @override Widget build(BuildContext context) { - print('ForecastDetailTimezone'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.forecastStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailTimezone'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTileTheme( - child: ListTile( - title: Text( - 'Timezone', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - snapshot.data?.timezone ?? 'NaN', - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(FontAwesomeIcons.globe), - ), - iconColor: Theme.of(context).iconTheme.color, + return StreamBuilder( + stream: forecastBloc.forecastStream, + builder: (context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + theme = themeSnapshot.data; + return ListTileTheme( + child: ListTile( + title: Text( + 'Timezone', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + forecastSnapshot.data?.timezone ?? 'NaN', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, fontSize: 16.0), + ), + trailing: Icon(FontAwesomeIcons.globe), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -307,35 +376,46 @@ class ForecastDetailTimezoneName extends StatelessWidget { const ForecastDetailTimezoneName(); @override Widget build(BuildContext context) { - print('ForecastDetailTimezoneName'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.timezoneNameStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailTimezoneName'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTileTheme( - child: ListTile( - title: Text( - 'Timezone Name', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - snapshot.data, - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(FontAwesomeIcons.mapMarked), - ), - iconColor: Theme.of(context).iconTheme.color, + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.timezoneNameStream, + builder: (context, timezoneSnapshot) { + switch (timezoneSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: ListTile( + title: Text( + 'Timezone Name', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + timezoneSnapshot.data, + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, fontSize: 16.0), + ), + trailing: Icon(FontAwesomeIcons.mapMarked), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); - break; default: return Container(); @@ -350,48 +430,64 @@ class ForecastDetailWindSpeed extends StatelessWidget { const ForecastDetailWindSpeed(); @override Widget build(BuildContext context) { - print('ForecastDetailWindSpeed'); - ForecastBloc bloc = BlocProvider.of(context); + // print('ForecastDetailWindSpeed'); PreferencesBloc prefBloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: (context, forecastSnapshot) { - switch (forecastSnapshot.connectionState) { + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return StreamBuilder( - stream: prefBloc.windStream, - builder: (context, AsyncSnapshot windSnapshot) { - switch (windSnapshot.connectionState) { + theme = themeSnapshot.data; + + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - // Wind saved unit - String unit = UnitConverter.strUnit(windSnapshot.data); - // Wind speed in user desired unit, can be null - double wind = UnitConverter.windConverter( - amount: forecastSnapshot.data?.windSpeed, - unit: windSnapshot.data, - ); - return ListTileTheme( - child: ListTile( - title: Text( - 'Wind', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - // wind speed is null - (wind == null) - ? 'NaN' - : '${wind.toStringAsFixed(2)} $unit', - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(FontAwesomeIcons.wind), - ), - iconColor: Theme.of(context).iconTheme.color, + return StreamBuilder( + stream: prefBloc.windStream, + builder: (context, windSnapshot) { + switch (windSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + // Saved Wind unit + String unit = + UnitConverter.strUnit(windSnapshot.data); + // Wind speed in user desired unit, can be null + double wind = UnitConverter.windConverter( + amount: forecastSnapshot.data?.windSpeed, + unit: windSnapshot.data, + ); + return ListTileTheme( + child: ListTile( + title: Text( + 'Wind', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + // wind speed is null + (wind == null) + ? 'NaN' + : '${wind.toStringAsFixed(2)} $unit', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16.0), + ), + trailing: Icon(FontAwesomeIcons.wind), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -399,7 +495,6 @@ class ForecastDetailWindSpeed extends StatelessWidget { } }, ); - break; default: return Container(); @@ -414,36 +509,48 @@ class ForecastDetailWindDir extends StatelessWidget { const ForecastDetailWindDir(); @override Widget build(BuildContext context) { - print('ForecastDetailWindDir'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailWindDir'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTileTheme( - child: ListTile( - title: Text( - 'Wind Direction', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - // wind direction is null - (snapshot.data?.windDirection == null) - ? 'NaN' - : '${snapshot.data.windDirection.toStringAsFixed(2)}°', - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(CustomIcon.weathercock, size: 28.0), - ), - iconColor: Theme.of(context).iconTheme.color, + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: ListTile( + title: Text( + 'Wind Direction', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + // wind direction is null + (snapshot.data?.windDirection == null) + ? 'NaN' + : '${snapshot.data.windDirection.toStringAsFixed(2)}°', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, fontSize: 16.0), + ), + trailing: Icon(CustomIcon.weathercock, size: 28.0), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -459,35 +566,48 @@ class ForecastDetailWindDirCompass extends StatelessWidget { const ForecastDetailWindDirCompass(); @override Widget build(BuildContext context) { - print('ForecastDetailWindDirCompass'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailWindDirCompass'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTileTheme( - child: ListTile( - title: Text( - 'Wind Direction Compass', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - // wind compass is null - snapshot.data?.windDirectionCompass ?? 'NaN', - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(FontAwesomeIcons.compass, size: 26.0), - ), - iconColor: Theme.of(context).iconTheme.color, + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: ListTile( + title: Text( + 'Wind Direction Compass', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + // wind compass is null + snapshot.data?.windDirectionCompass ?? 'NaN', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, fontSize: 16.0), + ), + trailing: Icon(FontAwesomeIcons.compass, size: 26.0), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); + break; default: return Container(); @@ -502,36 +622,48 @@ class ForecastDetailHumidity extends StatelessWidget { const ForecastDetailHumidity(); @override Widget build(BuildContext context) { - print('ForecastDetailHumidity'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailHumidity'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTileTheme( - child: ListTile( - title: Text( - 'Humidity', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - // Humidity is null - (snapshot.data?.humidity == null) - ? 'NaN' - : '${snapshot.data.humidity}%', - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(WeatherIcons.humidity, size: 30.0), - ), - iconColor: Theme.of(context).iconTheme.color, + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: ListTile( + title: Text( + 'Humidity', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + // Humidity is null + (snapshot.data?.humidity == null) + ? 'NaN' + : '${snapshot.data.humidity}%', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, fontSize: 16.0), + ), + trailing: Icon(WeatherIcons.humidity, size: 30.0), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -547,48 +679,62 @@ class ForecastDetailVisibility extends StatelessWidget { const ForecastDetailVisibility(); @override Widget build(BuildContext context) { - print('ForecastDetailVisibility'); - ForecastBloc bloc = BlocProvider.of(context); + // print('ForecastDetailVisibility'); PreferencesBloc prefBloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: (context, forecastSnapshot) { - switch (forecastSnapshot.connectionState) { + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return StreamBuilder( - stream: prefBloc.distanceStream, - builder: (context, distanceSnapshot) { - switch (distanceSnapshot.connectionState) { + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - // Length saved unit - DistanceUnit unit = distanceSnapshot.data; - // Length in user desired unit, can be null - double distance = UnitConverter.distanceConverter( - unit: unit, - amount: forecastSnapshot.data?.visibility, - ); - return ListTileTheme( - child: ListTile( - title: Text( - 'Visibility', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - // Visibility is null - (forecastSnapshot.data?.visibility == null) - ? 'NaN' - : '${distance.toStringAsFixed(2)} ${UnitConverter.strUnit(unit)}', - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(FontAwesomeIcons.eye), - ), - iconColor: Theme.of(context).iconTheme.color, + return StreamBuilder( + stream: prefBloc.distanceStream, + builder: (context, distanceSnapshot) { + switch (distanceSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + // saved Length unit + DistanceUnit unit = distanceSnapshot.data; + // Length in user desired unit, can be null + double distance = UnitConverter.distanceConverter( + unit: unit, + amount: forecastSnapshot.data?.visibility, + ); + return ListTileTheme( + child: ListTile( + title: Text( + 'Visibility', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + // Visibility is null + (forecastSnapshot.data?.visibility == null) + ? 'NaN' + : '${distance.toStringAsFixed(2)} ${UnitConverter.strUnit(unit)}', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16.0), + ), + trailing: Icon(FontAwesomeIcons.eye), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -610,48 +756,63 @@ class ForecastDetailPressure extends StatelessWidget { const ForecastDetailPressure(); @override Widget build(BuildContext context) { - print('ForecastDetailPressure'); - ForecastBloc bloc = BlocProvider.of(context); + // print('ForecastDetailPressure'); PreferencesBloc prefBloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: (context, forecastSnapshot) { - switch (forecastSnapshot.connectionState) { + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return StreamBuilder( - stream: prefBloc.pressureStream, - builder: (context, AsyncSnapshot pressureSnapshot) { - switch (pressureSnapshot.connectionState) { + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - // Pressure saved unit - PressureUnit unit = pressureSnapshot.data; - // Pressure in user desired unit, can be null - double pressure = UnitConverter.pressureConverter( - amount: forecastSnapshot.data?.airPressure, - unit: unit, - ); - return ListTileTheme( - child: ListTile( - title: Text( - 'Air Pressure', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - // Pressure is null - (pressure == null) - ? 'NaN' - : '${pressure.toStringAsFixed(2)} ${UnitConverter.strUnit(unit)}', - style: Theme.of(context).textTheme.bodyText2.copyWith( - fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(CustomIcon.gauge, size: 30.0), - ), - iconColor: Theme.of(context).iconTheme.color, + return StreamBuilder( + stream: prefBloc.pressureStream, + builder: (context, + AsyncSnapshot pressureSnapshot) { + switch (pressureSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + // Saved Pressure unit + PressureUnit unit = pressureSnapshot.data; + // Pressure in user desired unit, can be null + double pressure = UnitConverter.pressureConverter( + amount: forecastSnapshot.data?.airPressure, + unit: unit, + ); + return ListTileTheme( + child: ListTile( + title: Text( + 'Air Pressure', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + // Pressure is null + (pressure == null) + ? 'NaN' + : '${pressure.toStringAsFixed(2)} ${UnitConverter.strUnit(unit)}', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, + fontSize: 16.0), + ), + trailing: Icon(CustomIcon.gauge, size: 30.0), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: @@ -673,36 +834,48 @@ class ForecastDetailConfidence extends StatelessWidget { const ForecastDetailConfidence(); @override Widget build(BuildContext context) { - print('ForecastDetailConfidence'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailConfidence'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return ListTileTheme( - child: ListTile( - title: Text( - 'Confidence', - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - // predictability is null - (snapshot.data?.predictability == null) - ? 'NaN' - : '${snapshot.data.predictability}%', - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - trailing: Icon(CustomIcon.ball, size: 30.0), - ), - iconColor: Theme.of(context).iconTheme.color, + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return ListTileTheme( + child: ListTile( + title: Text( + 'Confidence', + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + // predictability is null + (snapshot.data?.predictability == null) + ? 'NaN' + : '${snapshot.data.predictability}%', + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, fontSize: 16.0), + ), + trailing: Icon(CustomIcon.ball, size: 30.0), + ), + iconColor: theme.iconTheme.color, + ); + break; + default: + return Container(); + } + }, ); break; default: diff --git a/lib/src/widgets/persist_header.dart b/lib/src/widgets/persist_header.dart index 46e9ead..756c1ea 100644 --- a/lib/src/widgets/persist_header.dart +++ b/lib/src/widgets/persist_header.dart @@ -29,7 +29,6 @@ class PersistHeader extends SliverPersistentHeaderDelegate { double shrinkOffset, bool overlapsContent, ) { - print('PersistHeader'); double top = MediaQuery.of(context).padding.top; return Stack( children: [ @@ -86,10 +85,9 @@ class PersistHeaderBg extends StatelessWidget { const PersistHeaderBg(); @override Widget build(BuildContext context) { - print('PersistHeaderBg'); + // print('PersistHeaderBg'); ForecastBloc bloc = BlocProvider.of(context); - - return StreamBuilder( + return StreamBuilder( stream: bloc.climateColorStream, builder: (context, snapshot) { switch (snapshot.connectionState) { @@ -116,10 +114,10 @@ class PersistHeaderAppBar extends StatelessWidget { const PersistHeaderAppBar(); @override Widget build(BuildContext context) { - print('PersistHeaderAppBar'); + // print('PersistHeaderAppBar'); ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( + return StreamBuilder( stream: bloc.climateColorStream, builder: (context, snapshot) { switch (snapshot.connectionState) { @@ -152,19 +150,17 @@ class PersistHeaderWide extends StatelessWidget { const PersistHeaderWide({Key key}) : super(key: key); @override Widget build(BuildContext context) { - print('PersistHeaderWide'); - ForecastBloc bloc = BlocProvider.of(context); + // print('PersistHeaderWide'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); return StreamBuilder( - stream: bloc.forecastStream, + stream: forecastBloc.forecastStream, builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: LocationClimate climate = snapshot.data; - print('object'); - print(climate.sunRise); - print(climate.sunRise.toIso8601String()); return Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, @@ -185,42 +181,55 @@ class PersistHeaderWide extends StatelessWidget { ), Flexible( flex: 3, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 3, - child: FittedBox( - alignment: Alignment.centerLeft, - child: Text( - climate.title, - style: Theme.of(context) - .textTheme - .headline4 - .copyWith( - color: Colors.white, + child: StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 3, + child: FittedBox( + alignment: Alignment.centerLeft, + child: Text( + climate.title, + style: themeSnapshot + .data.textTheme.headline4 + .copyWith( + color: Colors.white, + ), + ), ), - ), - ), - ), - Expanded( - flex: 2, - child: FittedBox( - alignment: Alignment.centerLeft, - child: Text( - DateFormat.yMd() - .format(climate.time.add(climate.offset)), - style: Theme.of(context) - .textTheme - .caption - .copyWith( - color: Colors.white54, - fontWeight: FontWeight.w300, + ), + Expanded( + flex: 2, + child: FittedBox( + alignment: Alignment.centerLeft, + child: Text( + (climate.time == null) + ? 'NaN' + : DateFormat.yMd().format(climate + .time + ?.add(climate.offset)), + style: themeSnapshot + .data.textTheme.caption + .copyWith( + color: Colors.white54, + fontWeight: FontWeight.w300, + ), + ), ), - ), - ), - ), - ], + ), + ], + ); + break; + default: + return const Skeleton(); + } + }, ), ), ], @@ -228,8 +237,8 @@ class PersistHeaderWide extends StatelessWidget { ), Expanded( flex: 3, - child: StreamBuilder( - stream: bloc.todayForecastStream, + child: StreamBuilder( + stream: forecastBloc.todayForecastStream, builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.active: @@ -269,17 +278,17 @@ class PersistHeaderNarrow extends StatelessWidget { const PersistHeaderNarrow(); @override Widget build(BuildContext context) { - print('PersistHeaderNarrow'); - ForecastBloc bloc = BlocProvider.of(context); + // print('PersistHeaderNarrow'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.forecastStream, + return StreamBuilder( + stream: forecastBloc.forecastStream, builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: LocationClimate climate = snapshot.data; - return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, @@ -307,44 +316,58 @@ class PersistHeaderNarrow extends StatelessWidget { ), Flexible( flex: 5, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - flex: 3, - child: FittedBox( - alignment: Alignment.bottomLeft, - child: Text( - climate.title, - style: Theme.of(context) - .textTheme - .headline4 - .copyWith( - color: Colors.white, + child: StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + flex: 3, + child: FittedBox( + alignment: Alignment.bottomLeft, + child: Text( + climate.title, + style: themeSnapshot + .data.textTheme.headline4 + .copyWith( + color: Colors.white, + ), + ), ), - ), - ), - ), - Expanded( - flex: 2, - child: FittedBox( - alignment: Alignment.topLeft, - child: Text( - DateFormat.yMd().format( - climate.time.add(climate.offset)), - style: Theme.of(context) - .textTheme - .caption - .copyWith( - color: Colors.white54, - fontWeight: FontWeight.w300, + ), + Expanded( + flex: 2, + child: FittedBox( + alignment: Alignment.topLeft, + child: Text( + (climate.time == null) + ? 'NaN' + : DateFormat.yMd().format( + climate?.time + ?.add(climate.offset)), + style: themeSnapshot + .data.textTheme.caption + .copyWith( + color: Colors.white54, + fontWeight: FontWeight.w300, + ), + ), ), - ), - ), - ), - ], + ), + ], + ); + break; + default: + return const Skeleton(); + } + }, ), ), ], @@ -353,8 +376,8 @@ class PersistHeaderNarrow extends StatelessWidget { ), Expanded( flex: 2, - child: StreamBuilder( - stream: bloc.todayForecastStream, + child: StreamBuilder( + stream: forecastBloc.todayForecastStream, builder: (context, snapshot) { switch (snapshot.connectionState) { case ConnectionState.active: @@ -395,33 +418,44 @@ class PersistHeaderLastUpdate extends StatelessWidget { const PersistHeaderLastUpdate(); @override Widget build(BuildContext context) { - print('PersistHeaderLastUpdate'); - ForecastBloc bloc = BlocProvider.of(context); + // print('PersistHeaderLastUpdate'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.todayForecastStream, - builder: (context, climateSnapshot) { - switch (climateSnapshot.connectionState) { + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - DateTime time = climateSnapshot.data.created; - return SizedBox( - height: 24.0, - width: MediaQuery.of(context).size.width / 2, - child: FittedBox( - alignment: Alignment.bottomLeft, - child: Text( - 'Updated ${_lastUpdate(time)}', - style: Theme.of(context) - .textTheme - .overline - .copyWith(color: Colors.white70), - ), - ), + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, climateSnapshot) { + switch (climateSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + DateTime time = climateSnapshot.data.created; + return SizedBox( + height: 24.0, + width: MediaQuery.of(context).size.width / 2, + child: FittedBox( + alignment: Alignment.bottomLeft, + child: Text( + 'Updated ${_lastUpdate(time)}', + style: themeSnapshot.data.textTheme.overline + .copyWith(color: Colors.white70), + ), + ), + ); + break; + default: + return Container(); + } + }, ); break; default: - return Container(); + return const Skeleton(); } }, ); diff --git a/lib/src/widgets/skeleton.dart b/lib/src/widgets/skeleton.dart index 8d4c2ae..f89393e 100644 --- a/lib/src/widgets/skeleton.dart +++ b/lib/src/widgets/skeleton.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; class Skeleton extends StatefulWidget { final double width, height; - const Skeleton({Key key, this.width, this.height}) : super(key: key); @override _SkeletonState createState() => _SkeletonState(); @@ -12,8 +11,6 @@ class _SkeletonState extends State with SingleTickerProviderStateMixin { AnimationController _animationController; Animation _animation; - bool _isDark = false; - @override void initState() { _animationController = @@ -26,21 +23,18 @@ class _SkeletonState extends State curve: Curves.linear, ), ); - _animationController.repeat(); - super.initState(); } @override Widget build(BuildContext context) { - _isDark = Theme.of(context).brightness == Brightness.dark; return Container( width: widget.width, height: widget.height, decoration: BoxDecoration( gradient: LinearGradient( - colors: _isDark + colors: Theme.of(context).brightness == Brightness.dark ? [Colors.white12, Colors.white24, Colors.white12] : [Colors.black12, Colors.black26, Colors.black12], begin: Alignment(_animation.value, 0), diff --git a/lib/src/widgets/sources.dart b/lib/src/widgets/sources.dart index 541405b..ee9f838 100644 --- a/lib/src/widgets/sources.dart +++ b/lib/src/widgets/sources.dart @@ -1,5 +1,4 @@ -import 'package:climate/src/blocs/bloc_provider.dart'; -import 'package:climate/src/blocs/forecast_bloc.dart'; +import 'package:climate/src/blocs/blocs.dart'; import 'package:climate/src/models/models.dart'; import 'package:climate/src/widgets/widgets.dart'; import 'package:flutter/material.dart'; @@ -9,70 +8,83 @@ class Sources extends StatelessWidget { const Sources(); @override Widget build(BuildContext context) { - print('Sources'); - ForecastBloc bloc = BlocProvider.of(context); - return StreamBuilder( - stream: bloc.forecastStream, - builder: (context, snapshot) { - switch (snapshot.connectionState) { + // print('ForecastDetailSunset'); + PreferencesBloc prefBloc = BlocProvider.of(context); + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - List sources = snapshot.data?.sources ?? []; - return ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - Source source = sources[index]; - return ListTile( - title: Text( - source.title, - style: Theme.of(context) - .textTheme - .subtitle2 - .copyWith(fontSize: 12.0), - ), - subtitle: Text( - source.url, - overflow: TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontWeight: FontWeight.w500, fontSize: 16.0), - ), - onTap: () async { - await canLaunch(source.url) - .then((value) async => await launch(source.url)) - .catchError((onError) async { - await showDialog( - context: context, - child: AlertDialog( - title: Text('Error!'), - content: Text("Can't launch ${source.url}"), - actions: [ - FlatButton( - onPressed: () => Navigator.of(context).pop(), - child: Text('Ok'), - ) - ], - ), - ); - }); - }, - ); + theme = themeSnapshot.data; + return StreamBuilder( + stream: forecastBloc.forecastStream, + builder: (context, snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + List sources = snapshot.data?.sources ?? []; + return ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + Source source = sources[index]; + return ListTile( + title: Text( + source.title, + style: theme.textTheme.subtitle2 + .copyWith(fontSize: 12.0), + ), + subtitle: Text( + source.url, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.bodyText2.copyWith( + fontWeight: FontWeight.w500, fontSize: 16.0), + ), + onTap: () async { + await canLaunch(source.url) + .then((value) async => await launch(source.url)) + .catchError((onError) async { + await showDialog( + context: context, + child: AlertDialog( + title: Text('Error!'), + content: Text("Can't launch ${source.url}"), + actions: [ + FlatButton( + onPressed: () => + Navigator.of(context).pop(), + child: Text('Ok'), + ) + ], + ), + ); + }); + }, + ); + }, + itemCount: sources.length, + ); + break; + default: + return ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: 6, + itemBuilder: (context, index) => ListTile( + title: Skeleton(height: 36.0), + trailing: Skeleton(width: 36.0, height: 36.0), + ), + ); + } }, - itemCount: sources.length, ); break; default: - return ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: 6, - itemBuilder: (context, index) => ListTile( - title: Skeleton(height: 36.0), - trailing: Skeleton(width: 36.0, height: 36.0), - ), - ); + return const Skeleton(); } }, ); diff --git a/lib/src/widgets/weather_status.dart b/lib/src/widgets/weather_status.dart index b1d364c..43f4394 100644 --- a/lib/src/widgets/weather_status.dart +++ b/lib/src/widgets/weather_status.dart @@ -1,108 +1,105 @@ import 'package:climate/src/blocs/blocs.dart'; +import 'package:climate/src/configs/configs.dart'; import 'package:climate/src/models/models.dart'; import 'package:climate/src/utils/utils.dart'; -import 'package:climate/src/widgets/widgets.dart'; import 'package:flutter/material.dart'; class WeatherStatus extends StatelessWidget { const WeatherStatus({Key key}) : super(key: key); @override Widget build(BuildContext context) { - print('WeatherStatus'); - ForecastBloc forecastBloc = BlocProvider.of(context); + // print('LocationTile'); PreferencesBloc prefBloc = BlocProvider.of(context); - - return StreamBuilder( - stream: forecastBloc.todayForecastStream, - builder: (context, AsyncSnapshot forecastSnapshot) { - switch (forecastSnapshot.connectionState) { + ForecastBloc forecastBloc = BlocProvider.of(context); + ThemeData theme; + TempUnit unit; + double temp; + return StreamBuilder( + stream: prefBloc.themeStream, + builder: (context, themeSnapshot) { + switch (themeSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - StreamBuilder( - stream: prefBloc.tempStream, - builder: (context, AsyncSnapshot tempSnapshot) { - switch (tempSnapshot.connectionState) { - case ConnectionState.active: - case ConnectionState.done: - // Temp saved unit - TempUnit unit = tempSnapshot.data; - // Temp in user desired unit - double temp = UnitConverter.tempConverter( - unit: unit, - amount: forecastSnapshot.data?.theTemp, - ); - // Temp unit in weather condition Color - return StreamBuilder( - stream: forecastBloc.climateColorStream, - builder: (context, colorSnapshot) { - switch (colorSnapshot.connectionState) { - case ConnectionState.active: - case ConnectionState.done: - return RichText( + // saved Theme data + theme = themeSnapshot.data; + return StreamBuilder( + stream: prefBloc.tempStream, + builder: (context, AsyncSnapshot tempSnapshot) { + switch (tempSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + // saved temp unit + unit = tempSnapshot.data; + return StreamBuilder( + stream: forecastBloc.todayForecastStream, + builder: (context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + // Temp in user desired unit + temp = UnitConverter.tempConverter( + unit: unit, + amount: forecastSnapshot.data?.theTemp, + ); + return Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + RichText( text: TextSpan( style: TextStyle( fontSize: 45.0, - color: Theme.of(context).brightness == - Brightness.dark + color: theme.brightness == Brightness.dark ? Colors.white : Colors.black87, fontWeight: FontWeight.bold, ), - text: - // temp is null - (forecastSnapshot.data?.theTemp == null) - ? 'NaN' - : '${temp.floor()}', + // If temp is null, display NaN + text: (temp == null) + ? 'NaN' + : '${temp.floor()}', children: [ TextSpan( text: unit == TempUnit.K ? '${UnitConverter.strUnit(unit).toUpperCase()}' : '°${UnitConverter.strUnit(unit).toUpperCase()}', style: TextStyle( - color: colorSnapshot.data, + color: CustomColor.weatherStateColor( + weatherStateAbbr: forecastSnapshot + .data.weatherStateAbbr), ), ) ], ), - ); - break; - default: - return Container(); - } - }, - ); - break; - default: - return Container(); - } - }, - ), - FittedBox( - fit: BoxFit.cover, - alignment: Alignment.center, - child: Text( - forecastSnapshot.data.weatherStateName, - style: Theme.of(context).textTheme.bodyText1.copyWith( - fontWeight: FontWeight.w700, - ), - ), - ) - ], + ), + FittedBox( + fit: BoxFit.cover, + alignment: Alignment.center, + child: Text( + forecastSnapshot.data.weatherStateName, + style: theme.textTheme.bodyText1.copyWith( + fontWeight: FontWeight.w700, + ), + ), + ) + ], + ); + break; + default: + return Container(); + } + }, + ); + break; + default: + return Container(); + } + }, ); - break; default: - return Container( - margin: EdgeInsets.symmetric(horizontal: 64.0), - child: const Skeleton(), - width: 64.0, - height: 64.0, - ); + return Container(); } }, ); diff --git a/lib/src/widgets/week_forecast.dart b/lib/src/widgets/week_forecast.dart index cb6fb77..aa0ed4f 100644 --- a/lib/src/widgets/week_forecast.dart +++ b/lib/src/widgets/week_forecast.dart @@ -9,96 +9,56 @@ class WeekForecast extends StatelessWidget { const WeekForecast(); @override Widget build(BuildContext context) { - print('WeekForecast'); - ForecastBloc forecastBloc = BlocProvider.of(context); - PreferencesBloc prefBloc = BlocProvider.of(context); - int minTemp; - int maxTemp; - String unit; - return StreamBuilder( - stream: prefBloc.tempStream, - builder: (context, tempSnapshot) { - switch (tempSnapshot.connectionState) { + ForecastBloc bloc = BlocProvider.of(context); + + return StreamBuilder( + stream: bloc.forecastStream, + builder: (context, forecastSnapshot) { + switch (forecastSnapshot.connectionState) { case ConnectionState.active: case ConnectionState.done: - return StreamBuilder( - stream: forecastBloc.forecastStream, - builder: (context, forecastSnapshot) { - switch (forecastSnapshot.connectionState) { - case ConnectionState.active: - case ConnectionState.done: - return CustomExpansion( - children: forecastSnapshot.data.consolidatedWeather - .map((forecast) { - // min temp in user desired unit - minTemp = UnitConverter.tempConverter( - amount: forecast.minTemp, - unit: tempSnapshot.data, - ).floor(); - - // max temp in user desired unit - maxTemp = UnitConverter.tempConverter( - amount: forecast.maxTemp, - unit: tempSnapshot.data, - ).floor(); - unit = - // Kelvin comes without degree sign - tempSnapshot.data == TempUnit.K - ? 'K' - : '°' + - UnitConverter.strUnit(tempSnapshot.data) - .toUpperCase(); - - return Container( - height: 36.0, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '${_dayName(forecast.applicableDate)}', - style: Theme.of(context) - .textTheme - .bodyText1 - .copyWith(fontWeight: FontWeight.w500), - ), - Text( - '$minTemp/$maxTemp' + unit, - style: Theme.of(context) - .textTheme - .bodyText1 - .copyWith(fontWeight: FontWeight.w500), - ), - ], - ), - ); - }).toList(), - ); - break; - default: - return ListView.builder( - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: 6, - itemBuilder: (context, index) => ListTile( - title: const Skeleton(height: 36.0), - trailing: const Skeleton(width: 64, height: 36.0), - ), - ); - } - }, + return CustomExpansion( + children: + forecastSnapshot.data.consolidatedWeather.map((forecast) { + return ExpansionTempTile( + applicableDate: forecast.applicableDate, + maxTemp: forecast.maxTemp, + minTemp: forecast.minTemp, + ); + }).toList(), + itemExtent: 36.0, ); + break; default: - return const Skeleton(); + return ListView.builder( + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: 6, + itemBuilder: (context, index) => ListTile( + title: const Skeleton(height: 36.0), + trailing: const Skeleton(width: 64, height: 36.0), + ), + ); } }, ); } +} - String _dayName(DateTime dateTime) { - //TODO:: Date times based on user defined timezone - +class ExpansionTempTile extends StatelessWidget { + final double minTemp; + final double maxTemp; + final DateTime applicableDate; + const ExpansionTempTile({this.minTemp, this.maxTemp, this.applicableDate}); + @override + Widget build(BuildContext context) { + PreferencesBloc bloc = BlocProvider.of(context); + int minimumTemp; + int maximumTemp; + TempUnit unit; + String unitStr = ''; + String dateStr = ''; DateFormat _df = DateFormat.Md(); // Time in Local DateTime now = DateTime.now(); @@ -106,11 +66,65 @@ class WeekForecast extends StatelessWidget { DateTime today = DateTime.utc(now.year, now.month, now.day); DateTime tomorrow = DateTime.utc(now.year, now.month, now.day + 1); - if (dateTime.isAtSameMomentAs(today)) - return "Today"; - else if (dateTime.isAtSameMomentAs(tomorrow)) - return "Tomorrow.${_df.format(dateTime)}"; - else - return _df.format(dateTime); + return StreamBuilder( + stream: bloc.tempStream, + builder: (context, tempSnapshot) { + switch (tempSnapshot.connectionState) { + case ConnectionState.active: + case ConnectionState.done: + unit = tempSnapshot.data; + + if (applicableDate.isAtSameMomentAs(today)) + dateStr = "Today"; + else if (applicableDate.isAtSameMomentAs(tomorrow)) + dateStr = "Tomorrow.${_df.format(applicableDate)}"; + else + dateStr = _df.format(applicableDate); + // min temp in user desired unit + minimumTemp = UnitConverter.tempConverter( + amount: minTemp, + unit: unit, + ).floor(); + + // max temp in user desired unit + maximumTemp = UnitConverter.tempConverter( + amount: maxTemp, + unit: unit, + ).floor(); + + // Kelvin comes without degree sign + unitStr = tempSnapshot.data == TempUnit.K + ? 'K' + : '°' + UnitConverter.strUnit(tempSnapshot.data).toUpperCase(); + + return Container( + height: 36.0, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '$dateStr', + style: Theme.of(context) + .textTheme + .bodyText1 + .copyWith(fontWeight: FontWeight.w500), + ), + Text( + '$minimumTemp/$maximumTemp' + unitStr, + style: Theme.of(context) + .textTheme + .bodyText1 + .copyWith(fontWeight: FontWeight.w500), + ), + ], + ), + ); + break; + default: + return Container(); + } + }, + ); } }