Skip to content
Snippets Groups Projects
thinking_indicator.dart 3.78 KiB
Newer Older
Benoît Harrault's avatar
Benoît Harrault committed
// 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 'package:flutter/widgets.dart';
import 'styling.dart';

/// This is a self-animated progress spinner, only instead of spinning it
/// moves five little circles in a horizontal arrangement.
class ThinkingIndicator extends ImplicitlyAnimatedWidget {
  final Color color;
  final double height;
  final bool visible;

  ThinkingIndicator({
    this.color = const Color(0xffffffff),
    this.height = 10.0,
    this.visible = true,
    Key? key,
  }) : super(
          duration: Styling.thinkingFadeDuration,
          key: key,
        );

  @override
  ImplicitlyAnimatedWidgetState createState() => _ThinkingIndicatorState();
}

class _ThinkingIndicatorState extends AnimatedWidgetBaseState<ThinkingIndicator> {
Benoît Harrault's avatar
Benoît Harrault committed
  Tween<double>? _opacityTween;

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: SizedBox(
        height: widget.height,
        child: Opacity(
          opacity: _opacityTween!.evaluate(animation),
          child: _opacityTween!.evaluate(animation) != 0
Benoît Harrault's avatar
Benoît Harrault committed
              ? _AnimatedCircles(
                  color: widget.color,
                  height: widget.height,
                )
              : null,
        ),
      ),
    );
  }

  @override
  void forEachTween(visitor) {
    _opacityTween = visitor(
      _opacityTween,
      widget.visible ? 1.0 : 0.0,
      (dynamic value) => Tween<double>(begin: value as double),
    ) as Tween<double>?;
  }
}

class _AnimatedCircles extends StatefulWidget {
  final Color color;
  final double height;

  const _AnimatedCircles({
    required this.color,
    required this.height,
    Key? key,
  }) : super(key: key);

  @override
  _AnimatedCirclesState createState() => _AnimatedCirclesState();
}

class _AnimatedCirclesState extends State<_AnimatedCircles>
    with SingleTickerProviderStateMixin {
  late Animation<double> _thinkingAnimation;
  late AnimationController _thinkingController;

  @override
  void initState() {
    super.initState();
    _thinkingController =
        AnimationController(duration: const Duration(milliseconds: 500), vsync: this)
          ..addStatusListener((status) {
            // This bit ensures that the animation reverses course rather than
            // stopping.
            if (status == AnimationStatus.completed) _thinkingController.reverse();
            if (status == AnimationStatus.dismissed) _thinkingController.forward();
          });
    _thinkingAnimation = Tween(begin: 0.0, end: widget.height)
        .animate(CurvedAnimation(parent: _thinkingController, curve: Curves.easeOut));
Benoît Harrault's avatar
Benoît Harrault committed
    _thinkingController.forward();
  }

  @override
  void dispose() {
    _thinkingController.dispose();
    super.dispose();
  }

  Widget _buildCircle() {
    return Container(
      width: widget.height,
      height: widget.height,
      decoration: BoxDecoration(
        border: Border.all(
          color: widget.color,
          width: 2.0,
        ),
        borderRadius: BorderRadius.all(const Radius.circular(5.0)),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _thinkingAnimation,
      builder: (context, child) {
        return Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildCircle(),
            SizedBox(width: _thinkingAnimation.value),
            _buildCircle(),
            SizedBox(width: _thinkingAnimation.value),
            _buildCircle(),
            SizedBox(width: _thinkingAnimation.value),
            _buildCircle(),
            SizedBox(width: _thinkingAnimation.value),
            _buildCircle(),
          ],
        );
      },
    );
  }
}