feat: enhance UI, branding assets, onboarding flow, and screen layouts
This commit is contained in:
@@ -11,9 +11,13 @@ import '../core/theme.dart';
|
||||
import '../models/shift.dart';
|
||||
import '../state/session_controller.dart';
|
||||
import '../state/shifts_controller.dart';
|
||||
import '../state/timecards_controller.dart';
|
||||
import '../widgets/collapsing_header.dart';
|
||||
import '../widgets/common.dart';
|
||||
import '../widgets/glass_segments.dart';
|
||||
import '../widgets/krow_wordmark.dart';
|
||||
import '../widgets/shift_card.dart';
|
||||
import 'timecard_screens.dart';
|
||||
|
||||
/// Clock in and out. The button unlocks 30 minutes before the shift starts,
|
||||
/// and only once the worker is at the venue.
|
||||
@@ -24,7 +28,11 @@ class ClockScreen extends ConsumerStatefulWidget {
|
||||
ConsumerState<ClockScreen> createState() => _ClockScreenState();
|
||||
}
|
||||
|
||||
/// The two halves of the Clock tab, as the prototype splits them.
|
||||
enum _ClockTab { today, timecards }
|
||||
|
||||
class _ClockScreenState extends ConsumerState<ClockScreen> {
|
||||
_ClockTab _tab = _ClockTab.today;
|
||||
Timer? _ticker;
|
||||
|
||||
/// Stands in for the geofence check while there is no GPS plugin wired up.
|
||||
@@ -66,56 +74,58 @@ class _ClockScreenState extends ConsumerState<ClockScreen> {
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
// Already compact, so it pins at one height rather than collapsing.
|
||||
// Sized with headroom over the 58px of content, so a larger system
|
||||
// text scale does not push it over.
|
||||
// Pins at one height: the two segments have to stay reachable, so
|
||||
// there is nothing worth collapsing away. Sized with headroom for a
|
||||
// large system text scale, which the eyebrow and title both grow with.
|
||||
KrowSliverHeader(
|
||||
expandedHeight: 96,
|
||||
collapsedHeight: 96,
|
||||
expandedHeight: 170,
|
||||
collapsedHeight: 170,
|
||||
builder: (context, t) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
Fmt.dayFull(DateTime.now()).toUpperCase(),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 9.5,
|
||||
letterSpacing: 1.5,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'Clock in',
|
||||
style: TextStyle(
|
||||
fontSize: 27,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.9,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
HeaderBrandLine(
|
||||
// The caption reports whichever half you are looking at.
|
||||
trailing: HeaderEyebrow(
|
||||
_tab == _ClockTab.today
|
||||
? Fmt.dayFull(DateTime.now())
|
||||
: '${Fmt.monthYear(DateTime.now())} · '
|
||||
'${Fmt.duration(ref.watch(hoursLoggedProvider))} logged',
|
||||
),
|
||||
),
|
||||
HeaderIconButton(
|
||||
icon: LucideIcons.fileText,
|
||||
tooltip: 'Timecards',
|
||||
onPressed: () => context.push('/timecards'),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'Clock in',
|
||||
style: TextStyle(
|
||||
fontSize: 27,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: -0.9,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
GlassSegments<_ClockTab>(
|
||||
segments: const [
|
||||
(_ClockTab.today, 'Today'),
|
||||
(_ClockTab.timecards, 'Timecards'),
|
||||
],
|
||||
value: _tab,
|
||||
onChanged: (v) => setState(() => _tab = v),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (shift == null)
|
||||
if (_tab == _ClockTab.timecards)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 18, 20, 28),
|
||||
sliver: SliverList.list(
|
||||
children: timecardItems(ref.watch(timecardsProvider)),
|
||||
),
|
||||
)
|
||||
else if (shift == null)
|
||||
const SliverPadding(
|
||||
padding: EdgeInsets.fromLTRB(20, 40, 20, 20),
|
||||
sliver: SliverToBoxAdapter(
|
||||
@@ -168,12 +178,6 @@ class _ClockScreenState extends ConsumerState<ClockScreen> {
|
||||
shift: shift,
|
||||
onTap: () => context.push('/shift/${shift.id}'),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => context.push('/timecards'),
|
||||
icon: const Icon(LucideIcons.fileText, size: 18),
|
||||
label: const Text('View timecards'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user