Skip to content

Commit 7916ccb

Browse files
committed
Improve forecast with chart
1 parent 1489509 commit 7916ccb

File tree

6 files changed

+255
-38
lines changed

6 files changed

+255
-38
lines changed

lib/src/app/app.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@ class App extends StatelessWidget {
2323
title: 'Weather',
2424
debugShowCheckedModeBanner: false,
2525
themeMode: ThemeMode.dark,
26-
darkTheme: yaruDark,
26+
darkTheme: yaruDark.copyWith(
27+
tabBarTheme: TabBarTheme.of(context).copyWith(
28+
labelColor: Colors.white,
29+
unselectedLabelColor: Colors.white.withOpacity(
30+
0.8,
31+
),
32+
),
33+
),
2734
home: const AppPage(),
2835
scrollBehavior: const MaterialScrollBehavior().copyWith(
2936
dragDevices: {
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import 'package:collection/collection.dart';
2+
import 'package:fl_chart/fl_chart.dart';
3+
import 'package:flutter/material.dart';
4+
import 'package:open_weather_client/models/weather_data.dart';
5+
import 'package:yaru/yaru.dart';
6+
7+
import '../../../build_context_x.dart';
8+
import '../../../constants.dart';
9+
import '../weather_data_x.dart';
10+
11+
class ForeCastChart extends StatelessWidget {
12+
const ForeCastChart({super.key, required this.data});
13+
14+
final List<WeatherData> data;
15+
16+
@override
17+
Widget build(BuildContext context) {
18+
return Center(
19+
child: Container(
20+
margin: const EdgeInsets.all(2 * kYaruPagePadding),
21+
decoration: BoxDecoration(
22+
borderRadius: BorderRadius.circular(kYaruContainerRadius),
23+
color: context.theme.colorScheme.surface.withOpacity(0.3),
24+
),
25+
height:
26+
context.mq.size.height - kYaruTitleBarHeight - 4 * kYaruPagePadding,
27+
width: context.mq.size.width - kPaneWidth - 2 * kYaruPagePadding,
28+
child: Padding(
29+
padding: const EdgeInsets.symmetric(vertical: kYaruPagePadding),
30+
child: BarChart(
31+
BarChartData(
32+
titlesData: getTitlesData(context),
33+
borderData: borderData,
34+
barGroups: barGroups,
35+
gridData: const FlGridData(show: false),
36+
alignment: BarChartAlignment.spaceAround,
37+
maxY: data.map((e) => e.temperature.tempMax).max,
38+
minY: data.map((e) => e.temperature.tempMin).min,
39+
),
40+
),
41+
),
42+
),
43+
);
44+
}
45+
46+
FlTitlesData getTitlesData(BuildContext context) => FlTitlesData(
47+
show: true,
48+
bottomTitles: AxisTitles(
49+
sideTitles: SideTitles(
50+
showTitles: true,
51+
reservedSize: 30,
52+
getTitlesWidget: (value, meta) {
53+
return SideTitleWidget(
54+
axisSide: meta.axisSide,
55+
child: Text(
56+
'${data[value.toInt()].temperature.tempMin.toInt()} °',
57+
style: TextStyle(
58+
fontWeight: FontWeight.bold,
59+
color: data[value.toInt()].temperature.tempMin < 5
60+
? YaruColors.purple
61+
: YaruColors.blue,
62+
),
63+
),
64+
);
65+
},
66+
),
67+
),
68+
leftTitles: const AxisTitles(
69+
sideTitles: SideTitles(showTitles: false, reservedSize: 0),
70+
),
71+
topTitles: AxisTitles(
72+
sideTitles: SideTitles(
73+
showTitles: true,
74+
reservedSize: 110,
75+
getTitlesWidget: (value, meta) {
76+
return SideTitleWidget(
77+
axisSide: meta.axisSide,
78+
child: Column(
79+
children: [
80+
Expanded(
81+
child: Text(
82+
data[value.toInt()].getWeekDay(context),
83+
style: const TextStyle(
84+
fontWeight: FontWeight.bold,
85+
),
86+
),
87+
),
88+
const SizedBox(
89+
height: 50,
90+
),
91+
Expanded(
92+
child: Text(
93+
'${data[value.toInt()].temperature.tempMax.toInt()} °',
94+
style: TextStyle(
95+
fontWeight: FontWeight.bold,
96+
color: data[value.toInt()].temperature.tempMax > 30
97+
? YaruColors.orange
98+
: Colors.yellow,
99+
),
100+
),
101+
),
102+
],
103+
),
104+
);
105+
},
106+
),
107+
),
108+
rightTitles: const AxisTitles(
109+
sideTitles: SideTitles(showTitles: false),
110+
),
111+
);
112+
113+
FlBorderData get borderData => FlBorderData(
114+
show: false,
115+
);
116+
117+
LinearGradient getBarGradient({required min, required max}) => LinearGradient(
118+
colors: [
119+
max > 30 ? YaruColors.orange : Colors.yellow,
120+
min < -5 ? YaruColors.purple : YaruColors.blue,
121+
],
122+
begin: Alignment.topCenter,
123+
end: Alignment.bottomCenter,
124+
);
125+
126+
List<BarChartGroupData> get barGroups => data.mapIndexed(
127+
(i, e) {
128+
return BarChartGroupData(
129+
x: i,
130+
barRods: [
131+
BarChartRodData(
132+
width: 30,
133+
toY: data[i].temperature.tempMax,
134+
fromY: data[i].temperature.tempMin + 1,
135+
gradient: getBarGradient(
136+
max: data[i].temperature.tempMax,
137+
min: data[i].temperature.tempMin,
138+
),
139+
),
140+
],
141+
);
142+
},
143+
).toList();
144+
}

lib/src/weather/weather_model.dart

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import 'dart:async';
44

5+
import 'package:collection/collection.dart';
56
import 'package:geocoding_resolver/geocoding_resolver.dart';
67
import 'package:geolocator/geolocator.dart';
8+
import 'package:open_weather_client/models/temperature.dart';
79
import 'package:open_weather_client/open_weather.dart';
810
import 'package:safe_change_notifier/safe_change_notifier.dart';
911

@@ -144,7 +146,7 @@ class WeatherModel extends SafeChangeNotifier {
144146
return fDf;
145147
}
146148

147-
List<WeatherData> get notTodayForeCast {
149+
List<WeatherData> get notTodayFullForecast {
148150
if (_fiveDaysForCast == null) return [];
149151

150152
final foreCast = _fiveDaysForCast!;
@@ -160,6 +162,46 @@ class WeatherModel extends SafeChangeNotifier {
160162
return fDf;
161163
}
162164

165+
List<WeatherData> get notTodayForecastDaily {
166+
if (_fiveDaysForCast == null) return [];
167+
168+
List<WeatherData> value = [];
169+
170+
for (var e in notTodayFullForecast) {
171+
if (value.none((v) => v.getWD() == e.getWD())) {
172+
value.add(
173+
WeatherData(
174+
details: e.details,
175+
temperature: Temperature(
176+
currentTemperature: e.temperature.currentTemperature,
177+
feelsLike: e.temperature.feelsLike,
178+
tempMin: notTodayFullForecast
179+
.where(
180+
(m) => m.getWD() == e.getWD(),
181+
)
182+
.map((e) => e.temperature.tempMin)
183+
.min,
184+
tempMax: notTodayFullForecast
185+
.where(
186+
(m) => m.getWD() == e.getWD(),
187+
)
188+
.map((m) => m.temperature.tempMax)
189+
.max,
190+
pressure: e.temperature.pressure,
191+
humidity: e.temperature.humidity,
192+
),
193+
wind: e.wind,
194+
coordinates: e.coordinates,
195+
name: e.name,
196+
date: e.date,
197+
),
198+
);
199+
}
200+
}
201+
202+
return value;
203+
}
204+
163205
List<WeatherData> get forecast =>
164206
_fiveDaysForCast == null ? [] : _fiveDaysForCast!;
165207

lib/src/weather/weather_page.dart

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:yaru/widgets.dart';
66
import '../../build_context_x.dart';
77
import '../../constants.dart';
88
import '../app/app_model.dart';
9+
import 'view/forecast_chart.dart';
910
import 'view/forecast_tile.dart';
1011
import 'view/today_tile.dart';
1112
import 'weather_data_x.dart';
@@ -27,7 +28,7 @@ class WeatherPage extends StatelessWidget with WatchItMixin {
2728
final todayForeCast =
2829
watchPropertyValue((WeatherModel m) => m.todayForeCast);
2930
final notTodayForeCast =
30-
watchPropertyValue((WeatherModel m) => m.notTodayForeCast);
31+
watchPropertyValue((WeatherModel m) => m.notTodayForecastDaily);
3132
final appModel = watchIt<AppModel>();
3233
final showToday = appModel.tabIndex == 0;
3334

@@ -95,47 +96,53 @@ class WeatherPage extends StatelessWidget with WatchItMixin {
9596
),
9697
),
9798
),
98-
SizedBox(
99-
height: showToday
100-
? 300
101-
: mq.size.height - kYaruPagePadding * 3,
102-
width: mq.size.width,
103-
child: ListView.builder(
104-
itemCount: showToday
105-
? todayForeCast.length
106-
: notTodayForeCast.length,
107-
padding: const EdgeInsetsDirectional.all(
108-
kYaruPagePadding,
109-
),
110-
scrollDirection: Axis.horizontal,
111-
itemBuilder: (context, index) {
112-
if (showToday) {
99+
if (!showToday)
100+
ForeCastChart(
101+
data: notTodayForeCast,
102+
),
103+
if (showToday)
104+
SizedBox(
105+
height: showToday
106+
? 300
107+
: mq.size.height - kYaruPagePadding * 3,
108+
width: mq.size.width,
109+
child: ListView.builder(
110+
itemCount: showToday
111+
? todayForeCast.length
112+
: notTodayForeCast.length,
113+
padding: const EdgeInsetsDirectional.all(
114+
kYaruPagePadding,
115+
),
116+
scrollDirection: Axis.horizontal,
117+
itemBuilder: (context, index) {
118+
if (showToday) {
119+
return ForecastTile(
120+
width: 300,
121+
height: 400,
122+
padding:
123+
const EdgeInsets.only(right: 20),
124+
day: todayForeCast[index]
125+
.getDate(context),
126+
time: todayForeCast[index]
127+
.getTime(context),
128+
selectedData: todayForeCast[index],
129+
fontSize: 15,
130+
);
131+
}
113132
return ForecastTile(
114133
width: 300,
115-
height: 400,
134+
height: mq.size.height,
116135
padding: const EdgeInsets.only(right: 20),
117-
day:
118-
todayForeCast[index].getDate(context),
119-
time:
120-
todayForeCast[index].getTime(context),
121-
selectedData: todayForeCast[index],
136+
day: notTodayForeCast[index]
137+
.getDate(context),
138+
time: notTodayForeCast[index]
139+
.getTime(context),
140+
selectedData: notTodayForeCast[index],
122141
fontSize: 15,
123142
);
124-
}
125-
return ForecastTile(
126-
width: 300,
127-
height: mq.size.height,
128-
padding: const EdgeInsets.only(right: 20),
129-
day: notTodayForeCast[index]
130-
.getDate(context),
131-
time: notTodayForeCast[index]
132-
.getTime(context),
133-
selectedData: notTodayForeCast[index],
134-
fontSize: 15,
135-
);
136-
},
143+
},
144+
),
137145
),
138-
),
139146
],
140147
),
141148
),

pubspec.lock

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ packages:
113113
url: "https://pub.dev"
114114
source: hosted
115115
version: "0.7.10"
116+
equatable:
117+
dependency: transitive
118+
description:
119+
name: equatable
120+
sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
121+
url: "https://pub.dev"
122+
source: hosted
123+
version: "2.0.5"
116124
fake_async:
117125
dependency: transitive
118126
description:
@@ -137,6 +145,14 @@ packages:
137145
url: "https://pub.dev"
138146
source: hosted
139147
version: "1.1.0"
148+
fl_chart:
149+
dependency: "direct main"
150+
description:
151+
name: fl_chart
152+
sha256: "2b7c1f5d867da9a054661641c8f499c55c47c39acccb97b3bc673f5fa9a39e74"
153+
url: "https://pub.dev"
154+
source: hosted
155+
version: "0.67.0"
140156
flutter:
141157
dependency: "direct main"
142158
description: flutter

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies:
3333
path_provider: ^2.1.3
3434
path: ^1.9.0
3535
connectivity_plus: ^6.0.2
36+
fl_chart: ^0.67.0
3637

3738
dev_dependencies:
3839
flutter_lints: ^3.0.1

0 commit comments

Comments
 (0)