Refine iOS navigation, media controls, and background sync
Build APK / build (push) Successful in 5m50s

This commit is contained in:
Ayushya Amitabh
2026-10-06 23:18:50 -04:00
parent c5b46fd493
commit ce2273c9a6
39 changed files with 2309 additions and 647 deletions
+2 -8
View File
@@ -4,14 +4,8 @@ import '../../theme/design_tokens.dart';
import '../frosted_glass_container.dart';
import 'viewer_icon_button.dart';
/// The bottom panel overlaid on the media viewer: an optional transport row
/// ([transportControls], video only) above the action row
/// (share/favorite/open/download/delete/details) - one continuous flush,
/// full-width `FrostedGlassContainer` panel, not two separate floating
/// pills. Kept on that blurred chrome as a deliberate exception to the
/// design system's flat product UI (see `file_viewer_screen.dart`'s
/// build() comment for why), unlike the now-flat bottom nav bar
/// (`bottom_nav_bar.dart`) this screen sits above.
/// A full-width media panel containing transport, actions and details.
/// Its solid/frosted surface follows the shared appearance preference.
class MediaActionBar extends StatelessWidget {
final bool isFavorite;
final bool isBusy;
+63 -6
View File
@@ -1,6 +1,7 @@
import 'dart:io';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:provider/provider.dart';
import 'package:video_player/video_player.dart';
@@ -198,12 +199,14 @@ class VideoTransportControls extends StatelessWidget {
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
child: WavySeekBar(
controller: controller,
playedColor: fg,
trackColor: fg.withValues(alpha: 0.3),
style: progressBarStyle,
),
child: Theme.of(context).platform == TargetPlatform.iOS
? IosSeekBar(controller: controller, color: fg)
: WavySeekBar(
controller: controller,
playedColor: fg,
trackColor: fg.withValues(alpha: 0.3),
style: progressBarStyle,
),
),
],
),
@@ -213,6 +216,60 @@ class VideoTransportControls extends StatelessWidget {
}
}
class IosSeekBar extends StatefulWidget {
final VideoPlayerController controller;
final Color color;
const IosSeekBar({super.key, required this.controller, required this.color});
@override
State<IosSeekBar> createState() => _IosSeekBarState();
}
class _IosSeekBarState extends State<IosSeekBar> {
double? _dragRatio;
bool _resumeAfterScrubbing = false;
@override
Widget build(BuildContext context) {
final value = widget.controller.value;
final durationMs = value.duration.inMilliseconds;
final ratio =
(_dragRatio ??
(durationMs > 0
? value.position.inMilliseconds / durationMs
: 0.0))
.clamp(0.0, 1.0);
return SizedBox(
width: double.infinity,
child: CupertinoSlider(
value: ratio,
activeColor: widget.color,
thumbColor: CupertinoColors.white,
onChangeStart: durationMs <= 0
? null
: (_) {
_resumeAfterScrubbing = value.isPlaying;
if (_resumeAfterScrubbing) widget.controller.pause();
},
onChanged: durationMs <= 0
? null
: (ratio) {
setState(() => _dragRatio = ratio);
widget.controller.seekTo(value.duration * ratio);
},
onChangeEnd: durationMs <= 0
? null
: (ratio) async {
await widget.controller.seekTo(value.duration * ratio);
if (!mounted) return;
setState(() => _dragRatio = null);
if (_resumeAfterScrubbing) widget.controller.play();
},
),
);
}
}
/// A Material You "expressive" wavy seek bar: the played portion of the
/// track animates as a travelling sine wave while playing and settles flat
/// when paused, matching the Android 16+ media player style. Drag or tap