content: // Copyright 2018 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:math' as math;
  
  import 'package:flutter/material.dart';
  
  // This widget is essentially the backdrop itself.
  class BackdropPage extends StatefulWidget {
    static const String routeName = '/material/backdrop';
  
    @override
    _BackdropPageState createState() => _BackdropPageState();
  }
  
  class _BackdropPageState extends State<BackdropPage> with SingleTickerProviderStateMixin {
    final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');
    AnimationController _controller;
    Category _category = allCategories[0];
  
    @override
    void initState() {
      super.initState();
      _controller = AnimationController(
        duration: const Duration(milliseconds: 300),
        value: 1.0,
        vsync: this,
      );
    }
  
    @override
    void dispose() {
      _controller.dispose();
      super.dispose();
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          elevation: 0.0,
          title: BackdropTitle(
            listenable: _controller.view,
          ),
          actions: <Widget>[
            IconButton(
              onPressed: _toggleBackdropPanelVisibility,
              icon: AnimatedIcon(
                icon: AnimatedIcons.close_menu,
                semanticLabel: 'close',
                progress: _controller.view,
              ),
            ),
          ],
        ),
        body: LayoutBuilder(
          builder: _buildStack,
        ),
      );
    }
  
    Widget _buildStack(BuildContext context, BoxConstraints constraints) {
      const panelTitleHeight = 48.0;
      final panelSize = constraints.biggest;
      final panelTop = panelSize.height - panelTitleHeight;
  
      final panelAnimation = _controller.drive(
        RelativeRectTween(
          begin: RelativeRect.fromLTRB(
            0.0,
            panelTop - MediaQuery.of(context).padding.bottom,
            0.0,
            panelTop - panelSize.height,
          ),
          end: const RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
        ),
      );
  
      final theme = Theme.of(context);
      final backdropItems = allCategories.map<Widget>((Category category) {
        final selected = category == _category;
        return Material(
          shape: const RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(4.0)),
          ),
          color: selected ? Colors.white.withOpacity(0.25) : Colors.transparent,
          child: ListTile(
            title: Text(category.title),
            selected: selected,
            onTap: () {
              _changeCategory(category);
            },
          ),
        );
      }).toList();
  
      return Container(
        key: _backdropKey,
        color: theme.primaryColor,
        child: Stack(
          children: <Widget>[
            ListTileTheme(
              iconColor: theme.primaryIconTheme.color,
              textColor: theme.primaryTextTheme.title.color.withOpacity(0.6),
              selectedColor: theme.primaryTextTheme.title.color,
              child: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: backdropItems,
                ),
              ),
            ),
            PositionedTransition(
              rect: panelAnimation,
              child: BackdropPanel(
                onTap: _toggleBackdropPanelVisibility,
                onVerticalDragUpdate: _handleDragUpdate,
                onVerticalDragEnd: _handleDragEnd,
                title: Text(_category.title),
                child: CategoryView(category: _category),
              ),
            ),
          ],
        ),
      );
    }
  
    void _changeCategory(Category category) {
      setState(() {
        _category = category;
        _controller.fling(velocity: 2.0);
      });
    }
  
    bool get _backdropPanelVisible {
      final status = _controller.status;
      return status == AnimationStatus.completed || status == AnimationStatus.forward;
    }
  
    void _toggleBackdropPanelVisibility() {
      _controller.fling(velocity: _backdropPanelVisible ? -2.0 : 2.0);
    }
  
    double get _backdropHeight {
      final RenderBox renderBox = _backdropKey.currentContext.findRenderObject();
      return renderBox.size.height;
    }
  
    // By design: the panel can only be opened with a swipe. To close the panel
    // the user must either tap its heading or the backdrop's menu icon.
  
    void _handleDragUpdate(DragUpdateDetails details) {
      if (_controller.isAnimating || _controller.status == AnimationStatus.completed) return;
  
      _controller.value -= details.primaryDelta / (_backdropHeight ?? details.primaryDelta);
    }
  
    void _handleDragEnd(DragEndDetails details) {
      if (_controller.isAnimating || _controller.status == AnimationStatus.completed) return;
  
      final flingVelocity = details.velocity.pixelsPerSecond.dy / _backdropHeight;
      if (flingVelocity < 0.0) {
        _controller.fling(velocity: math.max(2.0, -flingVelocity));
      } else if (flingVelocity > 0.0) {
        _controller.fling(velocity: math.min(-2.0, -flingVelocity));
      } else {
        _controller.fling(velocity: _controller.value < 0.5 ? -2.0 : 2.0);
      }
    }
  }
  
  class Category {
    const Category({this.title});
  
    final String title;
  
    @override
    String toString() => '$runtimeType("$title")';
  }
  
  const List<Category> allCategories = <Category>[
    Category(
      title: '1',
    ),
    Category(
      title: '2',
    ),
    Category(
      title: '3',
    ),
    Category(
      title: '4',
    ),
    Category(
      title: '5',
    ),
    Category(
      title: '6',
    ),
  ];
  
  class CategoryView extends StatelessWidget {
    const CategoryView({Key key, this.category}) : super(key: key);
  
    final Category category;
  
    @override
    Widget build(BuildContext context) {
      return ListView.builder(
          itemCount: allCategories.length,
          itemBuilder: (context, index) {
            return Card(
              elevation: 5,
              margin: const EdgeInsets.all(16),
              child: Center(
                child: Column(
                  children: <Widget>[
                    Text(allCategories[index].title),
                    const SizedBox(
                      height: 100,
                    )
                  ],
                ),
              ),
            );
          });
    }
  }
  
  // One BackdropPanel is visible at a time. It's stacked on top of the
  // the BackdropDemo.
  class BackdropPanel extends StatelessWidget {
    const BackdropPanel({
      Key key,
      this.onTap,
      this.onVerticalDragUpdate,
      this.onVerticalDragEnd,
      this.title,
      this.child,
    }) : super(key: key);
  
    final VoidCallback onTap;
    final GestureDragUpdateCallback onVerticalDragUpdate;
    final GestureDragEndCallback onVerticalDragEnd;
    final Widget title;
    final Widget child;
  
    @override
    Widget build(BuildContext context) {
      final theme = Theme.of(context);
      return Material(
        elevation: 2.0,
        borderRadius: const BorderRadius.only(
          topLeft: Radius.circular(16.0),
          topRight: Radius.circular(16.0),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            GestureDetector(
              behavior: HitTestBehavior.opaque,
              onVerticalDragUpdate: onVerticalDragUpdate,
              onVerticalDragEnd: onVerticalDragEnd,
              onTap: onTap,
              child: Container(
                height: 48.0,
                padding: const EdgeInsetsDirectional.only(start: 16.0),
                alignment: AlignmentDirectional.centerStart,
                child: DefaultTextStyle(
                  style: theme.textTheme.subhead,
                  child: Tooltip(
                    message: 'Tap to dismiss',
                    child: title,
                  ),
                ),
              ),
            ),
            const Divider(height: 1.0),
            Expanded(child: child),
          ],
        ),
      );
    }
  }
  
  // Cross fades between 'Select a Category' and 'Asset Viewer'.
  class BackdropTitle extends AnimatedWidget {
    const BackdropTitle({
      Key key,
      Listenable listenable,
    }) : super(key: key, listenable: listenable);
  
    @override
    Widget build(BuildContext context) {
      final Animation<double> animation = listenable;
      return DefaultTextStyle(
        style: Theme.of(context).primaryTextTheme.title,
        softWrap: false,
        overflow: TextOverflow.ellipsis,
        child: Stack(
          children: <Widget>[
            Opacity(
              opacity: CurvedAnimation(
                parent: ReverseAnimation(animation),
                curve: const Interval(0.5, 1.0),
              ).value,
              child: const Text('Select a Category'),
            ),
            Opacity(
              opacity: CurvedAnimation(
                parent: animation,
                curve: const Interval(0.5, 1.0),
              ).value,
              child: const Text('Asset Viewer'),
            ),
          ],
        ),
      );
    }
  }
  