Michael Goderbauer 5491c8c146
Auto-format Framework (#160545)
This auto-formats all *.dart files in the repository outside of the
`engine` subdirectory and enforces that these files stay formatted with
a presubmit check.

**Reviewers:** Please carefully review all the commits except for the
one titled "formatted". The "formatted" commit was auto-generated by
running `dev/tools/format.sh -a -f`. The other commits were hand-crafted
to prepare the repo for the formatting change. I recommend reviewing the
commits one-by-one via the "Commits" tab and avoiding Github's "Files
changed" tab as it will likely slow down your browser because of the
size of this PR.

---------

Co-authored-by: Kate Lovett <katelovett@google.com>
Co-authored-by: LongCatIsLooong <31859944+LongCatIsLooong@users.noreply.github.com>
2024-12-19 20:06:21 +00:00

323 lines
8.0 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
class VideoCard extends StatelessWidget {
const VideoCard({super.key, this.controller, this.title, this.subtitle});
final VideoPlayerController? controller;
final String? title;
final String? subtitle;
Widget _buildInlineVideo() {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0),
child: Center(
child: AspectRatio(
aspectRatio: 3 / 2,
child: Hero(tag: controller!, child: VideoPlayerLoading(controller)),
),
),
);
}
Widget _buildFullScreenVideo() {
return Scaffold(
appBar: AppBar(title: Text(title!)),
body: Center(
child: AspectRatio(
aspectRatio: 3 / 2,
child: Hero(tag: controller!, child: VideoPlayPause(controller)),
),
),
);
}
@override
Widget build(BuildContext context) {
Widget fullScreenRoutePageBuilder(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
return _buildFullScreenVideo();
}
void pushFullScreenWidget() {
final TransitionRoute<void> route = PageRouteBuilder<void>(
settings: RouteSettings(name: title),
pageBuilder: fullScreenRoutePageBuilder,
);
route.completed.then((void value) {
controller!.setVolume(0.0);
});
controller!.setVolume(1.0);
Navigator.of(context).push(route);
}
return SafeArea(
top: false,
bottom: false,
child: Card(
child: Column(
children: <Widget>[
ListTile(title: Text(title!), subtitle: Text(subtitle!)),
GestureDetector(onTap: pushFullScreenWidget, child: _buildInlineVideo()),
],
),
),
);
}
}
class VideoPlayerLoading extends StatefulWidget {
const VideoPlayerLoading(this.controller, {super.key});
final VideoPlayerController? controller;
@override
State<VideoPlayerLoading> createState() => _VideoPlayerLoadingState();
}
class _VideoPlayerLoadingState extends State<VideoPlayerLoading> {
bool? _initialized;
@override
void initState() {
super.initState();
_initialized = widget.controller!.value.isInitialized;
widget.controller!.addListener(() {
if (!mounted) {
return;
}
final bool controllerInitialized = widget.controller!.value.isInitialized;
if (_initialized != controllerInitialized) {
setState(() {
_initialized = controllerInitialized;
});
}
});
}
@override
Widget build(BuildContext context) {
if (_initialized!) {
return VideoPlayer(widget.controller!);
}
return Stack(
fit: StackFit.expand,
children: <Widget>[
VideoPlayer(widget.controller!),
const Center(child: CircularProgressIndicator()),
],
);
}
}
class VideoPlayPause extends StatefulWidget {
const VideoPlayPause(this.controller, {super.key});
final VideoPlayerController? controller;
@override
State createState() => _VideoPlayPauseState();
}
class _VideoPlayPauseState extends State<VideoPlayPause> {
_VideoPlayPauseState() {
listener = () {
if (mounted) {
setState(() {});
}
};
}
FadeAnimation? imageFadeAnimation;
late VoidCallback listener;
VideoPlayerController? get controller => widget.controller;
@override
void initState() {
super.initState();
controller!.addListener(listener);
}
@override
void deactivate() {
controller!.removeListener(listener);
super.deactivate();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.bottomCenter,
fit: StackFit.expand,
children: <Widget>[
GestureDetector(
child: VideoPlayerLoading(controller),
onTap: () {
if (!controller!.value.isInitialized) {
return;
}
if (controller!.value.isPlaying) {
imageFadeAnimation = const FadeAnimation(child: Icon(Icons.pause, size: 100.0));
controller!.pause();
} else {
imageFadeAnimation = const FadeAnimation(child: Icon(Icons.play_arrow, size: 100.0));
controller!.play();
}
},
),
Center(child: imageFadeAnimation),
],
);
}
}
class FadeAnimation extends StatefulWidget {
const FadeAnimation({super.key, this.child, this.duration = const Duration(milliseconds: 500)});
final Widget? child;
final Duration duration;
@override
State<FadeAnimation> createState() => _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation> with SingleTickerProviderStateMixin {
late AnimationController animationController;
@override
void initState() {
super.initState();
animationController = AnimationController(duration: widget.duration, vsync: this);
animationController.addListener(() {
if (mounted) {
setState(() {});
}
});
animationController.forward(from: 0.0);
}
@override
void deactivate() {
animationController.stop();
super.deactivate();
}
@override
void didUpdateWidget(FadeAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.child != widget.child) {
animationController.forward(from: 0.0);
}
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return animationController.isAnimating
? Opacity(opacity: 1.0 - animationController.value, child: widget.child)
: Container();
}
}
class VideoDemo extends StatefulWidget {
const VideoDemo({super.key});
static const String routeName = '/video';
@override
State<VideoDemo> createState() => _VideoDemoState();
}
class _VideoDemoState extends State<VideoDemo> with SingleTickerProviderStateMixin {
final VideoPlayerController butterflyController = VideoPlayerController.asset(
'videos/butterfly.mp4',
package: 'flutter_gallery_assets',
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
final VideoPlayerController beeController = VideoPlayerController.asset(
'videos/bee.mp4',
package: 'flutter_gallery_assets',
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
bool isDisposed = false;
// Only non-test mobile environments are supported for this demo.
bool isSupported = Platform.isAndroid || Platform.isIOS;
@override
void initState() {
super.initState();
if (!isSupported) {
return;
}
Future<void> initController(VideoPlayerController controller, String name) async {
controller.setLooping(true);
controller.setVolume(0.0);
controller.play();
await controller.initialize();
if (mounted) {
setState(() {});
}
}
initController(butterflyController, 'butterfly');
initController(beeController, 'bee');
}
@override
void dispose() {
isDisposed = true;
butterflyController.dispose();
beeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Videos')),
body:
isSupported
? Scrollbar(
child: ListView(
primary: true,
children: <Widget>[
VideoCard(
title: 'Butterfly',
subtitle: '… flutters by',
controller: butterflyController,
),
VideoCard(
title: 'Bee',
subtitle: '… gently buzzing',
controller: beeController,
),
],
),
)
: const Placeholder(),
);
}
}