Skip to content

Commit 3a595ee

Browse files
committed
feat(neon_dashboard): Display weather status
Signed-off-by: provokateurin <[email protected]>
1 parent 6527733 commit 3a595ee

11 files changed

Lines changed: 291 additions & 3 deletions

File tree

.cspell/nextcloud.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,23 @@ apppassword
33
bigfilechunking
44
bools
55
bulkupload
6+
clearsky
67
datetime
78
dialin
89
dialout
910
displayname
1011
etag
1112
fediverse
13+
heavyrain
14+
heavyrainshowers
1215
iscustomavatar
1316
itemsperpage
1417
keepalive
1518
keypair
1619
lastmod
1720
licence
21+
lightrain
22+
lightrainshowers
1823
logoheader
1924
matterbridge
2025
mimetypes
@@ -23,7 +28,9 @@ navigations
2328
nextcloud
2429
nextcloud's
2530
organisation
31+
partlycloudy
2632
productname
33+
rainshowers
2734
replyable
2835
resharing
2936
rgdnvw
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
{
22
"@@locale": "en",
33
"noEntries": "No entries",
4-
"setUserStatus": "Set status"
4+
"setUserStatus": "Set status",
5+
"weatherClearSky": "clear sky",
6+
"weatherCloudy": "cloudy",
7+
"weatherFairWeather": "fair weather",
8+
"weatherPartlyCloudy": "partly cloudy",
9+
"weatherFoggy": "foggy",
10+
"weatherRainfall": "rainfall",
11+
"weatherLightRainfall": "light rainfall",
12+
"weatherHeavyRainfall": "heavy rainfall",
13+
"weatherRainfallShowers": "rainfall showers",
14+
"weatherLightRainfallShowers": "light rainfall showers",
15+
"weatherHeavyRainfallShowers": "heavy rainfall showers",
16+
"locationSet": "Set location for weather",
17+
"address": "Address"
518
}

packages/neon/neon_dashboard/lib/l10n/localizations.dart

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,84 @@ abstract class DashboardLocalizations {
100100
/// In en, this message translates to:
101101
/// **'Set status'**
102102
String get setUserStatus;
103+
104+
/// No description provided for @weatherClearSky.
105+
///
106+
/// In en, this message translates to:
107+
/// **'clear sky'**
108+
String get weatherClearSky;
109+
110+
/// No description provided for @weatherCloudy.
111+
///
112+
/// In en, this message translates to:
113+
/// **'cloudy'**
114+
String get weatherCloudy;
115+
116+
/// No description provided for @weatherFairWeather.
117+
///
118+
/// In en, this message translates to:
119+
/// **'fair weather'**
120+
String get weatherFairWeather;
121+
122+
/// No description provided for @weatherPartlyCloudy.
123+
///
124+
/// In en, this message translates to:
125+
/// **'partly cloudy'**
126+
String get weatherPartlyCloudy;
127+
128+
/// No description provided for @weatherFoggy.
129+
///
130+
/// In en, this message translates to:
131+
/// **'foggy'**
132+
String get weatherFoggy;
133+
134+
/// No description provided for @weatherRainfall.
135+
///
136+
/// In en, this message translates to:
137+
/// **'rainfall'**
138+
String get weatherRainfall;
139+
140+
/// No description provided for @weatherLightRainfall.
141+
///
142+
/// In en, this message translates to:
143+
/// **'light rainfall'**
144+
String get weatherLightRainfall;
145+
146+
/// No description provided for @weatherHeavyRainfall.
147+
///
148+
/// In en, this message translates to:
149+
/// **'heavy rainfall'**
150+
String get weatherHeavyRainfall;
151+
152+
/// No description provided for @weatherRainfallShowers.
153+
///
154+
/// In en, this message translates to:
155+
/// **'rainfall showers'**
156+
String get weatherRainfallShowers;
157+
158+
/// No description provided for @weatherLightRainfallShowers.
159+
///
160+
/// In en, this message translates to:
161+
/// **'light rainfall showers'**
162+
String get weatherLightRainfallShowers;
163+
164+
/// No description provided for @weatherHeavyRainfallShowers.
165+
///
166+
/// In en, this message translates to:
167+
/// **'heavy rainfall showers'**
168+
String get weatherHeavyRainfallShowers;
169+
170+
/// No description provided for @locationSet.
171+
///
172+
/// In en, this message translates to:
173+
/// **'Set location for weather'**
174+
String get locationSet;
175+
176+
/// No description provided for @address.
177+
///
178+
/// In en, this message translates to:
179+
/// **'Address'**
180+
String get address;
103181
}
104182

