build method
- BuildContext context
override
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
Implementation
// BUILD
////////////////////////////////////////////////////////////////////////
@override
Widget build(BuildContext context) {
// Build the Timer Widget.
////////////////////////////////////
// APP BUTTONS
////////////////////////////////////
// We begin with building the six main app buttons that are displayed on the
// home screen. Each button has a label, tootltip, and a callback for when
// the button is pressed.
final startButton = AppButton(
title: 'Start',
tooltip: '''
Tap here to begin a session of silence for ${(_duration / 60).round()}
minutes, beginning and ending with three chimes. The blue progress
circle indicates an active session.
'''
.trim(),
onPressed: () {
logMessage('Start Session');
if (mounted) {
_reset();
dingDong(_player);
_controller.restart(duration: _duration);
_stopSleep();
setState(() {
_sessionType = 'bell';
_startTime = DateTime.now();
});
}
},
fontWeight: FontWeight.bold,
backgroundColor: Colors.lightGreenAccent.shade100,
);
final pauseResumeButton = AppButton(
title: _isPaused ? 'Resume' : 'Pause',
tooltip: _isPaused
? '''
Tap here to Resume the timer and the audio from where they were paused.
'''
.trim()
: '''
Tap here to Pause the timer and the audio. They can be resumed with a press
of the Resume button.
'''
.trim(),
onPressed: () {
setState(() {
if (_isPaused) {
// Resume the timer and audio.
_controller.resume();
_player.resume();
_stopSleep();
_isPaused = false;
} else {
// Pause the timer and audio.
_controller.pause();
_player.pause();
_allowSleep();
_isPaused = true;
}
});
},
backgroundColor: Colors.grey[200] ?? Colors.grey,
);
final introButton = AppButton(
title: 'Intro',
tooltip: '''
Tap here to play a short introduction for a session. After the introduction a
${(_duration / 60).round()} minute session of silence will begin and end with
three dings. The blue progress circle indicates an active session.
'''
.trim(),
onPressed: _intro,
fontWeight: FontWeight.bold,
backgroundColor: Colors.blue.shade100,
);
final guidedButton = AppButton(
title: 'Guided',
tooltip: '''
Tap here to play a ${10 + (_duration / 60).round()} minute guided session.
The session begins with instructions for meditation from John Main.
Introductory music is followed by three chimes and a ${(_duration / 60).round()}
minute silent session which is then finished with another three chimes. The
blue progress circle indicates an active session. The
audio may take a little time to download for the Web version.
'''
.trim(),
onPressed: _guided,
fontWeight: FontWeight.bold,
backgroundColor: Colors.purple.shade100,
);
////////////////////////////////////
// DURATION CHOICE
////////////////////////////////////
final Widget durationChoice = Wrap(
spacing: 8.0, // Gap between adjacent chips.
runSpacing: 4.0, // Gap between lines.
children: [5, 10, 15, 20, 25, 30].map((number) {
final isSelected = _duration == number * 60;
return ChoiceChip(
label: Text(
number.toString(),
style: TextStyle(
color: isSelected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
fontWeight: isSelected ? FontWeight.w700 : FontWeight.w500,
),
),
selected: isSelected,
backgroundColor: Theme.of(context).colorScheme.surface,
selectedColor:
Theme.of(context).colorScheme.primary.withValues(alpha: 0.15),
side: BorderSide(
color: isSelected
? Theme.of(context).colorScheme.primary.withValues(alpha: 0.4)
: Colors.grey.withValues(alpha: 0.3),
),
showCheckmark: false, // This will hide the tick mark.
onSelected: (selected) {
if (selected) {
setState(() {
_duration = number * 60;
debugPrint('CHOOSE: duration $_duration');
_controller.restart(duration: _duration);
_controller.pause();
_player.stop();
_allowSleep();
});
_saveSettings();
}
},
);
}).toList(),
);
////////////////////////////////////
// RETURN
////////////////////////////////////
final timerDisplay = AppCircularCountDownTimer(
duration: _duration,
controller: _controller,
onComplete: _complete,
);
final buttonsMatrix = Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24),
decoration: BoxDecoration(
// 20260220 gjw Choose a darker background for the active button
// area. This darker background, same as title bar and timer central,
// more clearly distinguishes the buttons matrix from the app
// background.
color: background,
// color: Colors.white.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(32),
border: Border.all(
color: Colors.white.withValues(alpha: 0.5),
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
introButton,
const SizedBox(width: widthSpacer),
startButton,
],
),
const SizedBox(height: heightSpacer),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
guidedButton,
const SizedBox(width: widthSpacer),
pauseResumeButton,
],
),
const SizedBox(height: 32),
Text(
'Select duration (minutes)',
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.w600,
color:
Theme.of(context).colorScheme.primary.withValues(alpha: 0.7),
),
),
const SizedBox(height: 12),
durationChoice,
const SizedBox(height: 32),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Column(
children: [
TextField(
controller: _titleController,
decoration: InputDecoration(
labelText: 'Title',
hintText: 'Enter session title (optional)',
prefixIcon: const Icon(Icons.label_outline),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.white.withValues(alpha: 0.8),
),
),
const SizedBox(height: 12),
TextField(
controller: _descriptionController,
decoration: InputDecoration(
labelText: 'Description',
hintText: 'Enter session description (optional)',
prefixIcon: const Icon(Icons.notes),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(16),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.white.withValues(alpha: 0.8),
),
maxLines: 2,
),
],
),
),
],
),
);
return OrientationBuilder(
builder: (context, orientation) {
if (orientation == Orientation.portrait) {
return SingleChildScrollView(
child: Padding(
// Add some top and bottom padding so the timer is not clipped at the
// top nor the chips at the bottom.
padding: const EdgeInsets.only(top: 10, bottom: 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 2 * heightSpacer),
timerDisplay,
const SizedBox(height: 2 * heightSpacer),
buttonsMatrix,
],
),
),
);
} else {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(child: Center(child: timerDisplay)),
const SizedBox(width: 2 * widthSpacer),
Expanded(child: Center(child: buttonsMatrix)),
],
),
),
);
}
},
);
}