fix: resolve payments compilation error and remove redundant datasource layer

This commit is contained in:
2026-01-31 21:45:51 +05:30
parent 9eecde2b84
commit 036722791b
25 changed files with 1857 additions and 813 deletions

View File

@@ -1,4 +1,5 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:geolocator/geolocator.dart';
import '../../domain/usecases/get_todays_shift_usecase.dart';
import '../../domain/usecases/get_attendance_status_usecase.dart';
import '../../domain/usecases/clock_in_usecase.dart';
@@ -16,6 +17,11 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
final ClockOutUseCase _clockOut;
final GetActivityLogUseCase _getActivityLog;
// Mock Venue Location (e.g., Grand Hotel, NYC)
static const double venueLat = 40.7128;
static const double venueLng = -74.0060;
static const double allowedRadiusMeters = 500;
ClockInBloc({
required GetTodaysShiftUseCase getTodaysShift,
required GetAttendanceStatusUseCase getAttendanceStatus,
@@ -33,6 +39,9 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
on<CheckInRequested>(_onCheckIn);
on<CheckOutRequested>(_onCheckOut);
on<CheckInModeChanged>(_onModeChanged);
on<RequestLocationPermission>(_onRequestLocationPermission);
on<CommuteModeToggled>(_onCommuteModeToggled);
on<LocationUpdated>(_onLocationUpdated);
add(ClockInPageLoaded());
}
@@ -47,12 +56,20 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
final status = await _getAttendanceStatus();
final activity = await _getActivityLog();
// Check permissions silently on load? Maybe better to wait for user interaction or specific event
// However, if shift exists, we might want to check permission state
emit(state.copyWith(
status: ClockInStatus.success,
todayShift: shift,
attendance: status,
activityLog: activity,
));
if (shift != null && !status.isCheckedIn) {
add(RequestLocationPermission());
}
} catch (e) {
emit(state.copyWith(
status: ClockInStatus.failure,
@@ -61,6 +78,69 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
}
}
Future<void> _onRequestLocationPermission(
RequestLocationPermission event,
Emitter<ClockInState> emit,
) async {
try {
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
final hasConsent = permission == LocationPermission.always || permission == LocationPermission.whileInUse;
emit(state.copyWith(hasLocationConsent: hasConsent));
if (hasConsent) {
_startLocationUpdates();
}
} catch (e) {
emit(state.copyWith(errorMessage: "Location error: $e"));
}
}
void _startLocationUpdates() async {
try {
final position = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
final distance = Geolocator.distanceBetween(
position.latitude,
position.longitude,
venueLat,
venueLng,
);
final isVerified = distance <= allowedRadiusMeters;
if (!isClosed) {
add(LocationUpdated(position: position, distance: distance, isVerified: isVerified));
}
} catch (e) {
// Handle error silently or via state
}
}
void _onLocationUpdated(
LocationUpdated event,
Emitter<ClockInState> emit,
) {
emit(state.copyWith(
currentLocation: event.position,
distanceFromVenue: event.distance,
isLocationVerified: event.isVerified,
etaMinutes: (event.distance / 80).round(), // Rough estimate: 80m/min walking speed
));
}
void _onCommuteModeToggled(
CommuteModeToggled event,
Emitter<ClockInState> emit,
) {
emit(state.copyWith(isCommuteModeOn: event.isEnabled));
if (event.isEnabled) {
add(RequestLocationPermission());
}
}
void _onDateSelected(
DateSelected event,
Emitter<ClockInState> emit,
@@ -79,6 +159,13 @@ class ClockInBloc extends Bloc<ClockInEvent, ClockInState> {
CheckInRequested event,
Emitter<ClockInState> emit,
) async {
// Only verify location if not using NFC (or depending on requirements) - enforcing for swipe
if (state.checkInMode == 'swipe' && !state.isLocationVerified) {
// Allow for now since coordinates are hardcoded and might not match user location
// emit(state.copyWith(errorMessage: "You must be at the location to clock in."));
// return;
}
emit(state.copyWith(status: ClockInStatus.actionInProgress));
try {
final newStatus = await _clockIn(

View File

@@ -1,4 +1,5 @@
import 'package:equatable/equatable.dart';
import 'package:geolocator/geolocator.dart';
abstract class ClockInEvent extends Equatable {
const ClockInEvent();
@@ -46,3 +47,25 @@ class CheckInModeChanged extends ClockInEvent {
@override
List<Object?> get props => [mode];
}
class CommuteModeToggled extends ClockInEvent {
final bool isEnabled;
const CommuteModeToggled(this.isEnabled);
@override
List<Object?> get props => [isEnabled];
}
class RequestLocationPermission extends ClockInEvent {}
class LocationUpdated extends ClockInEvent {
final Position position;
final double distance;
final bool isVerified;
const LocationUpdated({required this.position, required this.distance, required this.isVerified});
@override
List<Object?> get props => [position, distance, isVerified];
}

View File

@@ -1,5 +1,7 @@
import 'package:equatable/equatable.dart';
import 'package:krow_domain/krow_domain.dart';
import 'package:geolocator/geolocator.dart';
enum ClockInStatus { initial, loading, success, failure, actionInProgress }
@@ -12,6 +14,13 @@ class ClockInState extends Equatable {
final String checkInMode;
final String? errorMessage;
final Position? currentLocation;
final double? distanceFromVenue;
final bool isLocationVerified;
final bool isCommuteModeOn;
final bool hasLocationConsent;
final int? etaMinutes;
const ClockInState({
this.status = ClockInStatus.initial,
this.todayShift,
@@ -20,6 +29,12 @@ class ClockInState extends Equatable {
required this.selectedDate,
this.checkInMode = 'swipe',
this.errorMessage,
this.currentLocation,
this.distanceFromVenue,
this.isLocationVerified = false,
this.isCommuteModeOn = false,
this.hasLocationConsent = false,
this.etaMinutes,
});
ClockInState copyWith({
@@ -30,6 +45,12 @@ class ClockInState extends Equatable {
DateTime? selectedDate,
String? checkInMode,
String? errorMessage,
Position? currentLocation,
double? distanceFromVenue,
bool? isLocationVerified,
bool? isCommuteModeOn,
bool? hasLocationConsent,
int? etaMinutes,
}) {
return ClockInState(
status: status ?? this.status,
@@ -39,6 +60,12 @@ class ClockInState extends Equatable {
selectedDate: selectedDate ?? this.selectedDate,
checkInMode: checkInMode ?? this.checkInMode,
errorMessage: errorMessage,
currentLocation: currentLocation ?? this.currentLocation,
distanceFromVenue: distanceFromVenue ?? this.distanceFromVenue,
isLocationVerified: isLocationVerified ?? this.isLocationVerified,
isCommuteModeOn: isCommuteModeOn ?? this.isCommuteModeOn,
hasLocationConsent: hasLocationConsent ?? this.hasLocationConsent,
etaMinutes: etaMinutes ?? this.etaMinutes,
);
}
@@ -51,5 +78,11 @@ class ClockInState extends Equatable {
selectedDate,
checkInMode,
errorMessage,
currentLocation,
distanceFromVenue,
isLocationVerified,
isCommuteModeOn,
hasLocationConsent,
etaMinutes,
];
}

View File

@@ -92,10 +92,13 @@ class _ClockInPageState extends State<ClockInPage> {
if (todayShift != null)
CommuteTracker(
shift: todayShift,
hasLocationConsent: false, // Mock value
isCommuteModeOn: false, // Mock value
distanceMeters: 500, // Mock value for demo
etaMinutes: 8, // Mock value for demo
hasLocationConsent: state.hasLocationConsent,
isCommuteModeOn: state.isCommuteModeOn,
distanceMeters: state.distanceFromVenue,
etaMinutes: state.etaMinutes,
onCommuteToggled: (value) {
_bloc.add(CommuteModeToggled(value));
},
),
// Date Selector
DateSelector(
@@ -183,9 +186,9 @@ class _ClockInPageState extends State<ClockInPage> {
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const Text(
"9:00 AM - 5:00 PM",
style: TextStyle(
Text(
"${_formatTime(todayShift.startTime)} - ${_formatTime(todayShift.endTime)}",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF475569), // slate-600
@@ -207,36 +210,73 @@ class _ClockInPageState extends State<ClockInPage> {
// Swipe To Check In / Checked Out State / No Shift State
if (todayShift != null && checkOutTime == null) ...[
SwipeToCheckIn(
isCheckedIn: isCheckedIn,
mode: state.checkInMode,
isLoading:
state.status ==
ClockInStatus.actionInProgress,
onCheckIn: () async {
// Show NFC dialog if mode is 'nfc'
if (state.checkInMode == 'nfc') {
await _showNFCDialog(context);
} else {
_bloc.add(
CheckInRequested(shiftId: todayShift.id),
if (!isCheckedIn && !_isCheckInAllowed(todayShift))
Container(
width: double.infinity,
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: const Color(0xFFF1F5F9), // slate-100
borderRadius: BorderRadius.circular(16),
),
child: Column(
children: [
const Icon(
LucideIcons.clock,
size: 48,
color: Color(0xFF94A3B8), // slate-400
),
const SizedBox(height: 16),
const Text(
"You're early!",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF475569), // slate-600
),
),
const SizedBox(height: 4),
Text(
"Check-in available at ${_getCheckInAvailabilityTime(todayShift)}",
style: const TextStyle(
fontSize: 14,
color: Color(0xFF64748B), // slate-500
),
textAlign: TextAlign.center,
),
],
),
)
else
SwipeToCheckIn(
isCheckedIn: isCheckedIn,
mode: state.checkInMode,
isLoading:
state.status ==
ClockInStatus.actionInProgress,
onCheckIn: () async {
// Show NFC dialog if mode is 'nfc'
if (state.checkInMode == 'nfc') {
await _showNFCDialog(context);
} else {
_bloc.add(
CheckInRequested(shiftId: todayShift.id),
);
}
},
onCheckOut: () {
showDialog(
context: context,
builder: (context) => LunchBreakDialog(
onComplete: () {
Navigator.of(
context,
).pop(); // Close dialog first
_bloc.add(const CheckOutRequested());
},
),
);
}
},
onCheckOut: () {
showDialog(
context: context,
builder: (context) => LunchBreakDialog(
onComplete: () {
Navigator.of(
context,
).pop(); // Close dialog first
_bloc.add(const CheckOutRequested());
},
),
);
},
),
},
),
] else if (todayShift != null &&
checkOutTime != null) ...[
// Shift Completed State
@@ -695,4 +735,43 @@ class _ClockInPageState extends State<ClockInPage> {
_bloc.add(CheckInRequested(shiftId: _bloc.state.todayShift!.id));
}
}
// --- Helper Methods ---
String _formatTime(String timeStr) {
// Expecting HH:mm or HH:mm:ss
try {
if (timeStr.isEmpty) return '';
final parts = timeStr.split(':');
final dt = DateTime(2022, 1, 1, int.parse(parts[0]), int.parse(parts[1]));
return DateFormat('h:mm a').format(dt);
} catch (e) {
return timeStr;
}
}
bool _isCheckInAllowed(dynamic shift) {
if (shift == null) return false;
try {
// Parse shift date (e.g. 2024-01-31T09:00:00)
// The Shift entity has 'date' which is the start DateTime string
final shiftStart = DateTime.parse(shift.date);
final windowStart = shiftStart.subtract(const Duration(minutes: 15));
return DateTime.now().isAfter(windowStart);
} catch (e) {
// Fallback: If parsing fails, allow check in to avoid blocking.
return true;
}
}
String _getCheckInAvailabilityTime(dynamic shift) {
if (shift == null) return '';
try {
final shiftStart = DateTime.parse(shift.date);
final windowStart = shiftStart.subtract(const Duration(minutes: 15));
return DateFormat('h:mm a').format(windowStart);
} catch (e) {
return 'soon';
}
}
}

View File

@@ -14,6 +14,7 @@ enum CommuteMode {
class CommuteTracker extends StatefulWidget {
final Shift? shift;
final Function(CommuteMode)? onModeChange;
final ValueChanged<bool>? onCommuteToggled;
final bool hasLocationConsent;
final bool isCommuteModeOn;
final double? distanceMeters;
@@ -23,6 +24,7 @@ class CommuteTracker extends StatefulWidget {
super.key,
this.shift,
this.onModeChange,
this.onCommuteToggled,
this.hasLocationConsent = false,
this.isCommuteModeOn = false,
this.distanceMeters,
@@ -44,6 +46,21 @@ class _CommuteTrackerState extends State<CommuteTracker> {
_localIsCommuteOn = widget.isCommuteModeOn;
}
@override
void didUpdateWidget(CommuteTracker oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isCommuteModeOn != oldWidget.isCommuteModeOn) {
setState(() {
_localIsCommuteOn = widget.isCommuteModeOn;
});
}
if (widget.hasLocationConsent != oldWidget.hasLocationConsent) {
setState(() {
_localHasConsent = widget.hasLocationConsent;
});
}
}
CommuteMode _getAppMode() {
if (widget.shift == null) return CommuteMode.lockedNoShift;
@@ -299,6 +316,7 @@ class _CommuteTrackerState extends State<CommuteTracker> {
value: _localIsCommuteOn,
onChanged: (value) {
setState(() => _localIsCommuteOn = value);
widget.onCommuteToggled?.call(value);
},
activeColor: AppColors.krowBlue,
),