[mob][photos] Move SeekBar and PlayPauseButton widgets to separate files

This commit is contained in:
ashilkn
2024-08-07 15:07:52 +05:30
parent c572fc171c
commit 7795625708
3 changed files with 217 additions and 210 deletions

View File

@@ -0,0 +1,63 @@
import "package:flutter/material.dart";
import "package:native_video_player/native_video_player.dart";
class PlayPauseButton extends StatefulWidget {
final NativeVideoPlayerController? controller;
const PlayPauseButton(this.controller, {super.key});
@override
State<PlayPauseButton> createState() => _PlayPauseButtonState();
}
class _PlayPauseButtonState extends State<PlayPauseButton> {
bool _isPlaying = true;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (_playbackStatus == PlaybackStatus.playing) {
widget.controller?.pause();
setState(() {
_isPlaying = false;
});
} else {
widget.controller?.play();
setState(() {
_isPlaying = true;
});
}
},
child: Container(
width: 54,
height: 54,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
shape: BoxShape.circle,
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
switchInCurve: Curves.easeInOutQuart,
switchOutCurve: Curves.easeInOutQuart,
child: _isPlaying
? const Icon(
Icons.pause,
size: 32,
key: ValueKey("pause"),
)
: const Icon(
Icons.play_arrow,
size: 36,
key: ValueKey("play"),
),
),
),
);
}
PlaybackStatus? get _playbackStatus =>
widget.controller?.playbackInfo?.status;
}

View File

@@ -0,0 +1,151 @@
import "dart:async";
import "package:flutter/material.dart";
import "package:native_video_player/native_video_player.dart";
class SeekBar extends StatefulWidget {
final NativeVideoPlayerController controller;
final int? duration;
const SeekBar(this.controller, this.duration, {super.key});
@override
State<SeekBar> createState() => _SeekBarState();
}
class _SeekBarState extends State<SeekBar> with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
double _prevPositionFraction = 0.0;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
value: 0,
);
widget.controller.onPlaybackStatusChanged.addListener(
_onPlaybackStatusChanged,
);
widget.controller.onPlaybackPositionChanged.addListener(
_onPlaybackPositionChanged,
);
_startMovingSeekbar();
}
@override
void dispose() {
_animationController.dispose();
widget.controller.onPlaybackStatusChanged.removeListener(
_onPlaybackStatusChanged,
);
widget.controller.onPlaybackPositionChanged.removeListener(
_onPlaybackPositionChanged,
);
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (_, __) {
return SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 2.0,
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0),
overlayShape: const RoundSliderOverlayShape(overlayRadius: 14.0),
activeTrackColor: Colors.red,
inactiveTrackColor: Colors.grey,
thumbColor: Colors.red,
overlayColor: Colors.red.withOpacity(0.4),
),
child: Slider(
min: 0.0,
max: 1.0,
value: _animationController.value,
onChanged: (value) {},
divisions: 4500,
onChangeEnd: (value) {
widget.controller.seekTo((value * widget.duration!).round());
_animationController.animateTo(
value,
duration: const Duration(microseconds: 0),
);
},
allowedInteraction: SliderInteraction.tapAndSlide,
),
);
},
);
}
void _startMovingSeekbar() {
//Video starts playing after a slight delay. This delay is to ensure that
//the seek bar animation starts after the video starts playing.
Future.delayed(const Duration(milliseconds: 700), () {
if (widget.duration != null) {
unawaited(
_animationController.animateTo(
(1 / widget.duration!),
duration: const Duration(seconds: 1),
),
);
} else {
unawaited(
_animationController.animateTo(
0,
duration: const Duration(seconds: 1),
),
);
}
});
}
void _onPlaybackStatusChanged() {
if (widget.controller.playbackInfo?.status == PlaybackStatus.paused) {
_animationController.stop();
}
}
void _onPlaybackPositionChanged() async {
if (widget.controller.playbackInfo?.status == PlaybackStatus.paused) {
return;
}
final target = widget.controller.playbackInfo?.positionFraction ?? 0;
//To immediately set the position to 0 when the ends when playing in loop
if (_prevPositionFraction == 1.0 && target == 0.0) {
unawaited(
_animationController.animateTo(0, duration: const Duration(seconds: 0)),
);
}
//There is a slight delay (around 350 ms) for the event being listened to
//by this listener on the next target (target that comes after 0). Adding
//this buffer to keep the seek bar animation smooth.
if (target == 0) {
await Future.delayed(const Duration(milliseconds: 450));
}
if (widget.duration != null) {
unawaited(
_animationController.animateTo(
target + (1 / widget.duration!),
duration: const Duration(seconds: 1),
),
);
} else {
unawaited(
_animationController.animateTo(
target,
duration: const Duration(seconds: 1),
),
);
}
_prevPositionFraction = target;
}
}

View File

