Skip to content

Instantly share code, notes, and snippets.

@rrousselGit
Last active September 27, 2024 06:00
Show Gist options
  • Save rrousselGit/beaf7442a20ea7e2ed3f13bbd40984a8 to your computer and use it in GitHub Desktop.
Save rrousselGit/beaf7442a20ea7e2ed3f13bbd40984a8 to your computer and use it in GitHub Desktop.
A clock + its test
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:provider/provider.dart';
import 'package:clock/clock.dart';
void main() {
runApp(
Provider(
create: (_) => Clock(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
showPerformanceOverlay: true,
home: MyHomePage(),
routes: {
'/whatever': (c) => Scaffold(
appBar: AppBar(title: Text('Whatever')),
body: Center(
child: RaisedButton(
onPressed: () => Navigator.pop(c),
child: Text('Return to previous page'),
),
),
)
},
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Clock'),
),
body: Center(
child: AnimatedClock(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, '/whatever');
},
child: Icon(Icons.edit),
),
);
}
}
class AnimatedClock extends StatefulWidget {
@override
AnimatedClockState createState() => AnimatedClockState();
}
@visibleForTesting
class AnimatedClockState extends State<AnimatedClock>
with SingleTickerProviderStateMixin {
Ticker _ticker;
DateTime _initialTime;
DateTime _now;
@override
void initState() {
super.initState();
_now = _initialTime = context.read<Clock>().now();
_ticker = this.createTicker((elapsed) {
final newTime = _initialTime.add(elapsed);
// rebuild only if seconds changes instead of every frame
if (_now.second != newTime.second) {
setState(() => _now = newTime);
}
});
_ticker.start();
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// A widget that renders an unanimated clock from a DateTime
return _ClockRenderer(
dateTime: _now,
);
}
}
class _ClockRenderer extends StatelessWidget {
const _ClockRenderer({
Key key,
this.dateTime,
}) : super(key: key);
final DateTime dateTime;
@override
Widget build(BuildContext context) {
return Container(
width: 210,
height: 210,
decoration: BoxDecoration(
border: Border.all(width: 3, color: Colors.black),
borderRadius: BorderRadius.circular(210),
),
child: Stack(
children: <Widget>[
Positioned(
top: 105,
left: 100,
child: Transform(
alignment: Alignment.topCenter,
transform: Matrix4.rotationZ(pi + pi / 24 * 2 * dateTime.hour),
child: Container(height: 90, width: 5, color: Colors.black),
),
),
Positioned(
top: 105,
left: 100,
child: Transform(
alignment: Alignment.topCenter,
transform: Matrix4.rotationZ(pi + pi / 60 * 2 * dateTime.minute),
child: Container(height: 50, width: 5, color: Colors.grey),
),
),
Positioned(
top: 105,
left: 101.5,
child: Transform(
alignment: Alignment.topCenter,
transform: Matrix4.rotationZ(pi + pi / 60 * 2 * dateTime.second),
child: Container(height: 90, width: 2, color: Colors.red),
),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_clock/main.dart';
import 'package:provider/provider.dart';
void main() {
testWidgets('renders our clock', (tester) async {
await tester.pumpWidget(
Provider(
create: (_) => tester.binding.clock,
child: MaterialApp(
home: Center(
child: RepaintBoundary(
child: AnimatedClock(),
),
),
),
),
);
await expectLater(
find.byType(AnimatedClock),
matchesGoldenFile('initial_frame.png'),
);
await tester.pump(Duration(hours: 2, minutes: 42, seconds: 21));
await expectLater(
find.byType(AnimatedClock),
matchesGoldenFile('updated_frame.png'),
);
});
}
@torinkwok
Copy link

torinkwok commented Jul 6, 2020

The length of the hour hand:

Container(height: 90, width: 5, color: Colors.black)

is confusingly longer than the minute hand:

Container(height: 50, width: 5, color: Colors.grey)

The following screenshot is a golden file rendered by _ClockRenderer, which was configured with:

DateTime.parse('1989-06-04 20:18:04Z')

initial_frame

It took me a while to figure out that the hour hand is the black one. An hour hand and a minute hand with heights of 50 and 80 respectively would be better off:

initial_frame

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment