Skip to content

Commit

Permalink
feat: prevent pageview scroll on edit, timer input layout
Browse files Browse the repository at this point in the history
  • Loading branch information
ThatNerdSquared committed Jan 14, 2024
1 parent cc9e7f9 commit 63846db
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 124 deletions.
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pkgs/client/linux/* linguist-generated
pkgs/client/windows/* linguist-generated
pkgs/client/android/* linguist-generated

3 changes: 2 additions & 1 deletion docs/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@
grey timer field
- [ ] [NATHAN] restyle todos (is inset rectangle possible?), add time
allocation support to todo input
- [ ] [NATHAN] prevent scroll when editing/block running
- [x] [NATHAN] prevent scroll when editing/block running
- [ ] [NATHAN] add state + conditional rendering to display card components
differently when block is running
- [ ] [NATHAN] radial timer when block is running
- [ ] [CHARLIE] consistent text box focus highlight
- [ ] [NATHAN] implement vertical todos carousel for running block
- [ ] [NATHAN] fix pageview flickering
- [ ] [NATHAN] investigate flutter freezing when app backgrounded
Expand Down
1 change: 1 addition & 0 deletions pkgs/client/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ class LentoHomeState extends ConsumerState<LentoHome> {
),
child: PageView.builder(
controller: controller,
physics: editingEnv != null ? const NeverScrollableScrollPhysics() : null,
onPageChanged: (value) => setState(() {
pageViewIndex = value % limitIndex;
}),
Expand Down
216 changes: 119 additions & 97 deletions pkgs/client/lib/widgets/card_timer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class CardTimerState extends ConsumerState<CardTimer> {
bool _isEditingTimer = false;
final _formKey = GlobalKey<FormState>();
late Color _timerColor = widget.startingColour;
FocusNode secondsFocusNode = FocusNode();

double _calculateTimerMargin(maxWidth, isEditingTimer) {
if (isEditingTimer &&
Expand Down Expand Up @@ -54,114 +55,135 @@ class CardTimerState extends ConsumerState<CardTimer> {
ref.watch(lentoDeckProvider)[widget.cardId]!.isActivated;

return LayoutBuilder(
builder: (context, constraints) => Column(children: [
MouseRegion(
onHover: (pointer) {
setState(() {
isCardActivated
? null
: _timerColor = Theme.of(context).colorScheme.surfaceTint;
});
},
onExit: (pointer) {
setState(() {
_timerColor = Theme.of(context).colorScheme.surface;
_isEditingTimer = false;
});
},
child: GestureDetector(
onTap: () {
builder: (context, constraints) {
final fullTimerWidth = constraints.maxWidth -
2 *
_calculateTimerMargin(
constraints.maxWidth,
_isEditingTimer,
);
return Column(children: [
MouseRegion(
onHover: (pointer) {
setState(() {
if (!isCardActivated) {
_isEditingTimer = true;
}
isCardActivated
? null
: _timerColor = Theme.of(context).colorScheme.surfaceTint;
});
},
child: Container(
margin: EdgeInsets.only(
left: _calculateTimerMargin(
constraints.maxWidth, _isEditingTimer),
right: _calculateTimerMargin(
constraints.maxWidth, _isEditingTimer),
),
padding: const EdgeInsets.only(
top: PretConfig.defaultElementSpacing * 3 / 2,
bottom: PretConfig.defaultElementSpacing * 3 / 2,
),
decoration: BoxDecoration(
color: _timerColor,
borderRadius: PretConfig.defaultBorderRadius,
),
child: Column(
children: [
Form(
key: _formKey,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: !_isEditingTimer
? [
for (final item in [
blockDuration.fmtHours,
':',
blockDuration.fmtMinutes,
':',
blockDuration.fmtSeconds
])
Text(
item,
style: Theme.of(context)
.textTheme
.displayLarge!
.copyWith(fontWeight: FontWeight.w700),
)
]
: [
for (final timeSection in TimeSection.values)
TimerEditWheel(
cardId: widget.cardId,
timeSection: timeSection,
handleChange: _onTimerValChanged,
)
]),
),
Container(
onExit: (pointer) {
setState(() {
_timerColor = Theme.of(context).colorScheme.surface;
_isEditingTimer = false;
});
},
child: GestureDetector(
onTap: () {
setState(() {
if (!isCardActivated) {
_isEditingTimer = true;
secondsFocusNode.requestFocus();
}
});
},
child: Container(
margin: EdgeInsets.only(
left: _calculateTimerMargin(
constraints.maxWidth, _isEditingTimer),
right: _calculateTimerMargin(
constraints.maxWidth, _isEditingTimer),
),
padding: const EdgeInsets.only(
top: PretConfig.defaultElementSpacing * 3 / 2,
bottom: PretConfig.defaultElementSpacing * 3 / 2,
),
decoration: BoxDecoration(
color: _timerColor,
borderRadius: PretConfig.defaultBorderRadius,
),
child: Column(
children: [
SizedBox(
width: fullTimerWidth,
child: Form(
key: _formKey,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: !_isEditingTimer
? [
for (final item in [
blockDuration.fmtHours,
':',
blockDuration.fmtMinutes,
':',
blockDuration.fmtSeconds
])
Text(
item,
style: Theme.of(context)
.textTheme
.displayLarge!
.copyWith(
fontWeight: FontWeight.w700),
)
]
: [
for (final timeSection
in TimeSection.values)
Padding(
padding: const EdgeInsets.only(
bottom:
PretConfig.defaultElementSpacing,
),
child: TimerEditWheel(
cardId: widget.cardId,
timeSection: timeSection,
handleChange: _onTimerValChanged,
fullTimerWidth: fullTimerWidth,
focusNode:
timeSection == TimeSection.seconds
? secondsFocusNode
: null,
),
)
]),
),
),
Container(
margin: const EdgeInsets.only(
top: PretConfig.minElementSpacing,
top: PretConfig.thinElementSpacing,
),
decoration: const BoxDecoration(
borderRadius: PretConfig.defaultBorderRadius,
boxShadow: [PretConfig.defaultShadow],
),
child: Container(
decoration: const BoxDecoration(
boxShadow: [PretConfig.defaultShadow],
),
child: ElevatedButton(
onPressed: isCardActivated ? null : _startTimer,
style: ButtonStyle(
foregroundColor: MaterialStatePropertyAll(
Theme.of(context).colorScheme.primary),
backgroundColor: MaterialStatePropertyAll(
Theme.of(context).colorScheme.tertiary),
textStyle: MaterialStatePropertyAll(
Theme.of(context).textTheme.displaySmall),
padding: const MaterialStatePropertyAll(
EdgeInsets.all(
PretConfig.defaultElementSpacing)),
shape: const MaterialStatePropertyAll(
RoundedRectangleBorder(
borderRadius:
PretConfig.thinBorderRadius)),
),
child: Text(isCardActivated
? 'Session Started'
: 'Start Block')),
))
],
child: ElevatedButton(
onPressed: isCardActivated ? null : _startTimer,
style: ButtonStyle(
foregroundColor: MaterialStatePropertyAll(
Theme.of(context).colorScheme.primary),
backgroundColor: MaterialStatePropertyAll(
Theme.of(context).colorScheme.tertiary),
textStyle: MaterialStatePropertyAll(
Theme.of(context).textTheme.displaySmall),
padding: const MaterialStatePropertyAll(
EdgeInsets.all(
PretConfig.defaultElementSpacing)),
shape: const MaterialStatePropertyAll(
RoundedRectangleBorder(
borderRadius: PretConfig.thinBorderRadius)),
),
child: Text(isCardActivated
? 'Session Started'
: 'Start Block')),
)
],
),
),
),
),
),
]),
]);
},
);
}

Expand Down
77 changes: 51 additions & 26 deletions pkgs/client/lib/widgets/timer_edit_wheel.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pret_a_porter/pret_a_porter.dart';

import '../config.dart';
import '../main.dart';
Expand All @@ -8,43 +9,67 @@ class TimerEditWheel extends ConsumerWidget {
final String cardId;
final TimeSection timeSection;
final Function(String, TimeSection) handleChange;
final double fullTimerWidth;
final FocusNode? focusNode;

const TimerEditWheel({
super.key,
required this.cardId,
required this.timeSection,
required this.handleChange,
required this.fullTimerWidth,
this.focusNode,
});

@override
Widget build(BuildContext context, WidgetRef ref) {
final blockDuration = ref
.watch(lentoDeckProvider.select((deck) => deck[cardId]!.blockDuration));
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Text(switch (timeSection) {
TimeSection.hours => 'Hours',
TimeSection.minutes => 'Minutes',
TimeSection.seconds => 'Seconds'
}),
TextFormField(
initialValue: switch (timeSection) {
TimeSection.hours => blockDuration.hours,
TimeSection.minutes => blockDuration.minutes,
TimeSection.seconds => blockDuration.seconds,
}
.toString(),
validator: (val) {
if (val == null || val.isEmpty || int.tryParse(val) == null) {
return 'Please enter a valid number!';
}
if (0 > int.parse(val) ||
int.parse(val) > (timeSection == TimeSection.hours ? 23 : 59)) {
return 'Number is out of range! Please pick a number between 0 and 59, or 0 and 23 for hours.';
}
return null;
},
keyboardType: TextInputType.number,
onChanged: (value) => handleChange(value, timeSection))
]);
return LayoutBuilder(builder: (context, constraints) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: fullTimerWidth / 5,
child: TextFormField(
focusNode: focusNode,
decoration: const InputDecoration(
isDense: true,
counterText: '',
),
initialValue: switch (timeSection) {
TimeSection.hours => blockDuration.hours,
TimeSection.minutes => blockDuration.minutes,
TimeSection.seconds => blockDuration.seconds,
}
.toString(),
maxLength: 2,
validator: (val) {
if (val == null || val.isEmpty || int.tryParse(val) == null) {
return 'Please enter a valid number!';
}
if (0 > int.parse(val) ||
int.parse(val) >
(timeSection == TimeSection.hours ? 23 : 59)) {
return 'Number is out of range! Please pick a number between 0 and 59, or 0 and 23 for hours.';
}
return null;
},
keyboardType: TextInputType.number,
onChanged: (value) => handleChange(value, timeSection)),
),
Text(switch (timeSection) {
TimeSection.hours => 'h',
TimeSection.minutes => 'm',
TimeSection.seconds => 's'
}),
const Padding(
padding: EdgeInsets.only(
right: PretConfig.defaultElementSpacing,
),
)
],
);
});
}
}

0 comments on commit 63846db

Please sign in to comment.