Commit 3a393249 authored by Sigurd Meldgaard's avatar Sigurd Meldgaard Committed by Mikkel Nygaard Ravn

Gallery video demo (#13195)

parent 1bd8ffbc
......@@ -12,3 +12,4 @@ export 'material/material.dart';
export 'pesto_demo.dart';
export 'shrine_demo.dart';
export 'typography_demo.dart';
export 'video_demo.dart';
// Copyright 2017 The Chromium 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 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
// TODO(sigurdm): These should not be stored here.
const String butterflyUri =
'https://flutter.github.io/assets-for-api-docs/videos/butterfly.mp4';
const String beeUri =
'https://flutter.github.io/assets-for-api-docs/videos/bee.mp4';
class VideoCard extends StatelessWidget {
final VideoPlayerController controller;
final String title;
final String subtitle;
const VideoCard({Key key, this.controller, this.title, this.subtitle})
: super(key: key);
Widget _buildInlineVideo() {
return new Padding(
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 30.0),
child: new Center(
child: new AspectRatio(
aspectRatio: 3 / 2,
child: new Hero(
tag: controller,
child: new VideoPlayer(controller),
),
),
),
);
}
Widget _buildFullScreenVideo() {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new AspectRatio(
aspectRatio: 3 / 2,
child: new Hero(
tag: controller,
child: new VideoPlayPause(controller),
),
),
),
);
}
@override
Widget build(BuildContext context) {
Widget fullScreenRoutePageBuilder(BuildContext context,
Animation<double> animation, Animation<double> secondaryAnimation) {
return new AnimatedBuilder(
child: _buildFullScreenVideo(),
animation: animation,
builder: (BuildContext context, Widget child) {
// TODO(sigurdm): It seems we get a animation.value of 1.0
// at first when entering the route. Find out how to avoid
// this.
controller.setVolume(animation.value);
return child;
},
);
}
void pushFullScreenWidget() {
final TransitionRoute<Null> route = new PageRouteBuilder<Null>(
settings: new RouteSettings(name: title, isInitialRoute: false),
pageBuilder: fullScreenRoutePageBuilder,
);
route.completed.then((Null _) {
controller.setVolume(0.0);
});
Navigator.of(context).push(route);
}
return new Card(
child: new Column(
children: <Widget>[
new ListTile(title: new Text(title), subtitle: new Text(subtitle)),
new GestureDetector(
onTap: pushFullScreenWidget,
child: _buildInlineVideo(),
),
],
),
);
}
}
class VideoPlayPause extends StatefulWidget {
final VideoPlayerController controller;
const VideoPlayPause(this.controller);
@override
State createState() => new _VideoPlayPauseState();
}
class _VideoPlayPauseState extends State<VideoPlayPause> {
FadeAnimation imageFadeAnimation;
VoidCallback listener;
_VideoPlayPauseState() {
listener = () {
setState(() {});
};
}
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) {
final List<Widget> children = <Widget>[
new GestureDetector(
child: new VideoPlayer(controller),
onTap: () {
if (!controller.value.initialized) {
return;
}
if (controller.value.isPlaying) {
imageFadeAnimation = new FadeAnimation(
child: new Icon(Icons.pause, size: 100.0),
);
controller.pause();
} else {
imageFadeAnimation = new FadeAnimation(
child: new Icon(Icons.play_arrow, size: 100.0),
);
controller.play();
}
},
),
new Center(child: imageFadeAnimation),
];
if (!controller.value.initialized) {
children.add(new Container());
}
return new Stack(
alignment: Alignment.bottomCenter,
fit: StackFit.passthrough,
children: children,
);
}
}
class FadeAnimation extends StatefulWidget {
final Widget child;
final Duration duration;
const FadeAnimation({
this.child,
this.duration: const Duration(milliseconds: 500),
});
@override
_FadeAnimationState createState() => new _FadeAnimationState();
}
class _FadeAnimationState extends State<FadeAnimation>
with SingleTickerProviderStateMixin {
AnimationController animationController;
@override
void initState() {
super.initState();
animationController = new 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
? new Opacity(
opacity: 1.0 - animationController.value,
child: widget.child,
)
: new Container();
}
}
class ConnectivityOverlay extends StatefulWidget {
final Widget child;
final Completer<Null> connectedCompleter;
final GlobalKey<ScaffoldState> scaffoldKey;
const ConnectivityOverlay({
this.child,
this.connectedCompleter,
this.scaffoldKey,
});
@override
_ConnectivityOverlayState createState() => new _ConnectivityOverlayState();
}
class _ConnectivityOverlayState extends State<ConnectivityOverlay> {
StreamSubscription<ConnectivityResult> connectivitySubscription;
bool connected = true;
static const Widget errorSnackBar = const SnackBar(
backgroundColor: Colors.red,
content: const ListTile(
title: const Text('No network'),
subtitle: const Text(
'To load the videos you must have an active network connection',
),
),
);
Stream<ConnectivityResult> connectivityStream() async* {
final Connectivity connectivity = new Connectivity();
ConnectivityResult previousResult = await connectivity.checkConnectivity();
yield previousResult;
await for (ConnectivityResult result
in connectivity.onConnectivityChanged) {
if (result != previousResult) {
yield result;
previousResult = result;
}
}
}
@override
void initState() {
super.initState();
connectivitySubscription = connectivityStream().listen(
(ConnectivityResult connectivityResult) {
if (!mounted) {
return;
}
if (connectivityResult == ConnectivityResult.none) {
widget.scaffoldKey.currentState.showSnackBar(errorSnackBar);
} else {
if (!widget.connectedCompleter.isCompleted) {
widget.connectedCompleter.complete(null);
}
}
},
);
}
@override
void dispose() {
connectivitySubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}
class VideoDemo extends StatefulWidget {
const VideoDemo({Key key}) : super(key: key);
static const String routeName = '/video';
@override
_VideoDemoState createState() => new _VideoDemoState();
}
class _VideoDemoState extends State<VideoDemo>
with SingleTickerProviderStateMixin {
final VideoPlayerController butterflyController = new VideoPlayerController(
butterflyUri,
);
final VideoPlayerController beeController = new VideoPlayerController(
beeUri,
);
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
final Completer<Null> connectedCompleter = new Completer<Null>();
@override
void initState() {
super.initState();
Future<Null> initController(VideoPlayerController controller) async {
controller.setLooping(true);
controller.setVolume(0.0);
controller.play();
await connectedCompleter.future;
await controller.initialize();
setState(() {});
}
initController(butterflyController);
initController(beeController);
}
@override
void dispose() {
butterflyController.dispose();
beeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
key: scaffoldKey,
appBar: new AppBar(
title: const Text('Videos'),
),
body: new ConnectivityOverlay(
child: new ListView(
children: <Widget>[
new VideoCard(
title: 'Butterfly',
subtitle: '… flutters by',
controller: butterflyController,
),
new VideoCard(
title: 'Bee',
subtitle: '… gently buzzing',
controller: beeController,
),
],
),
connectedCompleter: connectedCompleter,
scaffoldKey: scaffoldKey,
),
);
}
}
......@@ -54,7 +54,7 @@ List<GalleryItem> _buildGalleryItems() {
// Demos
new GalleryItem(
title: 'Shrine',
subtitle:'Basic shopping app',
subtitle: 'Basic shopping app',
category: 'Demos',
routeName: ShrineDemo.routeName,
buildRoute: (BuildContext context) => new ShrineDemo(),
......@@ -73,6 +73,13 @@ List<GalleryItem> _buildGalleryItems() {
routeName: AnimationDemo.routeName,
buildRoute: (BuildContext context) => const AnimationDemo(),
),
new GalleryItem(
title: 'Video',
subtitle: 'Video playback',
category: 'Demos',
routeName: VideoDemo.routeName,
buildRoute: (BuildContext context) => const VideoDemo(),
),
// Material Components
new GalleryItem(
title: 'Bottom navigation',
......
......@@ -4,9 +4,11 @@ dependencies:
sdk: flutter
collection: 1.14.3
intl: 0.15.2
connectivity: 0.1.0
string_scanner: 1.0.2
url_launcher: 0.4.2+5
cupertino_icons: 0.1.1
video_player: 0.0.5
# Also update dev/benchmarks/complex_layout/pubspec.yaml
flutter_gallery_assets:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment