Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions chapter_05/lib/login_screen.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import 'package:flutter/material.dart';
import 'stopwatch.dart';
import './stopwatch.dart';

class LoginScreen extends StatefulWidget {
static const route = '/login';

const LoginScreen({Key? key}) : super(key: key);

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

class _LoginScreenState extends State<LoginScreen> {
String name;
late String name;
final _nameController = TextEditingController();
final _emailController = TextEditingController();
final _formKey = GlobalKey<FormState>();
Expand All @@ -17,7 +20,7 @@ class _LoginScreenState extends State<LoginScreen> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
title: const Text('Login'),
),
body: Center(
child: _buildLoginForm(),
Expand All @@ -37,14 +40,14 @@ class _LoginScreenState extends State<LoginScreen> {
controller: _nameController,
decoration: InputDecoration(labelText: 'Runner'),
validator: (text) =>
text.isEmpty ? 'Enter the runner\'s name.' : null,
text!.isEmpty ? 'Enter the runner\'s name.' : null,
),
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(labelText: 'Email'),
validator: (text) {
if (text.isEmpty) {
if (text!.isEmpty) {
return 'Enter the runner\'s email.';
}

Expand All @@ -56,9 +59,9 @@ class _LoginScreenState extends State<LoginScreen> {
return null;
},
),
SizedBox(height: 20),
const SizedBox(height: 20),
ElevatedButton(
child: Text('Continue'),
child: const Text('Continue'),
onPressed: _validate,
),
],
Expand All @@ -67,7 +70,7 @@ class _LoginScreenState extends State<LoginScreen> {

void _validate() {
final form = _formKey.currentState;
if (!form.validate()) {
if (!form!.validate()) {
return;
}
final name = _nameController.text;
Expand Down
12 changes: 8 additions & 4 deletions chapter_05/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,23 @@ import 'package:flutter/material.dart';
import 'package:stopwatch/login_screen.dart';
import './stopwatch.dart';

void main() => runApp(StopwatchApp());
void main() => runApp(const StopwatchApp());

class StopwatchApp extends StatelessWidget {
const StopwatchApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (context) => LoginScreen(),
'/': (context) => const LoginScreen(),
LoginScreen.route: (context) => LoginScreen(),
StopWatch.route: (context) => StopWatch(),
StopWatch.route: (context) => StopWatch(
email: '',
name: '',
),
},
initialRoute: '/',

);
}
}
7 changes: 3 additions & 4 deletions chapter_05/lib/platform_alert.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ class PlatformAlert {
final String title;
final String message;

const PlatformAlert({@required this.title, @required this.message})
: assert(title != null),
assert(message != null);
const PlatformAlert({required this.title, required this.message});

void show(BuildContext context) {
final platform = Theme.of(context).platform;

Expand Down Expand Up @@ -42,7 +41,7 @@ class PlatformAlert {
content: Text(message),
actions: [
CupertinoButton(
child: Text('Close'),
child: const Text('Close'),
onPressed: () => Navigator.of(context).pop())
]);
});
Expand Down
130 changes: 72 additions & 58 deletions chapter_05/lib/stopwatch.dart
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import 'dart:async';
import 'package:flutter/material.dart';

import 'platform_alert.dart';

class StopWatch extends StatefulWidget {
static const route = '/stopwatch';
final String name;
final String email;

const StopWatch({Key key, this.name, this.email}) : super(key: key);
const StopWatch({Key? key, required this.name, required this.email})
: super(key: key);

@override
State createState() => StopWatchState();
State<StatefulWidget> createState() => StopWatchState();
}

class StopWatchState extends State<StopWatch> {
int milliseconds = 0;
int seconds = 0;
late int milliseconds;
final itemHeight = 60.0;
final scrollController = ScrollController();
Timer timer;
final laps = <int>[];
bool isTicking = false;
late Timer timer;
final laps = [];
late bool isTicking = false;

@override
void initState() {
super.initState();

isTicking = false;
milliseconds = 0;
}

void _onTick(Timer time) {
setState(() {
Expand All @@ -30,14 +37,14 @@ class StopWatchState extends State<StopWatch> {

@override
Widget build(BuildContext context) {
String name = ModalRoute.of(context).settings.arguments;
String name = ModalRoute.of(context)!.settings.arguments as String;

return Scaffold(
appBar: AppBar(
title: Text(name),
),
body: Column(
children: <Widget>[
children: [
Expanded(child: _buildCounter(context)),
Expanded(child: _buildLapDisplay()),
],
Expand All @@ -50,56 +57,56 @@ class StopWatchState extends State<StopWatch> {
color: Theme.of(context).primaryColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
children: [
Text(
'Lap ${laps.length + 1}',
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(color: Colors.white),
?.copyWith(color: Colors.white),
),
Text(
_secondsText(milliseconds),
style: Theme.of(context)
.textTheme
.headline5
.copyWith(color: Colors.white),
?.copyWith(color: Colors.white),
),
SizedBox(height: 20),
_buildControls()
],
),
);
}

Row _buildControls() {
Widget _buildControls() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
children: [
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.green),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
backgroundColor: MaterialStateProperty.all(Colors.green),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
child: Text('Start'),
child: const Text('Start'),
onPressed: isTicking ? null : _startTimer,
),
SizedBox(width: 20),
const SizedBox(width: 20),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.yellow),
backgroundColor: MaterialStateProperty.all(Colors.yellow),
foregroundColor: MaterialStateProperty.all(Colors.black),
),
child: Text('Lap'),
child: const Text('Lap'),
onPressed: isTicking ? _lap : null,
),
SizedBox(width: 20),
const SizedBox(width: 20),
Builder(
builder: (context) => TextButton(
builder: (context) => ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.red),
foregroundColor: MaterialStateProperty.all<Color>(Colors.white),
backgroundColor: MaterialStateProperty.all(Colors.red),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
child: Text('Stop'),
child: const Text('Stop'),
onPressed: isTicking ? () => _stopTimer(context) : null,
),
),
Expand All @@ -108,28 +115,28 @@ class StopWatchState extends State<StopWatch> {
}

void _startTimer() {
timer = Timer.periodic(Duration(milliseconds: 100), _onTick);
timer = Timer.periodic(
const Duration(milliseconds: 100),
_onTick,
);
setState(() {
laps.clear();
isTicking = true;
});
}

void _stopTimer(BuildContext context) {
timer.cancel();
setState(() {
timer.cancel();
isTicking = false;
});
// final totalRuntime = laps.fold(milliseconds, (total, lap) => total + lap);
// final alert = PlatformAlert(
// title: 'Run Completed!',
// message: 'Total Run Time is ${_secondsText(totalRuntime)}.',
// );
// alert.show(context);
final controller =
showBottomSheet(context: context, builder: _buildRunCompleteSheet);

Future.delayed(Duration(seconds: 5)).then((_) {

final controller = showBottomSheet(
context: context,
builder: _buildRunCompleteSheet,
);

Future.delayed(const Duration(seconds: 5)).then((_) {
controller.close();
});
}
Expand All @@ -142,7 +149,7 @@ class StopWatchState extends State<StopWatch> {
void _lap() {
scrollController.animateTo(
itemHeight * laps.length,
duration: Duration(milliseconds: 500),
duration: const Duration(milliseconds: 500),
curve: Curves.easeIn,
);
setState(() {
Expand All @@ -162,33 +169,40 @@ class StopWatchState extends State<StopWatch> {
return ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: 50),
title: Text('Lap ${index + 1}'),
trailing: Text(_secondsText(milliseconds)),
trailing: Text(
_secondsText(milliseconds),
),
);
},
),
);
}

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

Widget _buildRunCompleteSheet(BuildContext context) {
final totalRuntime = laps.fold(milliseconds, (total, lap) => total + lap);
final totalRuntime =
laps.fold(milliseconds, (total, lap) => (total as dynamic) + lap);
final textTheme = Theme.of(context).textTheme;

return SafeArea(
child: Container(
color: Theme.of(context).cardColor,
width: double.infinity,
child: Padding(
child: Container(
color: Theme.of(context).cardColor,
width: double.infinity,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 30.0),
child: Column(mainAxisSize: MainAxisSize.min, children: [
Text('Run Finished!', style: textTheme.headline6),
Text('Total Run Time is ${_secondsText(totalRuntime)}.')
])),
));
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Run Finished!',
style: textTheme.headline6,
),
Text(
'Total Run Time is ${_secondsText(totalRuntime)}.',
)
],
),
),
),
);
}
}