import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:image_picker/image_picker.dart'; import '../api_service.dart'; import '../theme.dart'; class ClockScreen extends StatefulWidget { final ApiService api; final VoidCallback onAuthError; const ClockScreen({super.key, required this.api, required this.onAuthError}); @override State createState() => _ClockScreenState(); } class _ClockScreenState extends State { Today? _today; bool _loading = true; bool _busy = false; String? _selfie; @override void initState() { super.initState(); _load(); } Future _load() async { try { final t = await widget.api.today(); if (mounted) setState(() { _today = t; _loading = false; }); } on ApiException catch (e) { if (e.status == 401) return widget.onAuthError(); if (mounted) setState(() => _loading = false); } } Future _position() async { if (!await Geolocator.isLocationServiceEnabled()) { throw ApiException('Turn on location services to check in'); } var perm = await Geolocator.checkPermission(); if (perm == LocationPermission.denied) perm = await Geolocator.requestPermission(); if (perm == LocationPermission.denied || perm == LocationPermission.deniedForever) { throw ApiException('Location permission is required to check in'); } return Geolocator.getCurrentPosition(); } Future _selfieCapture() async { try { final XFile? x = await ImagePicker().pickImage( source: ImageSource.camera, preferredCameraDevice: CameraDevice.front, maxWidth: 640, imageQuality: 55); if (x != null) { final b = await x.readAsBytes(); setState(() => _selfie = 'data:image/jpeg;base64,${base64Encode(b)}'); } } catch (_) { _snack('Could not open camera'); } } Future _punch(bool checkIn) async { setState(() => _busy = true); try { final pos = await _position(); if (checkIn) { await widget.api.checkIn(lat: pos.latitude, lng: pos.longitude, selfie: _selfie); } else { await widget.api.checkOut(lat: pos.latitude, lng: pos.longitude, selfie: _selfie); } _selfie = null; await _load(); _snack(checkIn ? 'Checked in' : 'Checked out'); } on ApiException catch (e) { if (e.status == 401) return widget.onAuthError(); _snack(e.message); } finally { if (mounted) setState(() => _busy = false); } } void _snack(String m) { if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(m))); } @override Widget build(BuildContext context) { if (_loading) return const Center(child: CircularProgressIndicator(color: AppColors.emerald)); final t = _today; final checkedIn = t?.checkedIn ?? false; return RefreshIndicator( onRefresh: _load, color: AppColors.emerald, child: ListView( padding: const EdgeInsets.all(20), children: [ const SizedBox(height: 8), Center( child: Container( width: 150, height: 150, decoration: BoxDecoration( shape: BoxShape.circle, color: (checkedIn ? AppColors.emerald : AppColors.muted).withValues(alpha: 0.10), border: Border.all(color: (checkedIn ? AppColors.emerald : AppColors.border), width: 3), ), child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(checkedIn ? Icons.work_history : Icons.co_present, color: checkedIn ? AppColors.emerald : AppColors.muted, size: 40), const SizedBox(height: 6), Text(checkedIn ? 'Working' : (t?.checkOut != null ? 'Done' : 'Not in'), style: TextStyle(fontWeight: FontWeight.bold, color: checkedIn ? AppColors.emerald : AppColors.navy)), ]), ), ), const SizedBox(height: 20), Card( child: Padding( padding: const EdgeInsets.all(16), child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _stat('In', t?.checkIn ?? '—'), _stat('Out', t?.checkOut ?? '—'), _stat('Hours', t?.hours == null ? '—' : '${t!.hours}'), ]), ), ), const SizedBox(height: 12), if (t?.lateMinutes != null && t!.lateMinutes! > 0) Center(child: Text('Late by ${t.lateMinutes} min', style: const TextStyle(color: AppColors.danger, fontSize: 12))), const SizedBox(height: 20), // selfie row Row(children: [ GestureDetector( onTap: _selfieCapture, child: Container( width: 54, height: 54, decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.border), image: _selfie != null ? DecorationImage(image: MemoryImage(base64Decode(_selfie!.split(',').last)), fit: BoxFit.cover) : null, ), child: _selfie == null ? const Icon(Icons.camera_alt_outlined, color: AppColors.muted) : null, ), ), const SizedBox(width: 12), Expanded( child: Text(_selfie == null ? 'Tap to add a selfie (if your office requires one)' : 'Selfie attached', style: const TextStyle(fontSize: 12, color: AppColors.muted)), ), ]), const SizedBox(height: 20), FilledButton.icon( onPressed: _busy ? null : () => _punch(!checkedIn), style: FilledButton.styleFrom(backgroundColor: checkedIn ? AppColors.danger : AppColors.emerald), icon: _busy ? const SizedBox(height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white)) : Icon(checkedIn ? Icons.logout : Icons.login), label: Text(_busy ? 'Please wait…' : (checkedIn ? 'Check out' : 'Check in')), ), const SizedBox(height: 10), const Center(child: Text('Your location is captured to confirm office presence.', textAlign: TextAlign.center, style: TextStyle(fontSize: 11, color: AppColors.muted))), ], ), ); } Widget _stat(String label, String value) { return Column(children: [ Text(value, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.navy)), Text(label, style: const TextStyle(fontSize: 11, color: AppColors.muted)), ]); } }