UX-Simulation, Teil 3: Abbrechen, Badge, Versionsnummer, Wisch-Strecke
Die vier zuletzt zurückgestellten Punkte aus der UX-Simulation: - Abbrechen-Knopf für hängende YouTube-Downloads (Future.any gegen ein Abbruch-Signal, Anfrage läuft im Hintergrund aus statt die App zu blockieren) - Punkt am inaktiven Download-Unterreiter, wenn dort etwas im Hintergrund läuft (SubTabs bekommt einen optionalen badges-Parameter) - Versionsnummer unter "Über Melo" (neue Abhängigkeit: package_info_plus) - Mini-Player führt beim Wischen jetzt sichtbar mit und wertet zusätzlich zum Schwung auch die zurückgelegte Strecke aus — ein langsames, aber weites Wischen wurde vorher komplett ignoriert Dabei drei bestehende Widget-Tests korrigiert, die den Download-Tab ohne Provider<DownloadService> aufbauten (hauptmenue_test.dart, home_shell_test.dart, online_screen_test.dart) — lief bisher nur, weil niemand danach fragte; das neue Badge tut das jetzt app-weit. 550 Tests grün (vorher 539), flutter analyze ohne Befund.
This commit is contained in:
@@ -11,6 +11,12 @@ import 'baka_auth.dart';
|
||||
String sichererDateiname(String titel) =>
|
||||
titel.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim();
|
||||
|
||||
/// Signalisiert, dass [YtDownloadService.abbrechen] während einer laufenden
|
||||
/// Anfrage aufgerufen wurde.
|
||||
class _Abgebrochen implements Exception {
|
||||
const _Abgebrochen();
|
||||
}
|
||||
|
||||
/// Ergebnis eines geglückten Downloads.
|
||||
class YtErgebnis {
|
||||
const YtErgebnis({
|
||||
@@ -41,6 +47,11 @@ class YtDownloadService extends ChangeNotifier {
|
||||
String? _status;
|
||||
String? _fehler;
|
||||
|
||||
/// Ist gesetzt, während [herunterladen] läuft. [abbrechen] löst es aus —
|
||||
/// die Netzwerk-Anfrage selbst läuft im Hintergrund weiter, bis der Socket
|
||||
/// sie beendet, aber die App wartet nicht länger darauf.
|
||||
Completer<void>? _abbruch;
|
||||
|
||||
bool get laeuft => _laeuft;
|
||||
|
||||
/// Was gerade passiert — für die Anzeige während des Downloads.
|
||||
@@ -59,6 +70,20 @@ class YtDownloadService extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Bricht den laufenden Download ab, falls einer läuft.
|
||||
void abbrechen() {
|
||||
_abbruch?.complete();
|
||||
}
|
||||
|
||||
/// Wartet auf [aufgabe], bricht aber sofort mit [_Abgebrochen] ab, sobald
|
||||
/// [abbrechen] aufgerufen wird — ohne auf das Ende von [aufgabe] zu warten.
|
||||
Future<T> _abbrechbar<T>(Future<T> aufgabe, Completer<void> abbruch) {
|
||||
return Future.any<T>([
|
||||
aufgabe,
|
||||
abbruch.future.then((_) => throw const _Abgebrochen()),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Lädt [url] herunter und legt die MP3 in [zielOrdner] ab.
|
||||
/// Mit [cookies] nutzt der Proxy seine YouTube-Anmeldung — nötig für
|
||||
/// altersbeschränkte Videos.
|
||||
@@ -67,6 +92,7 @@ class YtDownloadService extends ChangeNotifier {
|
||||
{required String zielOrdner, bool cookies = true}) async {
|
||||
_fehler = null;
|
||||
_laeuft = true;
|
||||
final abbruch = _abbruch = Completer<void>();
|
||||
notifyListeners();
|
||||
|
||||
if (!auth.istAngemeldet) {
|
||||
@@ -82,13 +108,22 @@ class YtDownloadService extends ChangeNotifier {
|
||||
_melde('Proxy wird gefragt …');
|
||||
final http.Response antwort;
|
||||
try {
|
||||
antwort = await _client
|
||||
.post(
|
||||
Uri.parse('$proxyUrl/api/yt-dl'),
|
||||
headers: {...auth.authHeader, 'Content-Type': 'application/json'},
|
||||
body: jsonEncode({'url': url, 'cookies': cookies}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 180));
|
||||
antwort = await _abbrechbar(
|
||||
_client
|
||||
.post(
|
||||
Uri.parse('$proxyUrl/api/yt-dl'),
|
||||
headers: {
|
||||
...auth.authHeader,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: jsonEncode({'url': url, 'cookies': cookies}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 180)),
|
||||
abbruch,
|
||||
);
|
||||
} on _Abgebrochen {
|
||||
_scheitere('Abgebrochen');
|
||||
return null;
|
||||
} on TimeoutException {
|
||||
// Anders als bei einem Verbindungsfehler ist der Proxy hier vermutlich
|
||||
// erreichbar, arbeitet nur noch — "nicht erreichbar" würde in die
|
||||
@@ -124,14 +159,23 @@ class YtDownloadService extends ChangeNotifier {
|
||||
final datei = File('${ordner.path}/${sichererDateiname(titel)}.mp3');
|
||||
|
||||
try {
|
||||
final mp3 = await _client
|
||||
.get(Uri.parse('$proxyUrl$mp3Url'), headers: auth.authHeader)
|
||||
.timeout(const Duration(seconds: 180));
|
||||
final mp3 = await _abbrechbar(
|
||||
_client
|
||||
.get(Uri.parse('$proxyUrl$mp3Url'), headers: auth.authHeader)
|
||||
.timeout(const Duration(seconds: 180)),
|
||||
abbruch,
|
||||
);
|
||||
if (mp3.statusCode != 200) {
|
||||
_scheitere('Herunterladen fehlgeschlagen (${mp3.statusCode})');
|
||||
return null;
|
||||
}
|
||||
await datei.writeAsBytes(mp3.bodyBytes);
|
||||
} on _Abgebrochen {
|
||||
if (await datei.exists()) {
|
||||
await datei.delete();
|
||||
}
|
||||
_scheitere('Abgebrochen');
|
||||
return null;
|
||||
} on TimeoutException {
|
||||
if (await datei.exists()) {
|
||||
await datei.delete();
|
||||
|
||||
Reference in New Issue
Block a user