@@ -14,6 +14,8 @@ import "package:photos/models/file/extensions/file_props.dart";
import "package:photos/models/file/file.dart";
import "package:photos/services/files_service.dart";
import "package:photos/ui/actions/file/file_actions.dart";
import "package:photos/ui/viewer/file/native_video_player_controls/play_pause_button.dart";
import "package:photos/ui/viewer/file/native_video_player_controls/seek_bar.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/exif_util.dart";
@@ -177,7 +179,7 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
child: ValueListenableBuilder(
builder: (BuildContext context, bool value, _) {
return value
? _SeekBar(_controller!, duration)
? SeekBar(_controller!, duration)
: const SizedBox();
},
valueListenable: _isControllerInitialized,
@@ -346,212 +348,3 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
}
}
}
class PlayPauseButton extends StatefulWidget {
final NativeVideoPlayerController? controller;
const PlayPauseButton(this.controller, {super.key});
@override
State<PlayPauseButton> createState() => _PlayPauseButtonState();
}
class _PlayPauseButtonState extends State<PlayPauseButton> {
bool _isPlaying = true;
@override
Widget build(BuildContext context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (_playbackStatus == PlaybackStatus.playing) {
widget.controller?.pause();
setState(() {
_isPlaying = false;
});
} else {
widget.controller?.play();
setState(() {
_isPlaying = true;
});
}
},
child: Container(
width: 54,
height: 54,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.3),
shape: BoxShape.circle,
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
switchInCurve: Curves.easeInOutQuart,
switchOutCurve: Curves.easeInOutQuart,
child: _isPlaying
? const Icon(
Icons.pause,
size: 32,
key: ValueKey("pause"),
)
: const Icon(
Icons.play_arrow,
size: 36,
key: ValueKey("play"),
),
),
),
);
}
PlaybackStatus? get _playbackStatus =>
widget.controller?.playbackInfo?.status;
}
class _SeekBar extends StatefulWidget {
final NativeVideoPlayerController controller;
final int? duration;
const _SeekBar(this.controller, this.duration);
@override
State<_SeekBar> createState() => _SeekBarState();
}
class _SeekBarState extends State<_SeekBar>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
double _prevPositionFraction = 0.0;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
value: 0,
);
widget.controller.onPlaybackStatusChanged.addListener(
_onPlaybackStatusChanged,
);
widget.controller.onPlaybackPositionChanged.addListener(
_onPlaybackPositionChanged,
);
_startMovingSeekbar();
}
@override
void dispose() {
_animationController.dispose();
widget.controller.onPlaybackStatusChanged.removeListener(
_onPlaybackStatusChanged,
);
widget.controller.onPlaybackPositionChanged.removeListener(
_onPlaybackPositionChanged,
);
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animationController,
builder: (_, __) {
return SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 2.0,
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8.0),
overlayShape: const RoundSliderOverlayShape(overlayRadius: 14.0),
activeTrackColor: Colors.red,
inactiveTrackColor: Colors.grey,
thumbColor: Colors.red,
overlayColor: Colors.red.withOpacity(0.4),
),
child: Slider(
min: 0.0,
max: 1.0,
value: _animationController.value,
onChanged: (value) {},
divisions: 4500,
onChangeEnd: (value) {
widget.controller.seekTo((value * widget.duration!).round());
_animationController.animateTo(
value,
duration: const Duration(microseconds: 0),
);
},
allowedInteraction: SliderInteraction.tapAndSlide,
),
);
},
);
}
void _startMovingSeekbar() {
//Video starts playing after a slight delay. This delay is to ensure that
//the seek bar animation starts after the video starts playing.
Future.delayed(const Duration(milliseconds: 700), () {
if (widget.duration != null) {
unawaited(
_animationController.animateTo(
(1 / widget.duration!),
duration: const Duration(seconds: 1),
),
);
} else {
unawaited(
_animationController.animateTo(
0,
duration: const Duration(seconds: 1),
),
);
}
});
}
void _onPlaybackStatusChanged() {
if (widget.controller.playbackInfo?.status == PlaybackStatus.paused) {
_animationController.stop();
}
}
void _onPlaybackPositionChanged() async {
if (widget.controller.playbackInfo?.status == PlaybackStatus.paused) {
return;
}
final target = widget.controller.playbackInfo?.positionFraction ?? 0;
//To immediately set the position to 0 when the ends when playing in loop
if (_prevPositionFraction == 1.0 && target == 0.0) {
unawaited(
_animationController.animateTo(0, duration: const Duration(seconds: 0)),
);
}
//There is a slight delay (around 350 ms) for the event being listened to
//by this listener on the next target (target that comes after 0). Adding
//this buffer to keep the seek bar animation smooth.
if (target == 0) {
await Future.delayed(const Duration(milliseconds: 450));
}
if (widget.duration != null) {
unawaited(
_animationController.animateTo(
target + (1 / widget.duration!),
duration: const Duration(seconds: 1),
),
);
} else {
unawaited(
_animationController.animateTo(
target,
duration: const Duration(seconds: 1),
),
);
}
_prevPositionFraction = target;
}
}