Skip to content

Commit d02c572

Browse files
authored
feat: improve future loading and podcast update notifications (#1362)
* feat: improve future loading and podcast update notifications * test * fix deps and remove smtc * fix: clean up Rust toolchain before building Linux * fix: update androidNotificationChannelId to include Windows support * fix: clean and install Rust toolchain in CI workflow
1 parent e5cad8d commit d02c572

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+441
-315
lines changed

.github/workflows/ci.yaml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ on:
44
pull_request:
55
branches: [main]
66

7-
87
jobs:
98
analyze:
109
runs-on: ubuntu-latest
@@ -34,7 +33,14 @@ jobs:
3433
steps:
3534
- uses: actions/checkout@v5
3635
- uses: kuhnroyal/flutter-fvm-config-action/setup@v3
36+
- name: Clean Rust toolchain cache
37+
run: rm -rf ~/.rustup ~/.cargo
38+
39+
- name: Install Rust toolchain (skip docs)
40+
run: |
41+
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable --profile minimal
42+
export PATH="$HOME/.cargo/bin:$PATH"
3743
- run: sudo apt update
3844
- run: sudo apt install -y clang cmake curl libgtk-3-dev ninja-build pkg-config unzip libunwind-dev libmpv-dev libnotify-dev
3945
- run: flutter pub get
40-
- run: flutter build linux -v
46+
- run: flutter build linux -v

lib/common/view/confirm.dart

Lines changed: 150 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import 'package:flutter/material.dart';
22
import 'package:yaru/yaru.dart';
33

4+
import '../../extensions/build_context_x.dart';
45
import '../../l10n/l10n.dart';
6+
import 'progress.dart';
57
import 'theme.dart';
68
import 'ui_constants.dart';
79

8-
class ConfirmationDialog extends StatelessWidget {
10+
class ConfirmationDialog<T> extends StatefulWidget {
911
const ConfirmationDialog({
1012
super.key,
1113
this.onConfirm,
@@ -20,67 +22,180 @@ class ConfirmationDialog extends StatelessWidget {
2022
this.cancelLabel,
2123
this.confirmEnabled = true,
2224
this.contentPadding,
25+
this.titlePadding,
2326
});
24-
25-
final dynamic Function()? onConfirm;
26-
final bool confirmEnabled;
27-
final dynamic Function()? onCancel;
27+
final Future<T> Function()? onConfirm;
28+
final Future<T> Function()? onCancel;
2829
final List<Widget>? additionalActions;
2930
final Widget? title;
3031
final Widget? content;
3132
final bool showCancel;
3233
final bool showCloseIcon;
3334
final bool scrollable;
34-
final String? confirmLabel, cancelLabel;
35+
final String? confirmLabel;
36+
final String? cancelLabel;
3537
final EdgeInsetsGeometry? contentPadding;
38+
final bool confirmEnabled;
39+
final EdgeInsetsGeometry? titlePadding;
40+
41+
static Future<T?> show<T>({
42+
required BuildContext context,
43+
required Future<T> Function() onConfirm,
44+
Widget? title,
45+
Widget? content,
46+
String? confirmLabel,
47+
String? cancelLabel,
48+
bool barrierDismissible = false,
49+
bool confirmEnabled = true,
50+
bool scrollable = false,
51+
List<Widget>? additionalActions,
52+
EdgeInsetsGeometry? contentPadding,
53+
Future<T> Function()? onCancel,
54+
bool showCancel = true,
55+
}) => showDialog(
56+
context: context,
57+
barrierDismissible: barrierDismissible,
58+
builder: (context) => ConfirmationDialog<T>(
59+
title: title,
60+
content: content,
61+
onConfirm: onConfirm,
62+
confirmLabel: confirmLabel,
63+
cancelLabel: cancelLabel,
64+
showCloseIcon: barrierDismissible,
65+
confirmEnabled: confirmEnabled,
66+
scrollable: scrollable,
67+
additionalActions: additionalActions,
68+
contentPadding: contentPadding,
69+
onCancel: onCancel,
70+
showCancel: showCancel,
71+
),
72+
);
73+
74+
@override
75+
State<ConfirmationDialog<T>> createState() => _ConfirmationDialogState();
76+
}
77+
78+
class _ConfirmationDialogState<T> extends State<ConfirmationDialog<T>> {
79+
bool _loading = false;
80+
String? _error;
3681

3782
@override
3883
Widget build(BuildContext context) {
3984
final l10n = context.l10n;
4085
return AlertDialog(
86+
titlePadding: widget.titlePadding ?? EdgeInsets.zero,
4187
title: YaruDialogTitleBar(
42-
title: title,
4388
backgroundColor: Colors.transparent,
89+
title: widget.title,
90+
isClosable: widget.showCloseIcon,
4491
border: BorderSide.none,
45-
isClosable: showCloseIcon,
4692
),
47-
scrollable: scrollable,
48-
titlePadding: EdgeInsets.zero,
49-
content: content,
50-
contentPadding: contentPadding,
93+
scrollable: widget.scrollable,
94+
content: _error != null
95+
? Padding(
96+
padding: const EdgeInsets.all(8),
97+
child: Text(
98+
_error!,
99+
style: context.textTheme.bodyLarge!.copyWith(
100+
color: context.colorScheme.error,
101+
),
102+
textAlign: TextAlign.center,
103+
),
104+
)
105+
: _loading
106+
? const SizedBox.square(
107+
dimension: 60,
108+
child: Center(
109+
child: SizedBox.square(dimension: 24, child: Progress()),
110+
),
111+
)
112+
: widget.content,
113+
contentPadding: widget.contentPadding,
51114
actionsAlignment: MainAxisAlignment.start,
52115
actionsOverflowAlignment: OverflowBarAlignment.center,
53116
actionsPadding: const EdgeInsets.all(kMediumSpace),
54117
actions: [
55118
Row(
56119
children: space(
57120
expandAll: true,
58-
widthGap: kMediumSpace,
59-
children: [
60-
...?additionalActions,
61-
if (showCancel)
62-
OutlinedButton(
63-
onPressed: () {
64-
onCancel?.call();
65-
if (context.mounted && Navigator.of(context).canPop()) {
66-
Navigator.of(context).pop();
67-
}
68-
},
69-
child: Text(cancelLabel ?? l10n.cancel),
70-
),
71-
ElevatedButton(
72-
onPressed: confirmEnabled
73-
? () {
74-
onConfirm?.call();
75-
121+
widthGap: 16,
122+
children: _error != null
123+
? [
124+
OutlinedButton(
125+
onPressed: () {
76126
if (context.mounted && Navigator.of(context).canPop()) {
77127
Navigator.of(context).pop();
78128
}
79-
}
80-
: null,
81-
child: Text(confirmLabel ?? l10n.ok),
82-
),
83-
],
129+
},
130+
child: Text(l10n.ok),
131+
),
132+
]
133+
: [
134+
if (widget.showCancel)
135+
OutlinedButton(
136+
onPressed: _loading
137+
? null
138+
: () {
139+
if (widget.onCancel is Future<T> Function()) {
140+
setState(() => _loading = true);
141+
widget.onCancel!()
142+
.then((_) {
143+
if (context.mounted &&
144+
Navigator.of(context).canPop()) {
145+
Navigator.of(context).pop();
146+
}
147+
})
148+
.catchError((error) {
149+
setState(() {
150+
_loading = false;
151+
_error = error.toString();
152+
});
153+
});
154+
} else {
155+
widget.onCancel?.call();
156+
if (context.mounted &&
157+
Navigator.of(context).canPop()) {
158+
Navigator.of(context).pop();
159+
}
160+
}
161+
},
162+
child: Text(widget.cancelLabel ?? l10n.cancel),
163+
),
164+
...?widget.additionalActions,
165+
ElevatedButton(
166+
onPressed: _loading
167+
? null
168+
: widget.confirmEnabled
169+
? () {
170+
if (widget.onConfirm != null) {
171+
if (widget.onConfirm is Future<T> Function()) {
172+
setState(() => _loading = true);
173+
widget.onConfirm!()
174+
.then((_) {
175+
if (context.mounted &&
176+
Navigator.of(context).canPop()) {
177+
Navigator.of(context).pop();
178+
}
179+
})
180+
.catchError((error) {
181+
setState(() {
182+
_loading = false;
183+
_error = error.toString();
184+
});
185+
});
186+
} else {
187+
widget.onConfirm!();
188+
setState(() => _loading = false);
189+
}
190+
} else if (context.mounted &&
191+
Navigator.of(context).canPop()) {
192+
Navigator.of(context).pop();
193+
}
194+
}
195+
: null,
196+
child: Text(widget.confirmLabel ?? l10n.ok),
197+
),
198+
],
84199
),
85200
),
86201
],

lib/common/view/data_safe_mode_dialog.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ class DataSafeModeDialog extends StatelessWidget with WatchItMixin {
3333
? l10n.isBackInWifiDialogBody
3434
: l10n.isMaybeLowBandwidthDialogBody,
3535
),
36-
onConfirm: () => di<RadioModel>().setDataSafeMode(!isEthernetOrWifi),
36+
onConfirm: () async =>
37+
di<RadioModel>().setDataSafeMode(!isEthernetOrWifi),
3738
cancelLabel: l10n.stopToNotifyAboutDataSafeMode,
38-
onCancel: () {
39+
onCancel: () async {
3940
di<SettingsModel>().setNotifyDataSafeMode(false);
4041
cancel();
4142
},

lib/l10n/app_en.arb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,5 +745,13 @@
745745
"exportingPlaylistsPleaseWait": "Exporting your playlists, please wait ...",
746746
"author": "Author",
747747
"rating": "Rating",
748-
"keywords": "Keywords"
748+
"keywords": "Keywords",
749+
"newEpisodesAvailableFor": "New episodes available for {length} podcasts",
750+
"@newEpisodesAvailableFor": {
751+
"placeholders": {
752+
"length": {
753+
"type": "int"
754+
}
755+
}
756+
}
749757
}

lib/l10n/app_localizations.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4314,6 +4314,12 @@ abstract class AppLocalizations {
43144314
/// In en, this message translates to:
43154315
/// **'Keywords'**
43164316
String get keywords;
4317+
4318+
/// No description provided for @newEpisodesAvailableFor.
4319+
///
4320+
/// In en, this message translates to:
4321+
/// **'New episodes available for {length} podcasts'**
4322+
String newEpisodesAvailableFor(int length);
43174323
}
43184324

43194325
class _AppLocalizationsDelegate

lib/l10n/app_localizations_cs.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,4 +2193,9 @@ class AppLocalizationsCs extends AppLocalizations {
21932193

21942194
@override
21952195
String get keywords => 'Keywords';
2196+
2197+
@override
2198+
String newEpisodesAvailableFor(int length) {
2199+
return 'New episodes available for $length podcasts';
2200+
}
21962201
}

lib/l10n/app_localizations_da.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2195,4 +2195,9 @@ class AppLocalizationsDa extends AppLocalizations {
21952195

21962196
@override
21972197
String get keywords => 'Keywords';
2198+
2199+
@override
2200+
String newEpisodesAvailableFor(int length) {
2201+
return 'New episodes available for $length podcasts';
2202+
}
21982203
}

lib/l10n/app_localizations_de.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2203,4 +2203,9 @@ class AppLocalizationsDe extends AppLocalizations {
22032203

22042204
@override
22052205
String get keywords => 'Keywords';
2206+
2207+
@override
2208+
String newEpisodesAvailableFor(int length) {
2209+
return 'New episodes available for $length podcasts';
2210+
}
22062211
}

lib/l10n/app_localizations_en.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,4 +2191,9 @@ class AppLocalizationsEn extends AppLocalizations {
21912191

21922192
@override
21932193
String get keywords => 'Keywords';
2194+
2195+
@override
2196+
String newEpisodesAvailableFor(int length) {
2197+
return 'New episodes available for $length podcasts';
2198+
}
21942199
}

lib/l10n/app_localizations_es.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2203,4 +2203,9 @@ class AppLocalizationsEs extends AppLocalizations {
22032203

22042204
@override
22052205
String get keywords => 'Keywords';
2206+
2207+
@override
2208+
String newEpisodesAvailableFor(int length) {
2209+
return 'New episodes available for $length podcasts';
2210+
}
22062211
}

0 commit comments

Comments
 (0)