Skip to content

Commit f420bd3

Browse files
authored
Merge pull request #6 from LonelyCpp/theme
Dynamic theme selection
2 parents 0522616 + b000099 commit f420bd3

File tree

11 files changed

+299
-152
lines changed

11 files changed

+299
-152
lines changed

ios/Flutter/Debug.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
12
#include "Generated.xcconfig"

ios/Flutter/Release.xcconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
12
#include "Generated.xcconfig"

lib/main.dart

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,94 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_weather/src/screens/weather_screen.dart';
33
import 'package:bloc/bloc.dart';
44
import 'package:flutter_weather/src/themes.dart';
5+
import 'package:shared_preferences/shared_preferences.dart';
56

67
void main() {
78
BlocSupervisor().delegate = SimpleBlocDelegate();
8-
runApp(MyApp());
9+
runApp(AppStateContainer(child: WeatherApp()));
910
}
1011

11-
class MyApp extends StatelessWidget {
12+
class SimpleBlocDelegate extends BlocDelegate {
13+
@override
14+
onTransition(Bloc bloc, Transition transition) {
15+
super.onTransition(bloc, transition);
16+
print(transition);
17+
}
18+
}
19+
20+
class WeatherApp extends StatelessWidget {
1221
@override
1322
Widget build(BuildContext context) {
1423
return MaterialApp(
1524
title: 'Flutter Weather App',
16-
theme: Themes.dark,
25+
theme: AppStateContainer.of(context).theme,
1726
home: WeatherScreen(),
1827
);
1928
}
2029
}
2130

22-
class SimpleBlocDelegate extends BlocDelegate {
31+
/// top level widget to hold application state
32+
/// state is passed down with an inherited widget
33+
class AppStateContainer extends StatefulWidget {
34+
final Widget child;
35+
36+
AppStateContainer({@required this.child});
37+
2338
@override
24-
onTransition(Bloc bloc, Transition transition) {
25-
super.onTransition(bloc, transition);
26-
print(transition);
39+
_AppStateContainerState createState() => _AppStateContainerState();
40+
41+
static _AppStateContainerState of(BuildContext context) {
42+
return (context.inheritFromWidgetOfExactType(_InheritedStateContainer)
43+
as _InheritedStateContainer)
44+
.data;
2745
}
2846
}
47+
48+
class _AppStateContainerState extends State<AppStateContainer> {
49+
ThemeData _theme = Themes.getTheme(Themes.DARK_THEME_CODE);
50+
51+
@override
52+
initState() {
53+
super.initState();
54+
SharedPreferences.getInstance().then((sharedPref) {
55+
int themeCode =
56+
sharedPref.getInt(Themes.SHARED_PREF_KEY) ?? Themes.DARK_THEME_CODE;
57+
setState(() {
58+
this._theme = Themes.getTheme(themeCode);
59+
});
60+
});
61+
}
62+
63+
@override
64+
Widget build(BuildContext context) {
65+
print(theme.accentColor);
66+
return _InheritedStateContainer(
67+
data: this,
68+
child: widget.child,
69+
);
70+
}
71+
72+
ThemeData get theme => _theme;
73+
74+
updateTheme(int themeCode) {
75+
setState(() {
76+
_theme = Themes.getTheme(themeCode);
77+
});
78+
SharedPreferences.getInstance().then((sharedPref) {
79+
sharedPref.setInt(Themes.SHARED_PREF_KEY, themeCode);
80+
});
81+
}
82+
}
83+
84+
class _InheritedStateContainer extends InheritedWidget {
85+
final _AppStateContainerState data;
86+
87+
const _InheritedStateContainer({
88+
Key key,
89+
@required this.data,
90+
@required Widget child,
91+
}) : super(key: key, child: child);
92+
93+
@override
94+
bool updateShouldNotify(_InheritedStateContainer oldWidget) => true;
95+
}

lib/src/screens/weather_screen.dart

Lines changed: 69 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter_weather/main.dart';
23
import 'package:flutter_weather/src/api/weather_api_client.dart';
34
import 'package:flutter_weather/src/bloc/weather_bloc.dart';
45
import 'package:flutter_weather/src/bloc/weather_event.dart';
56
import 'package:flutter_weather/src/bloc/weather_state.dart';
67
import 'package:flutter_weather/src/repository/weather_repository.dart';
78
import 'package:flutter_weather/src/api/api_keys.dart';
89
import 'package:flutter_bloc/flutter_bloc.dart';
10+
import 'package:flutter_weather/src/themes.dart';
911
import 'package:flutter_weather/src/widgets/weather_widget.dart';
1012
import 'package:http/http.dart' as http;
1113
import 'package:intl/intl.dart';
1214

15+
enum OptionsMenu { changeCity, nightMode, lightMode }
16+
1317
class WeatherScreen extends StatefulWidget {
1418
final WeatherRepository weatherRepository = WeatherRepository(
1519
weatherApiClient: WeatherApiClient(
@@ -18,62 +22,73 @@ class WeatherScreen extends StatefulWidget {
1822
_WeatherScreenState createState() => _WeatherScreenState();
1923
}
2024

21-
class _WeatherScreenState extends State<WeatherScreen> with TickerProviderStateMixin {
25+
class _WeatherScreenState extends State<WeatherScreen>
26+
with TickerProviderStateMixin {
2227
WeatherBloc _weatherBloc;
2328
String _cityName = 'bengaluru';
2429
AnimationController _fadeController;
2530
Animation<double> _fadeAnimation;
2631

27-
2832
@override
2933
void initState() {
3034
super.initState();
3135
_weatherBloc = WeatherBloc(weatherRepository: widget.weatherRepository);
3236
_weatherBloc.dispatch(FetchWeather(cityName: _cityName));
3337
_fadeController = AnimationController(
3438
duration: const Duration(milliseconds: 1000), vsync: this);
35-
_fadeAnimation = CurvedAnimation(parent: _fadeController, curve: Curves.easeIn);
39+
_fadeAnimation =
40+
CurvedAnimation(parent: _fadeController, curve: Curves.easeIn);
3641
}
3742

3843
@override
3944
Widget build(BuildContext context) {
4045
return Scaffold(
4146
appBar: AppBar(
42-
backgroundColor: Theme.of(context).primaryColor,
47+
backgroundColor: AppStateContainer.of(context).theme.primaryColor,
4348
elevation: 0,
4449
title: Column(
4550
mainAxisAlignment: MainAxisAlignment.center,
4651
children: <Widget>[
4752
Text(
4853
DateFormat('EEEE, MMMM yyyy').format(DateTime.now()),
4954
style: TextStyle(
50-
color: Theme.of(context).accentColor.withAlpha(80),
55+
color: AppStateContainer.of(context)
56+
.theme
57+
.accentColor
58+
.withAlpha(80),
5159
fontSize: 14),
5260
)
5361
],
5462
),
5563
actions: <Widget>[
56-
GestureDetector(
57-
58-
child: Padding(
59-
padding: EdgeInsets.all(20),
64+
PopupMenuButton<OptionsMenu>(
6065
child: Icon(
61-
Icons.public,
62-
color: Theme.of(context).accentColor,
66+
Icons.more_vert,
67+
color: AppStateContainer.of(context).theme.accentColor,
6368
),
64-
),
65-
onTap: () {
66-
this.showCityChangeDialog();
67-
},
68-
)
69+
onSelected: this._onOptionMenuItemSelected,
70+
itemBuilder: (context) => <PopupMenuEntry<OptionsMenu>>[
71+
PopupMenuItem<OptionsMenu>(
72+
value: OptionsMenu.changeCity,
73+
child: Text("change city"),
74+
),
75+
PopupMenuItem<OptionsMenu>(
76+
value: OptionsMenu.nightMode,
77+
child: Text("night mode"),
78+
),
79+
PopupMenuItem<OptionsMenu>(
80+
value: OptionsMenu.lightMode,
81+
child: Text("light mode"),
82+
),
83+
])
6984
],
7085
),
7186
backgroundColor: Colors.white,
7287
body: Material(
73-
color: Theme.of(context).accentColor,
7488
child: Container(
7589
constraints: BoxConstraints.expand(),
76-
decoration: BoxDecoration(color: Theme.of(context).primaryColor),
90+
decoration: BoxDecoration(
91+
color: AppStateContainer.of(context).theme.primaryColor),
7792
child: FadeTransition(
7893
opacity: _fadeAnimation,
7994
child: BlocBuilder(
@@ -87,10 +102,12 @@ class _WeatherScreenState extends State<WeatherScreen> with TickerProviderStateM
87102
);
88103
} else if (weatherState is WeatherError ||
89104
weatherState is WeatherEmpty) {
90-
String errorText = 'There was an error fetching weather data';
91-
if(weatherState is WeatherError){
92-
if(weatherState.errorCode == 404){
93-
errorText = 'We have trouble fetching weather for $_cityName';
105+
String errorText =
106+
'There was an error fetching weather data';
107+
if (weatherState is WeatherError) {
108+
if (weatherState.errorCode == 404) {
109+
errorText =
110+
'We have trouble fetching weather for $_cityName';
94111
}
95112
}
96113
return Column(
@@ -106,14 +123,18 @@ class _WeatherScreenState extends State<WeatherScreen> with TickerProviderStateM
106123
),
107124
Text(
108125
errorText,
109-
style:
110-
TextStyle(color: Theme.of(context).accentColor),
126+
style: TextStyle(
127+
color: AppStateContainer.of(context)
128+
.theme
129+
.accentColor),
111130
),
112131
FlatButton(
113132
child: Text(
114133
"Try Again",
115-
style:
116-
TextStyle(color: Theme.of(context).accentColor),
134+
style: TextStyle(
135+
color: AppStateContainer.of(context)
136+
.theme
137+
.accentColor),
117138
),
118139
onPressed: _fetchWeather,
119140
)
@@ -122,7 +143,8 @@ class _WeatherScreenState extends State<WeatherScreen> with TickerProviderStateM
122143
} else if (weatherState is WeatherLoading) {
123144
return Center(
124145
child: CircularProgressIndicator(
125-
backgroundColor: Theme.of(context).primaryColor,
146+
backgroundColor:
147+
AppStateContainer.of(context).theme.primaryColor,
126148
),
127149
);
128150
}
@@ -132,18 +154,19 @@ class _WeatherScreenState extends State<WeatherScreen> with TickerProviderStateM
132154
));
133155
}
134156

135-
void showCityChangeDialog() {
157+
void _showCityChangeDialog() {
136158
showDialog(
137159
context: context,
138160
barrierDismissible: true,
139161
builder: (BuildContext context) {
140162
return AlertDialog(
141-
title: Text('Change city'),
163+
backgroundColor: Colors.white,
164+
title: Text('Change city', style: TextStyle(color: Colors.black)),
142165
actions: <Widget>[
143166
FlatButton(
144167
child: Text(
145168
'ok',
146-
style: TextStyle(color: Theme.of(context).primaryColor),
169+
style: TextStyle(color: Colors.black),
147170
),
148171
onPressed: () {
149172
_fetchWeather();
@@ -158,12 +181,28 @@ class _WeatherScreenState extends State<WeatherScreen> with TickerProviderStateM
158181
},
159182
decoration: InputDecoration(
160183
hintText: 'Enter the name of your city',
184+
hintStyle: TextStyle(color: Colors.black),
161185
),
186+
style: TextStyle(color: Colors.black),
162187
),
163188
);
164189
});
165190
}
166191

192+
_onOptionMenuItemSelected(OptionsMenu item) {
193+
switch (item) {
194+
case OptionsMenu.changeCity:
195+
this._showCityChangeDialog();
196+
break;
197+
case OptionsMenu.nightMode:
198+
AppStateContainer.of(context).updateTheme(Themes.DARK_THEME_CODE);
199+
break;
200+
case OptionsMenu.lightMode:
201+
AppStateContainer.of(context).updateTheme(Themes.LIGHT_THEME_CODE);
202+
break;
203+
}
204+
}
205+
167206
_fetchWeather() {
168207
_weatherBloc.dispatch(FetchWeather(cityName: _cityName));
169208
}

lib/src/themes.dart

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import 'package:flutter/material.dart';
22

33
class Themes {
4-
static final dark = ThemeData(
4+
static const SHARED_PREF_KEY = "theme_code";
5+
static const DARK_THEME_CODE = 0;
6+
static const LIGHT_THEME_CODE = 1;
7+
8+
static final _dark = ThemeData(
59
primarySwatch: MaterialColor(
610
Colors.black.value,
711
const <int, Color>{
@@ -19,7 +23,8 @@ class Themes {
1923
),
2024
accentColor: Colors.white,
2125
);
22-
static final light = ThemeData(
26+
27+
static final _light = ThemeData(
2328
primarySwatch: MaterialColor(
2429
Colors.white.value,
2530
const <int, Color>{
@@ -37,4 +42,12 @@ class Themes {
3742
),
3843
accentColor: Colors.black,
3944
);
45+
46+
static ThemeData getTheme(int code) {
47+
if(code == LIGHT_THEME_CODE){
48+
return _light;
49+
}
50+
return _dark;
51+
}
52+
4053
}

lib/src/widgets/forecast_horizontal_widget.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class ForecastHorizontal extends StatelessWidget {
3030
padding: const EdgeInsets.only(left: 10, right: 10),
3131
child: Center(
3232
child: ValueTile(
33-
DateFormat('h aa').format(
33+
DateFormat('E, ha').format(
3434
DateTime.fromMillisecondsSinceEpoch(item.time * 1000)),
3535
'${item.temperature.celsius.round()}°',
3636
iconData: item.getIconData(),

0 commit comments

Comments
 (0)