Skip to content

Commit

Permalink
[timetable] display week header for a while when user turned pages in…
Browse files Browse the repository at this point in the history
… preview
  • Loading branch information
liplum committed May 3, 2024
1 parent 2a897ec commit af7b326
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 43 deletions.
204 changes: 204 additions & 0 deletions lib/design/animation/marquee.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

class AnimatedMarquee extends StatefulWidget {
final List<Widget> children;
final Duration transitionDuration;
final Duration marqueeDuration;

const AnimatedMarquee({
super.key,
required this.children,
this.transitionDuration = Duration.zero,
required this.marqueeDuration,
});

@override
State<AnimatedMarquee> createState() => _AnimatedMarqueeState();
}

class _AnimatedMarqueeState extends State<AnimatedMarquee> {
var curIndex = 0;
late Timer marqueeTimer;

@override
void initState() {
super.initState();
marqueeTimer = startTimer(widget.marqueeDuration);
}

Timer startTimer(Duration duration) {
return Timer.periodic(duration, (timer) {
if (widget.children.isEmpty) {
setIndex(0);
} else {
setState(() {
setIndex((curIndex + 1) % widget.children.length);
});
}
});
}

void setIndex(int newIndex) {
if (newIndex != curIndex) {
setState(() {
curIndex = newIndex;
});
}
}

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

@override
void didUpdateWidget(covariant AnimatedMarquee oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.marqueeDuration != widget.marqueeDuration) {
marqueeTimer.cancel();
marqueeTimer = startTimer(widget.marqueeDuration);
}
setState(() {
setIndex(curIndex % widget.children.length);
});
}

@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
duration: widget.transitionDuration,
child: widget.children[curIndex],
);
}
}

class AnimatedDualSwitcherController {
_AnimatedDualSwitcherState? _attached;

void switchTo(bool state) {
final attached = _attached!;
attached.$state = state;
final alwaysSwitchTo = attached.widget.alwaysSwitchTo;
if (alwaysSwitchTo == null) return;
if (attached.$state != alwaysSwitchTo) {
attached.passed = Duration.zero;
}
attached.passed = Duration.zero;
}

void _attach(_AnimatedDualSwitcherState state) {
if (_attached != null) {
throw FlutterError("$AnimatedDualSwitcherController should be attached only one at the same time.");
}
_attached = state;
}

void dispose() {
_attached = null;
}
}

class AnimatedDualSwitcher extends StatefulWidget {
final Duration transitionDuration;
final Duration switchDuration;
final Widget trueChild;
final Widget falseChild;
final AnimatedDualSwitcherController? controller;
final bool? alwaysSwitchTo;
final bool initial;

const AnimatedDualSwitcher({
super.key,
required this.transitionDuration,
required this.switchDuration,
required this.trueChild,
required this.falseChild,
this.controller,
this.alwaysSwitchTo,
required this.initial,
});

Widget _getWidget(bool state) {
return state ? trueChild : falseChild;
}

@override
State<AnimatedDualSwitcher> createState() => _AnimatedDualSwitcherState();
}

class _AnimatedDualSwitcherState extends State<AnimatedDualSwitcher> with SingleTickerProviderStateMixin {
late Ticker marqueeTicker;
var lastElapsed = Duration.zero;
var passed = Duration.zero;
late var _state = widget.initial;

bool get $state => _state;

set $state(bool newV) {
if (newV != _state) {
setState(() {
_state = newV;
});
}
}

late var controller = widget.controller ?? AnimatedDualSwitcherController();

@override
void initState() {
super.initState();
startTicker();
controller._attach(this);
}

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

void startTicker() {
marqueeTicker = createTicker((elapsed) {
final alwaysSwitchTo = widget.alwaysSwitchTo;
final delta = elapsed - lastElapsed;
lastElapsed = elapsed;
assert(elapsed >= lastElapsed);
if ($state != alwaysSwitchTo) {
passed += delta;
}
if (passed >= widget.switchDuration) {
passed = Duration.zero;
if (alwaysSwitchTo == null) {
$state = !$state;
} else {
$state = alwaysSwitchTo;
}
}
});
marqueeTicker.start();
}

@override
void didUpdateWidget(covariant AnimatedDualSwitcher oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.controller != controller) {
controller.dispose();
controller = (widget.controller ?? AnimatedDualSwitcherController()).._attach(this);
}
if (oldWidget.switchDuration != widget.switchDuration) {
passed = Duration.zero;
}
}

@override
Widget build(BuildContext context) {
return AnimatedSwitcher(
duration: widget.transitionDuration,
child: widget._getWidget($state),
);
}
}
29 changes: 28 additions & 1 deletion lib/timetable/page/preview.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:rettulf/rettulf.dart';
import 'package:sit/design/adaptive/foundation.dart';
import 'package:sit/design/animation/marquee.dart';
import 'package:text_scroll/text_scroll.dart';

import '../entity/background.dart';
Expand All @@ -13,6 +15,9 @@ import '../entity/pos.dart';
import '../widgets/style.dart';
import '../widgets/timetable/board.dart';

import '../i18n.dart';
import 'timetable.dart';

class TimetablePreviewPage extends StatefulWidget {
final SitTimetableEntity entity;

Expand All @@ -32,6 +37,15 @@ class _TimetablePreviewPageState extends State<TimetablePreviewPage> {
late final $currentPos = ValueNotifier(timetable.locate(DateTime.now()));
final scrollController = ScrollController();
late SitTimetableEntity entity = widget.entity;
final $showWeekHeader = AnimatedDualSwitcherController();

@override
void initState() {
$currentPos.addListener(() {
$showWeekHeader.switchTo(true);
});
super.initState();
}

@override
void dispose() {
Expand All @@ -45,7 +59,15 @@ class _TimetablePreviewPageState extends State<TimetablePreviewPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: TextScroll(timetable.name),
title: AnimatedDualSwitcher(
transitionDuration: Durations.medium4,
switchDuration: const Duration(milliseconds: 2000),
trueChild: $currentPos >> (ctx, pos) => i18n.weekOrderedName(number: pos.weekIndex + 1).text(),
falseChild: TextScroll(timetable.name),
alwaysSwitchTo: false,
initial: false,
controller: $showWeekHeader,
),
actions: [
PlatformIconButton(
icon: const Icon(Icons.swap_horiz),
Expand All @@ -60,6 +82,11 @@ class _TimetablePreviewPageState extends State<TimetablePreviewPage> {
$displayMode: $displayMode,
$currentPos: $currentPos,
),
floatingActionButton: TimetableJumpButton(
$displayMode: $displayMode,
$currentPos: $currentPos,
timetable: timetable,
),
);
}
}
Expand Down
Loading

0 comments on commit af7b326

Please sign in to comment.