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:
@@ -42,19 +42,40 @@ class MiniPlayer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _Leiste extends StatelessWidget {
|
||||
class _Leiste extends StatefulWidget {
|
||||
const _Leiste({required this.handler, required this.item});
|
||||
|
||||
final MeloAudioHandler handler;
|
||||
final MediaItem item;
|
||||
|
||||
@override
|
||||
State<_Leiste> createState() => _LeisteState();
|
||||
}
|
||||
|
||||
class _LeisteState extends State<_Leiste> {
|
||||
/// Wie weit der Finger die Leiste gerade zieht — nur zur Anzeige, die
|
||||
/// eigentliche Entscheidung fällt erst bei [onVerticalDragEnd]/
|
||||
/// [onHorizontalDragEnd]. `dy` bleibt <= 0: nach unten ist unbelegt, ein
|
||||
/// Mitführen dorthin würde Wirkung vortäuschen, die es nicht gibt.
|
||||
Offset _versatz = Offset.zero;
|
||||
bool _ziehtGerade = false;
|
||||
|
||||
void _oeffne(BuildContext context) => Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const NowPlayingScreen()),
|
||||
);
|
||||
|
||||
void _zurueckschnappen() {
|
||||
setState(() {
|
||||
_ziehtGerade = false;
|
||||
_versatz = Offset.zero;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final texte = Theme.of(context).textTheme;
|
||||
final handler = widget.handler;
|
||||
final item = widget.item;
|
||||
return DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
color: MeloTheme.surface,
|
||||
@@ -68,14 +89,30 @@ class _Leiste extends StatelessWidget {
|
||||
children: [
|
||||
GestureDetector(
|
||||
// Wischen wie in jeder anderen Musik-App: nach oben öffnet die
|
||||
// Vollbild-Wiedergabe, seitwärts wechselt den Titel.
|
||||
// Vollbild-Wiedergabe, seitwärts wechselt den Titel. Die Leiste
|
||||
// folgt dabei sichtbar dem Finger — sonst blieb ein
|
||||
// bedächtiges, aber weites Wischen (unter der Schwung-Schwelle)
|
||||
// ganz ohne Reaktion.
|
||||
onVerticalDragUpdate: (details) => setState(() {
|
||||
_ziehtGerade = true;
|
||||
final dy = _versatz.dy + details.delta.dy;
|
||||
_versatz = Offset(_versatz.dx, dy > 0 ? 0 : dy);
|
||||
}),
|
||||
onVerticalDragEnd: (details) {
|
||||
if (oeffnetVollbild(details.velocity.pixelsPerSecond.dy)) {
|
||||
_oeffne(context);
|
||||
}
|
||||
final oeffnet = oeffnetVollbildMitStrecke(
|
||||
_versatz.dy, details.velocity.pixelsPerSecond.dy);
|
||||
_zurueckschnappen();
|
||||
if (oeffnet) _oeffne(context);
|
||||
},
|
||||
onHorizontalDragUpdate: (details) => setState(() {
|
||||
_ziehtGerade = true;
|
||||
_versatz = Offset(_versatz.dx + details.delta.dx, _versatz.dy);
|
||||
}),
|
||||
onHorizontalDragEnd: (details) {
|
||||
switch (titelWechsel(details.velocity.pixelsPerSecond.dx)) {
|
||||
final wechsel = titelWechselMitStrecke(
|
||||
_versatz.dx, details.velocity.pixelsPerSecond.dx);
|
||||
_zurueckschnappen();
|
||||
switch (wechsel) {
|
||||
case Wechsel.naechster:
|
||||
handler.skipToNext();
|
||||
case Wechsel.vorheriger:
|
||||
@@ -86,7 +123,15 @@ class _Leiste extends StatelessWidget {
|
||||
},
|
||||
child: InkWell(
|
||||
onTap: () => _oeffne(context),
|
||||
child: SizedBox(
|
||||
child: AnimatedContainer(
|
||||
// Während des Ziehens sofort folgen (keine Verzögerung);
|
||||
// beim Loslassen ohne Wirkung sichtbar zurückschnappen.
|
||||
duration: _ziehtGerade
|
||||
? Duration.zero
|
||||
: MeloMotion.ruhig(context, MeloMotion.normal),
|
||||
curve: MeloMotion.curve,
|
||||
transform:
|
||||
Matrix4.translationValues(_versatz.dx, _versatz.dy, 0),
|
||||
height: MiniPlayer.hoehe,
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -23,3 +23,28 @@ Wechsel? titelWechsel(double geschwindigkeit) {
|
||||
/// Wiedergabe aus Versehen wegschiebt.
|
||||
bool oeffnetVollbild(double geschwindigkeit) =>
|
||||
geschwindigkeit < -wischSchwelle;
|
||||
|
||||
/// Ab welcher zurückgelegten Strecke (in Pixeln) eine langsame, aber
|
||||
/// eindeutige Wischbewegung auch ohne Schwung zählt.
|
||||
///
|
||||
/// Ohne diese Schwelle bekam ein bedächtiges, aber weites Wischen (unter
|
||||
/// [wischSchwelle] Pixel pro Sekunde) überhaupt keine Reaktion — für den
|
||||
/// Nutzer sah das aus wie ein nicht reagierender Bildschirm statt "zu
|
||||
/// langsam gewischt".
|
||||
const wischStreckeSchwelle = 80.0;
|
||||
|
||||
/// Wie [titelWechsel], aber eine ausreichend weite Bewegung zählt zusätzlich
|
||||
/// auch bei geringem Schwung.
|
||||
Wechsel? titelWechselMitStrecke(double strecke, double geschwindigkeit) {
|
||||
final ausSchwung = titelWechsel(geschwindigkeit);
|
||||
if (ausSchwung != null) return ausSchwung;
|
||||
if (strecke.abs() <= wischStreckeSchwelle) return null;
|
||||
return strecke < 0 ? Wechsel.naechster : Wechsel.vorheriger;
|
||||
}
|
||||
|
||||
/// Wie [oeffnetVollbild], aber eine ausreichend weite Bewegung nach oben
|
||||
/// zählt zusätzlich auch bei geringem Schwung.
|
||||
bool oeffnetVollbildMitStrecke(double strecke, double geschwindigkeit) {
|
||||
if (oeffnetVollbild(geschwindigkeit)) return true;
|
||||
return strecke < -wischStreckeSchwelle;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user