feat: enhance UI, branding assets, onboarding flow, and screen layouts

This commit is contained in:
R-Bharathraj
2026-09-18 15:23:30 +05:30
parent cafffd27e0
commit 5122eebc11
68 changed files with 1661 additions and 470 deletions

View File

@@ -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'),
),
],
),
),