mirror of
https://github.com/flutter/samples.git
synced 2026-04-03 02:02:27 +00:00
* add package:http dependency in dad_jokes * add package:http dependency in filipino_cuisine * don't build package:http demos until flutter/flutter#34858 is resolved * update gallery * update github_dataviz * update particle_background * don't build github_dataviz (uses package:http) * update slide_puzzle * update spinning_square * update timeflow * update vision_challenge * update charts * update dad_jokes * update filipino cuisine * ignore build output * update timeflow and vision_challenge * update slide_puzzle * don't commit build/ directory * move preview.png images to assets * fix path url join * update readme * update web/readme.md
188 lines
4.0 KiB
Dart
188 lines
4.0 KiB
Dart
import 'dart:async';
|
|
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
|
|
import 'app_state.dart';
|
|
import 'core/puzzle_animator.dart';
|
|
import 'frame_nanny.dart';
|
|
import 'shared_theme.dart';
|
|
import 'theme_plaster.dart';
|
|
import 'theme_seattle.dart';
|
|
import 'theme_simple.dart';
|
|
|
|
class PuzzleHomeState extends State
|
|
with TickerProviderStateMixin
|
|
implements AppState {
|
|
TabController _tabController;
|
|
AnimationController _controller;
|
|
Animation<Offset> _shuffleOffsetAnimation;
|
|
|
|
@override
|
|
Animation<Offset> get shuffleOffsetAnimation => _shuffleOffsetAnimation;
|
|
|
|
@override
|
|
final PuzzleAnimator puzzle;
|
|
|
|
@override
|
|
final animationNotifier = _AnimationNotifier();
|
|
|
|
@override
|
|
TabController get tabController => _tabController;
|
|
|
|
final _nanny = FrameNanny();
|
|
|
|
SharedTheme _currentTheme;
|
|
|
|
@override
|
|
SharedTheme get currentTheme => _currentTheme;
|
|
|
|
@override
|
|
set currentTheme(SharedTheme theme) {
|
|
setState(() {
|
|
_currentTheme = theme;
|
|
});
|
|
}
|
|
|
|
Duration _tickerTimeSinceLastEvent = Duration.zero;
|
|
Ticker _ticker;
|
|
Duration _lastElapsed;
|
|
StreamSubscription sub;
|
|
|
|
@override
|
|
bool autoPlay = false;
|
|
|
|
PuzzleHomeState(this.puzzle) {
|
|
sub = puzzle.onEvent.listen(_onPuzzleEvent);
|
|
|
|
_themeDataCache = List.unmodifiable([
|
|
ThemeSimple(this),
|
|
ThemeSeattle(this),
|
|
ThemePlaster(this),
|
|
]);
|
|
|
|
_currentTheme = themeData.first;
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_ticker ??= createTicker(_onTick);
|
|
_ensureTicking();
|
|
|
|
_controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 200),
|
|
);
|
|
|
|
_shuffleOffsetAnimation = _controller.drive(const _Shake());
|
|
_tabController = TabController(vsync: this, length: _themeDataCache.length);
|
|
|
|
_tabController.addListener(() {
|
|
currentTheme = _themeDataCache[_tabController.index];
|
|
});
|
|
}
|
|
|
|
List<SharedTheme> _themeDataCache;
|
|
|
|
@override
|
|
Iterable<SharedTheme> get themeData => _themeDataCache;
|
|
|
|
@override
|
|
void setAutoPlay(bool newValue) {
|
|
if (newValue != autoPlay) {
|
|
setState(() {
|
|
// Only allow enabling autoPlay if the puzzle is not solved
|
|
autoPlay = newValue && !puzzle.solved;
|
|
if (autoPlay) {
|
|
_ensureTicking();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) => _currentTheme.build(context);
|
|
|
|
@override
|
|
void dispose() {
|
|
animationNotifier.dispose();
|
|
_tabController.dispose();
|
|
_controller?.dispose();
|
|
_ticker?.dispose();
|
|
sub.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onPuzzleEvent(PuzzleEvent e) {
|
|
_tickerTimeSinceLastEvent = Duration.zero;
|
|
_ensureTicking();
|
|
if (e == PuzzleEvent.noop) {
|
|
assert(e == PuzzleEvent.noop);
|
|
_controller.reset();
|
|
_controller.forward();
|
|
}
|
|
setState(() {
|
|
// noop
|
|
});
|
|
}
|
|
|
|
void _ensureTicking() {
|
|
if (!_ticker.isTicking) {
|
|
_ticker.start();
|
|
}
|
|
}
|
|
|
|
void _onTick(Duration elapsed) {
|
|
if (elapsed == Duration.zero) {
|
|
_lastElapsed = elapsed;
|
|
}
|
|
final delta = elapsed - _lastElapsed;
|
|
_lastElapsed = elapsed;
|
|
|
|
if (delta.inMilliseconds <= 0) {
|
|
// `_delta` may be negative or zero if `elapsed` is zero (first tick)
|
|
// or during a restart. Just ignore this case.
|
|
return;
|
|
}
|
|
|
|
_tickerTimeSinceLastEvent += delta;
|
|
puzzle.update(_nanny.tick(delta));
|
|
|
|
if (!puzzle.stable) {
|
|
animationNotifier.animate();
|
|
} else {
|
|
if (!autoPlay) {
|
|
_ticker.stop();
|
|
_lastElapsed = null;
|
|
}
|
|
}
|
|
|
|
if (autoPlay &&
|
|
_tickerTimeSinceLastEvent > const Duration(milliseconds: 200)) {
|
|
puzzle.playRandom();
|
|
|
|
if (puzzle.solved) {
|
|
setAutoPlay(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class _Shake extends Animatable<Offset> {
|
|
const _Shake();
|
|
|
|
@override
|
|
Offset transform(double t) => Offset(0.01 * math.sin(t * math.pi * 3), 0);
|
|
}
|
|
|
|
class _AnimationNotifier extends ChangeNotifier implements AnimationNotifier {
|
|
_AnimationNotifier();
|
|
|
|
@override
|
|
void animate() {
|
|
notifyListeners();
|
|
}
|
|
}
|