fixed designs and flow
This commit is contained in:
@@ -249,7 +249,7 @@ class _OtpScreenState extends ConsumerState<OtpScreen> {
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 30,
|
||||
letterSpacing: 14,
|
||||
color: Color(0xFFC4D4C9),
|
||||
color: KrowPalette.edge,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -382,7 +382,7 @@ class VerifiedScreen extends ConsumerWidget {
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
color: KrowPalette.sage,
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: SafeArea(
|
||||
@@ -603,7 +603,7 @@ class _PermissionsScreenState extends ConsumerState<PermissionsScreen> {
|
||||
final permission = _permissions[_step];
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: KrowPalette.sage,
|
||||
backgroundColor: Colors.white,
|
||||
body: Stack(
|
||||
children: [
|
||||
// The app itself, already built, sitting behind the ask.
|
||||
@@ -681,7 +681,7 @@ class _PermissionSheet extends StatelessWidget {
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFCFDDD4),
|
||||
color: KrowPalette.edge,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -9,7 +9,8 @@ import '../state/availability_controller.dart';
|
||||
import '../widgets/common.dart';
|
||||
import '../widgets/week_strip.dart';
|
||||
|
||||
/// Auto-Match only offers shifts inside the blocks switched on here.
|
||||
/// What the worker tells Auto-Match about each day of the coming week:
|
||||
/// available, actively wanting work, or marked off.
|
||||
class AvailabilityScreen extends ConsumerStatefulWidget {
|
||||
const AvailabilityScreen({super.key});
|
||||
|
||||
@@ -30,11 +31,10 @@ class _AvailabilityScreenState extends ConsumerState<AvailabilityScreen> {
|
||||
final controller = ref.watch(availabilityProvider.notifier);
|
||||
ref.watch(availabilityProvider);
|
||||
|
||||
final blocks = controller.blocksFor(_selected);
|
||||
final hours = blocks.fold<int>(0, (sum, b) => sum + b.hours);
|
||||
final plan = controller.planFor(_selected);
|
||||
final marked = <int>{
|
||||
for (var i = 0; i < _week.length; i++)
|
||||
if (controller.blocksFor(_week[i]).isNotEmpty) i,
|
||||
if (!controller.isDayOff(_week[i])) i,
|
||||
};
|
||||
|
||||
return Scaffold(
|
||||
@@ -49,7 +49,7 @@ class _AvailabilityScreenState extends ConsumerState<AvailabilityScreen> {
|
||||
Row(
|
||||
children: [
|
||||
HeaderIconButton(
|
||||
icon: LucideIcons.arrowLeft,
|
||||
icon: LucideIcons.chevronLeft,
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -115,67 +115,63 @@ class _AvailabilityScreenState extends ConsumerState<AvailabilityScreen> {
|
||||
KrowCard(
|
||||
padding: const EdgeInsets.fromLTRB(16, 14, 16, 16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
Fmt.dayShort(_selected),
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
blocks.isEmpty
|
||||
? 'Marked as a day off'
|
||||
: 'Available · $hours hrs',
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: blocks.isEmpty
|
||||
? KrowPalette.mute
|
||||
: KrowPalette.blue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Switch(
|
||||
value: blocks.isNotEmpty,
|
||||
onChanged: (v) =>
|
||||
controller.setDayAvailable(_selected, v),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
Fmt.dayShort(_selected),
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
plan.status.summary,
|
||||
style: TextStyle(
|
||||
fontSize: 12.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: plan.isSet
|
||||
? KrowPalette.blue
|
||||
: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
for (final block in DayBlock.values)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 9),
|
||||
child: _BlockRow(
|
||||
block: block,
|
||||
on: blocks.contains(block),
|
||||
onTap: () =>
|
||||
controller.toggleBlock(_selected, block),
|
||||
),
|
||||
for (final status in DayAvailability.values) ...[
|
||||
_StatusOption(
|
||||
status: status,
|
||||
selected: plan.status == status,
|
||||
onTap: () => controller.setStatus(_selected, status),
|
||||
),
|
||||
// The window belongs to the option it qualifies, so it
|
||||
// opens directly beneath it rather than at the foot of
|
||||
// the card where it would read as a separate question.
|
||||
if (status == DayAvailability.prefers)
|
||||
_Expand(
|
||||
open: plan.status == DayAvailability.prefers,
|
||||
child: _WindowPicker(
|
||||
window: plan.window,
|
||||
onChanged: (w) =>
|
||||
controller.setWindow(_selected, w),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
InfoBanner(
|
||||
blocks.isEmpty
|
||||
? 'With every block off, Auto-Match will not send you '
|
||||
'anything on this day.'
|
||||
: 'Saved. Auto-Match will only offer you shifts inside '
|
||||
'these blocks.',
|
||||
icon: blocks.isEmpty ? null : LucideIcons.check,
|
||||
),
|
||||
InfoBanner(switch (plan.status) {
|
||||
DayAvailability.unavailable =>
|
||||
'Auto-Match will not send you anything on this day.',
|
||||
DayAvailability.available =>
|
||||
'Saved. Auto-Match can offer you any shift on this day.',
|
||||
DayAvailability.prefers =>
|
||||
plan.window.isAnyTime
|
||||
? 'Saved. Auto-Match will offer you this day first.'
|
||||
: 'Saved. Auto-Match will look for shifts '
|
||||
'${plan.window.sentence} first.',
|
||||
}, icon: plan.isSet ? LucideIcons.check : null),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -222,116 +218,266 @@ class _Preset extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _BlockRow extends StatelessWidget {
|
||||
const _BlockRow({required this.block, required this.on, required this.onTap});
|
||||
/// One of the three answers, as a selectable card.
|
||||
class _StatusOption extends StatelessWidget {
|
||||
const _StatusOption({
|
||||
required this.status,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final DayBlock block;
|
||||
final bool on;
|
||||
final DayAvailability status;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
IconData get _icon => switch (block) {
|
||||
DayBlock.morning => LucideIcons.sunrise,
|
||||
DayBlock.afternoon => LucideIcons.sun,
|
||||
DayBlock.evening => LucideIcons.moon,
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const duration = Duration(milliseconds: 260);
|
||||
const duration = Duration(milliseconds: 220);
|
||||
const curve = Curves.easeOutCubic;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: onTap,
|
||||
// Every part of the row animates together, so switching a block on is a
|
||||
// single motion rather than four things changing at once.
|
||||
child: AnimatedContainer(
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: on ? KrowPalette.blueTint : const Color(0xFFE5F0E8),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: on
|
||||
? KrowPalette.blue.withValues(alpha: 0.3)
|
||||
: Colors.transparent,
|
||||
return Semantics(
|
||||
inMutuallyExclusiveGroup: true,
|
||||
selected: selected,
|
||||
button: true,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: AnimatedContainer(
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
padding: const EdgeInsets.fromLTRB(14, 13, 14, 13),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? KrowPalette.blueTint : Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: selected ? KrowPalette.blue : KrowPalette.line,
|
||||
width: selected ? 1.5 : 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
color: on ? KrowPalette.blue : Colors.white,
|
||||
borderRadius: BorderRadius.circular(11),
|
||||
border: Border.all(
|
||||
color: on ? KrowPalette.blue : KrowPalette.line,
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
_icon,
|
||||
size: 18,
|
||||
color: on ? Colors.white : const Color(0xFF949FA5),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 13),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
style: TextStyle(
|
||||
fontSize: 14.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: on ? KrowPalette.ink : KrowPalette.mute,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_Radio(selected: selected),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
style: TextStyle(
|
||||
fontSize: 14.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: selected ? KrowPalette.blue : KrowPalette.ink,
|
||||
),
|
||||
child: Text(status.label),
|
||||
),
|
||||
child: Text(block.label),
|
||||
),
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: on ? KrowPalette.blue : const Color(0xFF949FA5),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
status.blurb,
|
||||
style: const TextStyle(
|
||||
fontSize: 11.5,
|
||||
height: 1.35,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
child: Text(block.window),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: on ? KrowPalette.blue : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(7),
|
||||
border: Border.all(
|
||||
color: on ? KrowPalette.blue : const Color(0xFFC4D4C9),
|
||||
width: 1.5,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: AnimatedScale(
|
||||
scale: on ? 1 : 0,
|
||||
duration: duration,
|
||||
curve: Curves.easeOutBack,
|
||||
child: const Icon(
|
||||
LucideIcons.check,
|
||||
size: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The filled dot. Drawn rather than using Material's Radio, which carries its
|
||||
/// own 48px tap target and would push the rows apart.
|
||||
class _Radio extends StatelessWidget {
|
||||
const _Radio({required this.selected});
|
||||
|
||||
final bool selected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const duration = Duration(milliseconds: 220);
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: duration,
|
||||
curve: Curves.easeOutCubic,
|
||||
width: 20,
|
||||
height: 20,
|
||||
// Nudged down to sit on the title's optical centre rather than its box.
|
||||
margin: const EdgeInsets.only(top: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? KrowPalette.blue : Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: selected ? KrowPalette.blue : KrowPalette.edge,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
child: AnimatedScale(
|
||||
scale: selected ? 1 : 0,
|
||||
duration: duration,
|
||||
curve: Curves.easeOutBack,
|
||||
child: const Icon(LucideIcons.check, size: 12, color: Colors.white),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reveals its child by height, so the card grows into the extra question
|
||||
/// instead of the rows below it jumping.
|
||||
class _Expand extends StatelessWidget {
|
||||
const _Expand({required this.open, required this.child});
|
||||
|
||||
final bool open;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedSize(
|
||||
duration: const Duration(milliseconds: 220),
|
||||
curve: Curves.easeOutCubic,
|
||||
alignment: Alignment.topCenter,
|
||||
child: open
|
||||
? Padding(padding: const EdgeInsets.only(top: 10), child: child)
|
||||
: const SizedBox(width: double.infinity),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The optional hours on a "prefers to work" day.
|
||||
class _WindowPicker extends StatelessWidget {
|
||||
const _WindowPicker({required this.window, required this.onChanged});
|
||||
|
||||
final PreferredWindow window;
|
||||
final ValueChanged<PreferredWindow> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.fromLTRB(14, 12, 14, 14),
|
||||
decoration: BoxDecoration(
|
||||
color: KrowPalette.sage,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SectionLabel('Preferred window (optional)'),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _HourField(
|
||||
label: 'From',
|
||||
value: window.from,
|
||||
// A start later than the end is not a range, so the stale
|
||||
// end is dropped rather than kept as something impossible.
|
||||
onChanged: (h) => onChanged(
|
||||
PreferredWindow(
|
||||
from: h,
|
||||
to: (h != null && window.to != null && window.to! <= h)
|
||||
? null
|
||||
: window.to,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: _HourField(
|
||||
label: 'To',
|
||||
value: window.to,
|
||||
// Only hours after the start are offered, so the pair cannot
|
||||
// be put into an order that means nothing.
|
||||
min: window.from == null ? null : window.from! + 1,
|
||||
onChanged: (h) =>
|
||||
onChanged(PreferredWindow(from: window.from, to: h)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HourField extends StatelessWidget {
|
||||
const _HourField({
|
||||
required this.label,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.min,
|
||||
});
|
||||
|
||||
final String label;
|
||||
final int? value;
|
||||
final ValueChanged<int?> onChanged;
|
||||
|
||||
/// Earliest hour offered, used to keep "To" after "From".
|
||||
final int? min;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final first = min ?? 0;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 11.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Container(
|
||||
height: 44,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(color: KrowPalette.line),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<int?>(
|
||||
value: value,
|
||||
isExpanded: true,
|
||||
isDense: true,
|
||||
icon: const Icon(
|
||||
LucideIcons.chevronDown,
|
||||
size: 16,
|
||||
color: KrowPalette.mute,
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontSize: 13.5,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: KrowPalette.ink,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onChanged: onChanged,
|
||||
items: [
|
||||
const DropdownMenuItem<int?>(
|
||||
value: null,
|
||||
child: Text('Any time'),
|
||||
),
|
||||
for (var h = first; h < 24; h++)
|
||||
DropdownMenuItem<int?>(
|
||||
value: h,
|
||||
child: Text('${h.toString().padLeft(2, '0')}:00'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,11 @@ import '../state/shifts_controller.dart';
|
||||
import '../state/timecards_controller.dart';
|
||||
import '../widgets/collapsing_header.dart';
|
||||
import '../widgets/common.dart';
|
||||
import '../widgets/motion.dart';
|
||||
import '../widgets/glass_segments.dart';
|
||||
import '../widgets/krow_wordmark.dart';
|
||||
import '../widgets/shift_card.dart';
|
||||
import '../widgets/swipe_action.dart';
|
||||
import 'timecard_screens.dart';
|
||||
|
||||
/// Clock in and out. The button unlocks 30 minutes before the shift starts,
|
||||
@@ -122,7 +124,9 @@ class _ClockScreenState extends ConsumerState<ClockScreen> {
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 28),
|
||||
sliver: SliverList.list(
|
||||
children: timecardItems(ref.watch(timecardsProvider)),
|
||||
children: Entrance.stagger(
|
||||
timecardItems(ref.watch(timecardsProvider)),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (shift == null)
|
||||
@@ -142,7 +146,7 @@ class _ClockScreenState extends ConsumerState<ClockScreen> {
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 28, 20, 24),
|
||||
sliver: SliverList.list(
|
||||
children: [
|
||||
children: Entrance.stagger([
|
||||
_ClockDial(shift: shift, atVenue: _atVenue),
|
||||
const SizedBox(height: 22),
|
||||
if (shift.status == ShiftStatus.active)
|
||||
@@ -155,16 +159,18 @@ class _ClockScreenState extends ConsumerState<ClockScreen> {
|
||||
label: const Text('Clock out'),
|
||||
)
|
||||
else
|
||||
FilledButton.icon(
|
||||
onPressed: _canClockIn(shift, locationGranted)
|
||||
? () => _clockIn(shift)
|
||||
: null,
|
||||
icon: const Icon(LucideIcons.logIn, size: 18),
|
||||
label: Text(
|
||||
_canClockIn(shift, locationGranted)
|
||||
? 'Clock in'
|
||||
: 'Clock in · ${Fmt.untilStart(shift.start)}',
|
||||
),
|
||||
SwipeAction(
|
||||
// The thumb is a handle, so its icon says how to
|
||||
// work the control, not what the control does - the
|
||||
// label already says 'clock in', and the check on
|
||||
// commit carries the outcome.
|
||||
icon: LucideIcons.arrowRight,
|
||||
enabled: _canClockIn(shift, locationGranted),
|
||||
label: _canClockIn(shift, locationGranted)
|
||||
? 'Swipe to clock in'
|
||||
: 'Clock in · ${Fmt.untilStart(shift.start)}',
|
||||
confirmedLabel: 'Clocked in',
|
||||
onConfirm: () => _clockIn(shift),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_GeofenceCard(
|
||||
@@ -178,7 +184,7 @@ class _ClockScreenState extends ConsumerState<ClockScreen> {
|
||||
shift: shift,
|
||||
onTap: () => context.push('/shift/${shift.id}'),
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -195,8 +201,8 @@ class _ClockScreenState extends ConsumerState<ClockScreen> {
|
||||
|
||||
void _clockIn(Shift shift) {
|
||||
// Starting and ending a shift are the two moments that matter most in the
|
||||
// app; both get a firm confirmation you can feel without looking.
|
||||
HapticFeedback.mediumImpact();
|
||||
// app; both get a firm confirmation you can feel without looking. Clocking
|
||||
// in gets its haptic from SwipeAction, at the instant the swipe commits.
|
||||
ref.read(shiftsProvider.notifier).clockIn(shift.id);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Clocked in at ${Fmt.time(DateTime.now())}')),
|
||||
|
||||
@@ -48,7 +48,7 @@ class ComplianceHubScreen extends ConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
HeaderIconButton(
|
||||
icon: LucideIcons.arrowLeft,
|
||||
icon: LucideIcons.chevronLeft,
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
StatusChip(
|
||||
|
||||
@@ -349,7 +349,7 @@ class _OnboardingScreenState extends ConsumerState<OnboardingScreen> {
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
color: KrowPalette.sage,
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: SafeArea(
|
||||
@@ -502,7 +502,7 @@ class WelcomeScreen extends StatelessWidget {
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: const BoxDecoration(
|
||||
color: KrowPalette.sage,
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
child: SafeArea(
|
||||
|
||||
@@ -11,6 +11,7 @@ import '../state/profile_controller.dart';
|
||||
import '../state/shifts_controller.dart';
|
||||
import '../widgets/collapsing_header.dart';
|
||||
import '../widgets/common.dart';
|
||||
import '../widgets/motion.dart';
|
||||
import '../widgets/glass_segments.dart';
|
||||
import '../widgets/krow_wordmark.dart';
|
||||
|
||||
@@ -127,7 +128,7 @@ class _PayScreenState extends ConsumerState<PayScreen> {
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 28),
|
||||
sliver: SliverList.list(
|
||||
children: [
|
||||
children: Entrance.stagger([
|
||||
KrowCard(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 14),
|
||||
child: Column(
|
||||
@@ -260,7 +261,7 @@ class _PayScreenState extends ConsumerState<PayScreen> {
|
||||
'Amounts are pre-tax. Your take-home differs after tax, '
|
||||
'deductions and withholding from your payroll provider.',
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -516,7 +517,7 @@ class PaymentDetailScreen extends ConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
HeaderIconButton(
|
||||
icon: LucideIcons.arrowLeft,
|
||||
icon: LucideIcons.chevronLeft,
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
const StatusChip('Pending', tone: ChipTone.yellow),
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../state/compliance_controller.dart';
|
||||
import '../state/profile_controller.dart';
|
||||
import '../widgets/collapsing_header.dart';
|
||||
import '../widgets/common.dart';
|
||||
import '../widgets/motion.dart';
|
||||
import '../widgets/krow_wordmark.dart';
|
||||
|
||||
class ProfileScreen extends ConsumerWidget {
|
||||
@@ -161,7 +162,7 @@ class ProfileScreen extends ConsumerWidget {
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 14, 20, 0),
|
||||
sliver: SliverList.list(
|
||||
children: [
|
||||
children: Entrance.stagger([
|
||||
StatRail(
|
||||
stats: [
|
||||
('Shifts', '${worker.shiftsWorked}'),
|
||||
@@ -323,7 +324,7 @@ class ProfileScreen extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -39,7 +39,7 @@ class ShiftDetailScreen extends ConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
HeaderIconButton(
|
||||
icon: LucideIcons.arrowLeft,
|
||||
icon: LucideIcons.chevronLeft,
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
HeaderIconButton(
|
||||
|
||||
@@ -9,6 +9,7 @@ import '../core/theme.dart';
|
||||
import '../models/shift.dart';
|
||||
import '../state/shifts_controller.dart';
|
||||
import '../widgets/common.dart';
|
||||
import '../widgets/motion.dart';
|
||||
import '../widgets/glass_segments.dart';
|
||||
import '../widgets/krow_wordmark.dart';
|
||||
import '../widgets/shift_card.dart';
|
||||
@@ -165,7 +166,7 @@ class _BookedTab extends ConsumerWidget {
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 24),
|
||||
children: [
|
||||
children: Entrance.stagger([
|
||||
WeekStrip(
|
||||
days: week,
|
||||
selected: selected,
|
||||
@@ -193,10 +194,10 @@ class _BookedTab extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const InfoBanner(
|
||||
'Auto-Match only offers you shifts inside the availability blocks you '
|
||||
'have switched on.',
|
||||
'Auto-Match only offers you shifts on the days you have marked '
|
||||
'yourself available.',
|
||||
),
|
||||
],
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -242,7 +243,7 @@ class _OpenTab extends ConsumerWidget {
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 24),
|
||||
children: [
|
||||
children: Entrance.stagger([
|
||||
TextField(
|
||||
onChanged: onQuery,
|
||||
decoration: const InputDecoration(
|
||||
@@ -292,7 +293,7 @@ class _OpenTab extends ConsumerWidget {
|
||||
trailingAction: _ApplyButton(shift: shift),
|
||||
),
|
||||
),
|
||||
],
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -360,7 +361,7 @@ class _HistoryTab extends ConsumerWidget {
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 24),
|
||||
children: [
|
||||
children: Entrance.stagger([
|
||||
StatRail(
|
||||
stats: [
|
||||
('Completed', '$completed'),
|
||||
@@ -385,7 +386,7 @@ class _HistoryTab extends ConsumerWidget {
|
||||
onTap: () => context.push('/shift/${shift.id}'),
|
||||
),
|
||||
),
|
||||
],
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import '../state/profile_controller.dart';
|
||||
import '../state/shifts_controller.dart';
|
||||
import '../widgets/collapsing_header.dart';
|
||||
import '../widgets/common.dart';
|
||||
import '../widgets/motion.dart';
|
||||
import '../widgets/shift_card.dart';
|
||||
import '../widgets/krow_wordmark.dart';
|
||||
|
||||
@@ -168,7 +169,7 @@ class TodayScreen extends ConsumerWidget {
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 24),
|
||||
sliver: SliverList.list(
|
||||
children: [
|
||||
children: Entrance.stagger([
|
||||
if (!compliant) ...[
|
||||
const SizedBox(height: 16),
|
||||
KrowCard(
|
||||
@@ -276,7 +277,7 @@ class TodayScreen extends ConsumerWidget {
|
||||
),
|
||||
if (open.isEmpty)
|
||||
const InfoBanner(
|
||||
'No open shifts right now. Turn on more availability blocks '
|
||||
'No open shifts right now. Mark more days available '
|
||||
'and we will match you to more work.',
|
||||
)
|
||||
else
|
||||
@@ -289,7 +290,7 @@ class TodayScreen extends ConsumerWidget {
|
||||
onTap: () => context.push('/shift/${shift.id}'),
|
||||
),
|
||||
),
|
||||
],
|
||||
]),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user