105183
class _DashboardLocalizationsDelegate extends LocalizationsDelegate<DashboardLocalizations> {

packages/neon/neon_dashboard/lib/l10n/localizations_en.dart

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,43 @@ class DashboardLocalizationsEn extends DashboardLocalizations {
99

1010
@override
1111
String get setUserStatus => 'Set status';
12+
13+
@override
14+
String get weatherClearSky => 'clear sky';
15+
16+
@override
17+
String get weatherCloudy => 'cloudy';
18+
19+
@override
20+
String get weatherFairWeather => 'fair weather';
21+
22+
@override
23+
String get weatherPartlyCloudy => 'partly cloudy';
24+
25+
@override
26+
String get weatherFoggy => 'foggy';
27+
28+
@override
29+
String get weatherRainfall => 'rainfall';
30+
31+
@override
32+
String get weatherLightRainfall => 'light rainfall';
33+
34+
@override
35+
String get weatherHeavyRainfall => 'heavy rainfall';
36+
37+
@override
38+
String get weatherRainfallShowers => 'rainfall showers';
39+
40+
@override
41+
String get weatherLightRainfallShowers => 'light rainfall showers';
42+
43+
@override
44+
String get weatherHeavyRainfallShowers => 'heavy rainfall showers';
45+
46+
@override
47+
String get locationSet => 'Set location for weather';
48+
49+
@override
50+
String get address => 'Address';
1251
}

packages/neon/neon_dashboard/lib/src/pages/main.dart

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:intersperse/intersperse.dart';
55
import 'package:neon_dashboard/l10n/localizations.dart';
66
import 'package:neon_dashboard/src/blocs/dashboard.dart';
77
import 'package:neon_dashboard/src/widgets/dry_intrinsic_height.dart';
8+
import 'package:neon_dashboard/src/widgets/set_weather_location_dialog.dart';
89
import 'package:neon_dashboard/src/widgets/widget.dart';
910
import 'package:neon_dashboard/src/widgets/widget_button.dart';
1011
import 'package:neon_dashboard/src/widgets/widget_item.dart';
@@ -27,6 +28,7 @@ class DashboardMainPage extends StatelessWidget {
2728
final bloc = NeonProvider.of<DashboardBloc>(context);
2829
final accountsBloc = NeonProvider.of<AccountsBloc>(context);
2930
final userStatusBloc = accountsBloc.activeUserStatusBloc;
31+
final weatherStatusBloc = accountsBloc.activeWeatherStatusBloc;
3032

3133
return NeonCustomBackground(
3234
child: ResultBuilder.behaviorSubject(
@@ -36,6 +38,7 @@ class DashboardMainPage extends StatelessWidget {
3638
_buildStatuses(
3739
account: accountsBloc.activeAccount.value!,
3840
userStatusBloc: userStatusBloc,
41+
weatherStatusBloc: weatherStatusBloc,
3942
),
4043
];
4144

@@ -109,6 +112,7 @@ class DashboardMainPage extends StatelessWidget {
109112
Widget _buildStatuses({
110113
required Account account,
111114
required UserStatusBloc userStatusBloc,
115+
required WeatherStatusBloc weatherStatusBloc,
112116
}) =>
113117
Row(
114118
mainAxisAlignment: MainAxisAlignment.center,
@@ -152,6 +156,86 @@ class DashboardMainPage extends StatelessWidget {
152156
return const SizedBox.shrink();
153157
},
154158
),
159+
StreamBuilder(
160+
stream: weatherStatusBloc.isSupported,
161+
builder: (context, weatherStatusSupportedSnapshot) => ResultBuilder.behaviorSubject(
162+
subject: weatherStatusBloc.forecasts,
163+
builder: (context, forecastsResult) {
164+
if (weatherStatusSupportedSnapshot.data ?? false) {
165+
Future<void> onWeatherStatusPressed() async {
166+
final location = await showDialog<String>(
167+
context: context,
168+
builder: (context) => DashboardSetWeatherLocationDialog(
169+
currentAddress: weatherStatusBloc.location.valueOrNull?.data?.address,
170+
),
171+
);
172+
if (location != null) {
173+
weatherStatusBloc.setLocation(location);
174+
}
175+
}
176+
177+
if (forecastsResult.hasData) {
178+
final weatherCode = forecastsResult.requireData.first.data.next1Hours.summary.symbolCode;
179+
final temperature = forecastsResult.requireData.first.data.instant.details.airTemperature;
180+
final description = switch (weatherCode.replaceAll(RegExp(r'_(day|night)$'), '')) {
181+
'clearsky' => DashboardLocalizations.of(context).weatherClearSky,
182+
'cloudy' => DashboardLocalizations.of(context).weatherCloudy,
183+
'fair' => DashboardLocalizations.of(context).weatherFairWeather,
184+
'partlycloudy' => DashboardLocalizations.of(context).weatherPartlyCloudy,
185+
'fog' => DashboardLocalizations.of(context).weatherFoggy,
186+
'rain' => DashboardLocalizations.of(context).weatherRainfall,
187+
'lightrain' => DashboardLocalizations.of(context).weatherLightRainfall,
188+
'heavyrain' => DashboardLocalizations.of(context).weatherHeavyRainfall,
189+
'rainshowers' => DashboardLocalizations.of(context).weatherRainfallShowers,
190+
'lightrainshowers' => DashboardLocalizations.of(context).weatherLightRainfallShowers,
191+
'heavyrainshowers' => DashboardLocalizations.of(context).weatherHeavyRainfallShowers,
192+
_ => throw UnimplementedError('Unknown weather code: $weatherCode'),
193+
};
194+
195+
final icon = switch (weatherCode) {
196+
'clearsky_day' => 'sun',
197+
'clearsky_night' => 'moon',
198+
'cloudy' => 'cloud-cloud',
199+
'fair_day' => 'sun-small-cloud',
200+
'fair_night' => 'moon-small-cloud',
201+
'partlycloudy_day' => 'sun-cloud',
202+
'partlycloudy_night' => 'moon-cloud',
203+
'fog' => 'fog',
204+
'rain' => 'rain',
205+
'lightrain' => 'light-rain',
206+
'heavyrain' => 'heavy-rain',
207+
'rainshowers_day' => 'sun-cloud-rain',
208+
'rainshowers_night' => 'moon-cloud-rain',
209+
'lightrainshowers_day' => 'sun-cloud-light-rain',
210+
'lightrainshowers_night' => 'moon-cloud-light-rain',
211+
'heavyrainshowers_day' => 'sun-cloud-heavy-rain',
212+
'heavyrainshowers_night' => 'moon-cloud-heavy-rain',
213+
_ => throw UnimplementedError('Unknown icon: $weatherCode'),
214+
};
215+
216+
return _buildStatus(
217+
context: context,
218+
icon: NeonServerIcon(
219+
icon: icon,
220+
),
221+
label: Text('$temperature °C $description'),
222+
onPressed: onWeatherStatusPressed,
223+
);
224+
} else {
225+
return _buildStatus(
226+
context: context,
227+
icon: const NeonServerIcon(
228+
icon: 'sun-small-cloud',
229+
),
230+
label: Text(DashboardLocalizations.of(context).locationSet),
231+
onPressed: onWeatherStatusPressed,
232+
);
233+
}
234+
}
235+
return const SizedBox.shrink();
236+
},
237+
),
238+
),
155239
]
156240
.intersperse(
157241
const SizedBox(
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:neon_dashboard/l10n/localizations.dart';
3+
import 'package:neon_framework/l10n/localizations.dart';
4+
import 'package:neon_framework/widgets.dart';
5+
6+
/// Dialog for setting the weather location.
7+
class DashboardSetWeatherLocationDialog extends StatefulWidget {
8+
/// Create a new dialog for setting the weather location.
9+
const DashboardSetWeatherLocationDialog({
10+
required this.currentAddress,
11+
super.key,
12+
});
13+
14+
/// The current weather address.
15+
final String? currentAddress;
16+
17+
@override
18+
State<DashboardSetWeatherLocationDialog> createState() => _DashboardSetWeatherLocationDialogState();
19+
}
20+
21+
class _DashboardSetWeatherLocationDialogState extends State<DashboardSetWeatherLocationDialog> {
22+
final controller = TextEditingController();
23+
24+
@override
25+
void initState() {
26+
super.initState();
27+
28+
controller.text = widget.currentAddress ?? '';
29+
}
30+
31+
void submit() {
32+
Navigator.pop(context, controller.text.isNotEmpty ? controller.text : null);
33+
}
34+
35+
@override
36+
Widget build(BuildContext context) => NeonDialog(
37+
title: Text(DashboardLocalizations.of(context).locationSet),
38+
content: TextField(
39+
controller: controller,
40+
keyboardType: TextInputType.streetAddress,
41+
decoration: InputDecoration(
42+
hintText: DashboardLocalizations.of(context).address,
43+
),
44+
onSubmitted: (_) {
45+
submit();
46+
},
47+
),
48+
actions: [
49+
NeonDialogAction(
50+
onPressed: submit,
51+
child: Text(NeonLocalizations.of(context).actionDone),
52+
),
53+
],
54+
);
55+
}

packages/neon_framework/lib/blocs.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export 'package:neon_framework/src/bloc/result.dart';
44
export 'package:neon_framework/src/blocs/accounts.dart' show AccountsBloc;
55
export 'package:neon_framework/src/blocs/timer.dart';
66
export 'package:neon_framework/src/blocs/user_status.dart';
7+
export 'package:neon_framework/src/blocs/weather_status.dart';

packages/neon_framework/lib/l10n/en.arb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"actionExit": "Exit",
8787
"actionContinue": "Continue",
8888
"actionCancel": "Cancel",
89+
"actionDone": "Done",
8990
"firstLaunchGoToSettingsToEnablePushNotifications": "Go to the settings to enable push notifications",
9091
"nextPushSupported": "NextPush is supported!",
9192
"nextPushSupportedText": "NextPush is a FOSS way of receiving push notifications using the UnifiedPush protocol via a Nextcloud instance.\nYou can install NextPush from the F-Droid app store.",

packages/neon_framework/lib/l10n/localizations.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,12 @@ abstract class NeonLocalizations {
323323
/// **'Cancel'**
324324
String get actionCancel;
325325

326+
/// No description provided for @actionDone.
327+
///
328+
/// In en, this message translates to:
329+
/// **'Done'**
330+
String get actionDone;
331+
326332
/// No description provided for @firstLaunchGoToSettingsToEnablePushNotifications.
327333
///
328334
/// In en, this message translates to:

packages/neon_framework/lib/l10n/localizations_en.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ class NeonLocalizationsEn extends NeonLocalizations {
153153
@override
154154
String get actionCancel => 'Cancel';
155155

156+
@override
157+
String get actionDone => 'Done';
158+
156159
@override
157160
String get firstLaunchGoToSettingsToEnablePushNotifications => 'Go to the settings to enable push notifications';
158161

0 commit comments

Comments
 (0)