chore: fix 273+ analysis issues and repair corrupted core files
This commit is contained in:
@@ -9,7 +9,6 @@ import 'package:krow_domain/krow_domain.dart'
|
||||
AppException,
|
||||
BaseApiService,
|
||||
ClientSession,
|
||||
NetworkException,
|
||||
PasswordMismatchException,
|
||||
SignInFailedException,
|
||||
SignUpFailedException,
|
||||
@@ -111,19 +110,11 @@ class AuthRepositoryImpl implements AuthRepositoryInterface {
|
||||
} on AppException {
|
||||
rethrow;
|
||||
} catch (e) {
|
||||
// Map common Firebase-originated errors from the V2 API response
|
||||
// to domain exceptions.
|
||||
if (e is AppException) rethrow;
|
||||
|
||||
// Extract error code if available from the API response
|
||||
final String errorMessage = e.toString();
|
||||
if (errorMessage.contains('EMAIL_EXISTS') ||
|
||||
errorMessage.contains('email-already-in-use')) {
|
||||
throw AccountExistsException(technicalMessage: errorMessage);
|
||||
} else if (errorMessage.contains('WEAK_PASSWORD') ||
|
||||
errorMessage.contains('weak-password')) {
|
||||
throw WeakPasswordException(technicalMessage: errorMessage);
|
||||
} else if (errorMessage.contains('network-request-failed')) {
|
||||
throw NetworkException(technicalMessage: errorMessage);
|
||||
}
|
||||
throw SignUpFailedException(technicalMessage: 'Unexpected error: $e');
|
||||
_throwSignUpError('SIGN_UP_ERROR', errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
class ClientIntroPage extends StatelessWidget {
|
||||
class ClientIntroPage extends StatefulWidget {
|
||||
const ClientIntroPage({super.key});
|
||||
|
||||
@override
|
||||
State<ClientIntroPage> createState() => _ClientIntroPageState();
|
||||
}
|
||||
|
||||
class _ClientIntroPageState extends State<ClientIntroPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future<void>.delayed(const Duration(seconds: 2), () {
|
||||
if (mounted && Modular.to.path == ClientPaths.root) {
|
||||
Modular.to.toClientGetStartedPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
|
||||
@@ -11,14 +11,14 @@ import 'package:client_home/src/presentation/blocs/client_home_state.dart';
|
||||
///
|
||||
/// Shows an error message with a retry button when data fails to load.
|
||||
class ClientHomeErrorState extends StatelessWidget {
|
||||
/// The current home state containing error information.
|
||||
final ClientHomeState state;
|
||||
|
||||
/// Creates a [ClientHomeErrorState].
|
||||
const ClientHomeErrorState({
|
||||
required this.state,
|
||||
super.key,
|
||||
});
|
||||
/// The current home state containing error information.
|
||||
final ClientHomeState state;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -8,14 +8,14 @@ import 'package:client_home/src/presentation/widgets/dashboard_widget_builder.da
|
||||
///
|
||||
/// Shows visible dashboard widgets in a vertical scrollable list with dividers.
|
||||
class ClientHomeNormalModeBody extends StatelessWidget {
|
||||
/// The current home state.
|
||||
final ClientHomeState state;
|
||||
|
||||
/// Creates a [ClientHomeNormalModeBody].
|
||||
const ClientHomeNormalModeBody({
|
||||
required this.state,
|
||||
super.key,
|
||||
});
|
||||
/// The current home state.
|
||||
final ClientHomeState state;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,7 @@ class CoverageBloc extends Bloc<CoverageEvent, CoverageState>
|
||||
Emitter<CoverageState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
emit(CoverageLoading());
|
||||
final CoverageReport report = await _getCoverageReportUseCase.call(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Base state for the coverage report BLoC.
|
||||
|
||||
@@ -24,7 +24,7 @@ class DailyOpsBloc extends Bloc<DailyOpsEvent, DailyOpsState>
|
||||
Emitter<DailyOpsState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
emit(DailyOpsLoading());
|
||||
final DailyOpsReport report = await _getDailyOpsReportUseCase.call(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Base state for the daily operations report BLoC.
|
||||
|
||||
@@ -24,7 +24,7 @@ class ForecastBloc extends Bloc<ForecastEvent, ForecastState>
|
||||
Emitter<ForecastState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
emit(ForecastLoading());
|
||||
final ForecastReport report = await _getForecastReportUseCase.call(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Base state for the forecast report BLoC.
|
||||
|
||||
@@ -24,7 +24,7 @@ class NoShowBloc extends Bloc<NoShowEvent, NoShowState>
|
||||
Emitter<NoShowState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
emit(NoShowLoading());
|
||||
final NoShowReport report = await _getNoShowReportUseCase.call(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Base state for the no-show report BLoC.
|
||||
|
||||
@@ -25,7 +25,7 @@ class PerformanceBloc extends Bloc<PerformanceEvent, PerformanceState>
|
||||
Emitter<PerformanceState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
emit(PerformanceLoading());
|
||||
final PerformanceReport report = await _getPerformanceReportUseCase.call(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Base state for the performance report BLoC.
|
||||
|
||||
@@ -24,7 +24,7 @@ class SpendBloc extends Bloc<SpendEvent, SpendState>
|
||||
Emitter<SpendState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
emit(SpendLoading());
|
||||
final SpendReport report = await _getSpendReportUseCase.call(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
|
||||
/// Base state for the spend report BLoC.
|
||||
|
||||
@@ -26,7 +26,7 @@ class ReportsSummaryBloc
|
||||
Emitter<ReportsSummaryState> emit,
|
||||
) async {
|
||||
await handleError(
|
||||
emit: emit,
|
||||
emit: emit.call,
|
||||
action: () async {
|
||||
emit(ReportsSummaryLoading());
|
||||
final ReportSummary summary = await _getReportsSummaryUseCase.call(
|
||||
|
||||
@@ -71,7 +71,7 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white.withOpacity(0.2),
|
||||
color: UiColors.white.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -95,7 +95,7 @@ class _CoverageReportPageState extends State<CoverageReportPage> {
|
||||
context.t.client_reports.coverage_report
|
||||
.subtitle,
|
||||
style: UiTypography.body3r.copyWith(
|
||||
color: UiColors.white.withOpacity(0.7),
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -203,7 +203,7 @@ class _CoverageSummaryCard extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
),
|
||||
],
|
||||
@@ -214,7 +214,7 @@ class _CoverageSummaryCard extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
color: color.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(icon, size: 16, color: color),
|
||||
|
||||
@@ -101,7 +101,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white.withOpacity(0.2),
|
||||
color: UiColors.white.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -126,7 +126,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
context.t.client_reports.daily_ops_report
|
||||
.subtitle,
|
||||
style: UiTypography.body3r.copyWith(
|
||||
color: UiColors.white.withOpacity(0.7),
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -155,7 +155,7 @@ class _DailyOpsReportPageState extends State<DailyOpsReportPage> {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
color: UiColors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 4,
|
||||
),
|
||||
],
|
||||
@@ -390,7 +390,7 @@ class _OpsStatCard extends StatelessWidget {
|
||||
vertical: 3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.12),
|
||||
color: color.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
@@ -451,7 +451,7 @@ class _ShiftListItem extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.02),
|
||||
color: UiColors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 2,
|
||||
),
|
||||
],
|
||||
@@ -497,7 +497,7 @@ class _ShiftListItem extends StatelessWidget {
|
||||
margin: const EdgeInsets.only(left: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor.withOpacity(0.1),
|
||||
color: statusColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
|
||||
@@ -119,7 +119,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white.withOpacity(0.2),
|
||||
color: UiColors.white.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -141,7 +141,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
Text(
|
||||
context.t.client_reports.forecast_report.subtitle,
|
||||
style: UiTypography.body2m.copyWith(
|
||||
color: UiColors.white.withOpacity(0.7),
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -213,7 +213,7 @@ class _ForecastReportPageState extends State<ForecastReportPage> {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
),
|
||||
],
|
||||
@@ -289,7 +289,7 @@ class _MetricCard extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 8,
|
||||
),
|
||||
],
|
||||
@@ -469,7 +469,7 @@ class _ForecastChart extends StatelessWidget {
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
color: UiColors.tagPending.withOpacity(0.5),
|
||||
color: UiColors.tagPending.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -74,7 +74,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white.withOpacity(0.15),
|
||||
color: UiColors.white.withValues(alpha: 0.15),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -97,7 +97,7 @@ class _NoShowReportPageState extends State<NoShowReportPage> {
|
||||
Text(
|
||||
context.t.client_reports.no_show_report.subtitle,
|
||||
style: UiTypography.body3r.copyWith(
|
||||
color: UiColors.white.withOpacity(0.6),
|
||||
color: UiColors.white.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -225,7 +225,7 @@ class _SummaryChip extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
color: UiColors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
@@ -302,7 +302,7 @@ class _WorkerCard extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 6,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -157,7 +157,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white.withOpacity(0.2),
|
||||
color: UiColors.white.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -182,7 +182,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
context.t.client_reports.performance_report
|
||||
.subtitle,
|
||||
style: UiTypography.body3r.copyWith(
|
||||
color: UiColors.white.withOpacity(0.7),
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -212,7 +212,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
@@ -270,7 +270,7 @@ class _PerformanceReportPageState extends State<PerformanceReportPage> {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
),
|
||||
],
|
||||
@@ -387,7 +387,7 @@ class _KpiRow extends StatelessWidget {
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: kpi.iconColor.withOpacity(0.1),
|
||||
color: kpi.iconColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(kpi.icon, size: 18, color: kpi.iconColor),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_bloc.dart';
|
||||
import 'package:client_reports/src/presentation/blocs/summary/reports_summary_event.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -81,7 +81,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white.withOpacity(0.2),
|
||||
color: UiColors.white.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -104,7 +104,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
Text(
|
||||
context.t.client_reports.spend_report.subtitle,
|
||||
style: UiTypography.body3r.copyWith(
|
||||
color: UiColors.white.withOpacity(0.7),
|
||||
color: UiColors.white.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -164,7 +164,7 @@ class _SpendReportPageState extends State<SpendReportPage> {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
@@ -358,7 +358,7 @@ class _SpendStatCard extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.06),
|
||||
color: UiColors.black.withValues(alpha: 0.06),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
@@ -392,7 +392,7 @@ class _SpendStatCard extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: themeColor.withOpacity(0.1),
|
||||
color: themeColor.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
@@ -424,7 +424,7 @@ class _SpendByCategoryCard extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.04),
|
||||
color: UiColors.black.withValues(alpha: 0.04),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:client_reports/src/presentation/widgets/reports_page/report_card.dart';
|
||||
import 'package:client_reports/src/presentation/widgets/reports_page/report_card.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -44,7 +44,7 @@ class ReportCard extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: UiColors.black.withOpacity(0.02),
|
||||
color: UiColors.black.withValues(alpha: 0.02),
|
||||
blurRadius: 2,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -51,7 +51,7 @@ class ReportsHeader extends StatelessWidget {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white.withOpacity(0.2),
|
||||
color: UiColors.white.withValues(alpha: 0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
@@ -84,7 +84,7 @@ class ReportsHeader extends StatelessWidget {
|
||||
height: 44,
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.white.withOpacity(0.2),
|
||||
color: UiColors.white.withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: TabBar(
|
||||
|
||||
@@ -176,104 +176,4 @@ class _QuickLinkItem extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _NotificationsSettingsCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ClientSettingsBloc, ClientSettingsState>(
|
||||
builder: (BuildContext context, ClientSettingsState state) {
|
||||
return Card(
|
||||
elevation: 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: UiConstants.radiusLg,
|
||||
side: const BorderSide(color: UiColors.border),
|
||||
),
|
||||
color: UiColors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.client_settings.preferences.title,
|
||||
style: UiTypography.footnote1b.textPrimary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space2),
|
||||
_NotificationToggle(
|
||||
icon: UiIcons.bell,
|
||||
title: context.t.client_settings.preferences.push,
|
||||
value: state.pushEnabled,
|
||||
onChanged: (bool val) =>
|
||||
ReadContext(context).read<ClientSettingsBloc>().add(
|
||||
ClientSettingsNotificationToggled(
|
||||
type: 'push',
|
||||
isEnabled: val,
|
||||
),
|
||||
),
|
||||
),
|
||||
_NotificationToggle(
|
||||
icon: UiIcons.mail,
|
||||
title: context.t.client_settings.preferences.email,
|
||||
value: state.emailEnabled,
|
||||
onChanged: (bool val) =>
|
||||
ReadContext(context).read<ClientSettingsBloc>().add(
|
||||
ClientSettingsNotificationToggled(
|
||||
type: 'email',
|
||||
isEnabled: val,
|
||||
),
|
||||
),
|
||||
),
|
||||
_NotificationToggle(
|
||||
icon: UiIcons.phone,
|
||||
title: context.t.client_settings.preferences.sms,
|
||||
value: state.smsEnabled,
|
||||
onChanged: (bool val) =>
|
||||
ReadContext(context).read<ClientSettingsBloc>().add(
|
||||
ClientSettingsNotificationToggled(
|
||||
type: 'sms',
|
||||
isEnabled: val,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NotificationToggle extends StatelessWidget {
|
||||
|
||||
const _NotificationToggle({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final bool value;
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Icon(icon, size: 20, color: UiColors.iconSecondary),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
Text(title, style: UiTypography.footnote1m.textPrimary),
|
||||
],
|
||||
),
|
||||
Switch.adaptive(
|
||||
value: value,
|
||||
activeColor: UiColors.primary,
|
||||
onChanged: onChanged,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class SettingsProfileHeader extends StatelessWidget {
|
||||
final String businessName = session?.businessName ?? 'Your Company';
|
||||
final String email = session?.email ?? 'client@example.com';
|
||||
// V2 session does not include a photo URL; show letter avatar.
|
||||
final String? photoUrl = null;
|
||||
const String? photoUrl = null;
|
||||
final String avatarLetter = businessName.trim().isNotEmpty
|
||||
? businessName.trim()[0].toUpperCase()
|
||||
: 'C';
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
import 'dart:async';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
/// A simple introductory page that displays the KROW logo.
|
||||
class IntroPage extends StatelessWidget {
|
||||
/// A simple introductory page that displays the KROW logo and navigates
|
||||
/// to the get started page after a short delay.
|
||||
class IntroPage extends StatefulWidget {
|
||||
const IntroPage({super.key});
|
||||
|
||||
@override
|
||||
State<IntroPage> createState() => _IntroPageState();
|
||||
}
|
||||
|
||||
class _IntroPageState extends State<IntroPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Timer(const Duration(seconds: 2), () {
|
||||
if (Modular.to.path == StaffPaths.root) {
|
||||
Modular.to.toGetStartedPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -12,3 +31,4 @@ class IntroPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,12 +142,14 @@ class _PhoneVerificationPageState extends State<PhoneVerificationPage> {
|
||||
(state.status == AuthStatus.loading &&
|
||||
state.verificationId != null);
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(AuthResetRequested(mode: widget.mode));
|
||||
return true;
|
||||
return PopScope(
|
||||
canPop: true,
|
||||
onPopInvokedWithResult: (bool didPop, dynamic result) {
|
||||
if (didPop) {
|
||||
BlocProvider.of<AuthBloc>(
|
||||
context,
|
||||
).add(AuthResetRequested(mode: widget.mode));
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: UiAppBar(
|
||||
|
||||
@@ -13,8 +13,7 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Column(
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(height: UiConstants.space8),
|
||||
// Logo
|
||||
@@ -113,7 +112,6 @@ class _GetStartedBackgroundState extends State<GetStartedBackground> {
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:pinput/pinput.dart';
|
||||
@@ -50,7 +49,7 @@ class _OtpInputFieldState extends State<OtpInputField> {
|
||||
}
|
||||
|
||||
Future<void> _listenForSmsCode() async {
|
||||
final res = await _smartAuth.getSmsCode();
|
||||
final SmsCodeResult res = await _smartAuth.getSmsCode();
|
||||
if (res.code != null && mounted) {
|
||||
_controller.text = res.code!;
|
||||
_onChanged(_controller.text);
|
||||
@@ -68,7 +67,7 @@ class _OtpInputFieldState extends State<OtpInputField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final defaultPinTheme = PinTheme(
|
||||
final PinTheme defaultPinTheme = PinTheme(
|
||||
width: 45,
|
||||
height: 56,
|
||||
textStyle: UiTypography.headline3m,
|
||||
@@ -81,7 +80,7 @@ class _OtpInputFieldState extends State<OtpInputField> {
|
||||
),
|
||||
);
|
||||
|
||||
final focusedPinTheme = defaultPinTheme.copyWith(
|
||||
final PinTheme focusedPinTheme = defaultPinTheme.copyWith(
|
||||
decoration: defaultPinTheme.decoration!.copyWith(
|
||||
border: Border.all(
|
||||
color: widget.error.isNotEmpty ? UiColors.textError : UiColors.primary,
|
||||
@@ -90,7 +89,7 @@ class _OtpInputFieldState extends State<OtpInputField> {
|
||||
),
|
||||
);
|
||||
|
||||
final submittedPinTheme = defaultPinTheme.copyWith(
|
||||
final PinTheme submittedPinTheme = defaultPinTheme.copyWith(
|
||||
decoration: defaultPinTheme.decoration!.copyWith(
|
||||
border: Border.all(
|
||||
color: widget.error.isNotEmpty ? UiColors.textError : UiColors.primary,
|
||||
@@ -99,7 +98,7 @@ class _OtpInputFieldState extends State<OtpInputField> {
|
||||
),
|
||||
);
|
||||
|
||||
final errorPinTheme = defaultPinTheme.copyWith(
|
||||
final PinTheme errorPinTheme = defaultPinTheme.copyWith(
|
||||
decoration: defaultPinTheme.decoration!.copyWith(
|
||||
border: Border.all(color: UiColors.textError, width: 2),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports, unused_element, unused_field, duplicate_ignore
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports, unused_element, unused_field, duplicate_ignore
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -20,14 +20,14 @@ class PendingPaymentCard extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
UiColors.primary.withOpacity(0.08),
|
||||
UiColors.primary.withOpacity(0.04),
|
||||
UiColors.primary.withValues(alpha: 0.08),
|
||||
UiColors.primary.withValues(alpha: 0.04),
|
||||
],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(UiConstants.radiusBase),
|
||||
border: Border.all(color: UiColors.primary.withOpacity(0.12)),
|
||||
border: Border.all(color: UiColors.primary.withValues(alpha: 0.12)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
|
||||
@@ -23,7 +23,7 @@ class DocumentSelectedCard extends StatelessWidget {
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: UiColors.primary.withOpacity(0.1),
|
||||
color: UiColors.primary.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(UiConstants.space2),
|
||||
),
|
||||
child: const Center(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports, unused_element, unused_field, duplicate_ignore
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs, implementation_imports, unused_element, unused_field, duplicate_ignore
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -53,7 +53,7 @@ class AttireGrid extends StatelessWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? UiColors.primary.withOpacity(0.1)
|
||||
? UiColors.primary.withValues(alpha: 0.1)
|
||||
: Colors.transparent,
|
||||
borderRadius: UiConstants.radiusSm,
|
||||
border: Border.all(
|
||||
@@ -141,7 +141,7 @@ class AttireGrid extends StatelessWidget {
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: hasPhoto
|
||||
? UiColors.primary.withOpacity(0.05)
|
||||
? UiColors.primary.withValues(alpha: 0.05)
|
||||
: UiColors.white,
|
||||
border: Border.all(
|
||||
color: hasPhoto ? UiColors.primary : UiColors.border,
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
// ignore_for_file: always_specify_types, depend_on_referenced_packages, dead_code, dead_null_aware_expression, unused_local_variable, unused_import, sort_constructors_first, prefer_final_fields, prefer_const_constructors, deprecated_member_use, implicit_call_tearoffs
|
||||
import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:google_places_flutter/model/prediction.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
|
||||
import '../blocs/personal_info_bloc.dart';
|
||||
import '../blocs/personal_info_event.dart';
|
||||
import '../blocs/personal_info_state.dart';
|
||||
import '../widgets/preferred_locations_page/places_search_field.dart';
|
||||
import '../widgets/preferred_locations_page/locations_list.dart';
|
||||
import '../widgets/preferred_locations_page/empty_locations_state.dart';
|
||||
import 'package:staff_profile_info/src/presentation/blocs/personal_info_bloc.dart';
|
||||
import 'package:staff_profile_info/src/presentation/blocs/personal_info_state.dart';
|
||||
import 'package:staff_profile_info/src/presentation/blocs/personal_info_event.dart';
|
||||
|
||||
/// The maximum number of preferred locations a staff member can add.
|
||||
const int _kMaxLocations = 5;
|
||||
|
||||
/// Uber-style Preferred Locations editing page.
|
||||
/// Page for staff members to manage their preferred work locations.
|
||||
///
|
||||
/// Allows staff to search for US locations using the Google Places API,
|
||||
/// add them as chips (max 5), and save back to their profile.
|
||||
/// Allows searching for and adding multiple locations to the profile.
|
||||
class PreferredLocationsPage extends StatefulWidget {
|
||||
/// Creates a [PreferredLocationsPage].
|
||||
const PreferredLocationsPage({super.key});
|
||||
@@ -30,211 +20,112 @@ class PreferredLocationsPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PreferredLocationsPageState extends State<PreferredLocationsPage> {
|
||||
late final TextEditingController _searchController;
|
||||
late final FocusNode _searchFocusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_searchController = TextEditingController();
|
||||
_searchFocusNode = FocusNode();
|
||||
}
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
_searchFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onLocationSelected(Prediction prediction, PersonalInfoBloc bloc) {
|
||||
final String description = prediction.description ?? '';
|
||||
if (description.isEmpty) return;
|
||||
|
||||
bloc.add(PersonalInfoLocationAdded(location: description));
|
||||
|
||||
// Clear search field after selection
|
||||
void _onAddLocation(String location, PersonalInfoBloc bloc) {
|
||||
if (location.trim().isEmpty) return;
|
||||
bloc.add(PersonalInfoLocationAdded(location: location));
|
||||
_searchController.clear();
|
||||
_searchFocusNode.unfocus();
|
||||
}
|
||||
|
||||
void _removeLocation(String location, PersonalInfoBloc bloc) {
|
||||
void _onRemoveLocation(String location, PersonalInfoBloc bloc) {
|
||||
bloc.add(PersonalInfoLocationRemoved(location: location));
|
||||
}
|
||||
|
||||
void _save(BuildContext context, PersonalInfoBloc bloc, PersonalInfoState state) {
|
||||
bloc.add(const PersonalInfoFormSubmitted());
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final TranslationsStaffOnboardingPersonalInfoEn i18n = t.staff.onboarding.personal_info;
|
||||
// Access the same PersonalInfoBloc singleton managed by the module.
|
||||
final PersonalInfoBloc bloc = Modular.get<PersonalInfoBloc>();
|
||||
|
||||
return BlocProvider<PersonalInfoBloc>.value(
|
||||
value: bloc,
|
||||
child: BlocConsumer<PersonalInfoBloc, PersonalInfoState>(
|
||||
listener: (BuildContext context, PersonalInfoState state) {
|
||||
if (state.status == PersonalInfoStatus.saved) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: i18n.preferred_locations.save_success,
|
||||
type: UiSnackbarType.success,
|
||||
);
|
||||
} else if (state.status == PersonalInfoStatus.error) {
|
||||
UiSnackbar.show(
|
||||
context,
|
||||
message: state.errorMessage != null
|
||||
? translateErrorKey(state.errorMessage!)
|
||||
: 'An error occurred',
|
||||
type: UiSnackbarType.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
builder: (BuildContext context, PersonalInfoState state) {
|
||||
final List<String> locations = _currentLocations(state);
|
||||
final bool atMax = locations.length >= _kMaxLocations;
|
||||
final bool isSaving = state.status == PersonalInfoStatus.saving;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: UiColors.background,
|
||||
appBar: UiAppBar(
|
||||
title: i18n.preferred_locations.title,
|
||||
showBackButton: true,
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
// ” Description
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space5,
|
||||
UiConstants.space5,
|
||||
UiConstants.space3,
|
||||
),
|
||||
child: Text(
|
||||
i18n.preferred_locations.description,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
),
|
||||
|
||||
// ” Search autocomplete field
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
),
|
||||
child: PlacesSearchField(
|
||||
controller: _searchController,
|
||||
focusNode: _searchFocusNode,
|
||||
hint: i18n.preferred_locations.search_hint,
|
||||
enabled: !atMax && !isSaving,
|
||||
onSelected: (Prediction p) => _onLocationSelected(p, bloc),
|
||||
),
|
||||
),
|
||||
|
||||
// ” "Max reached" banner
|
||||
if (atMax)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
UiConstants.space5,
|
||||
UiConstants.space2,
|
||||
UiConstants.space5,
|
||||
0,
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const Icon(
|
||||
UiIcons.info,
|
||||
size: 14,
|
||||
color: UiColors.textWarning,
|
||||
),
|
||||
const SizedBox(width: UiConstants.space1),
|
||||
Text(
|
||||
i18n.preferred_locations.max_reached,
|
||||
style: UiTypography.footnote1r.textWarning,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
|
||||
// ” Section label
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: UiConstants.space5,
|
||||
),
|
||||
child: Text(
|
||||
i18n.preferred_locations.added_label,
|
||||
style: UiTypography.titleUppercase3m.textSecondary,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: UiConstants.space3),
|
||||
|
||||
// Locations list / empty state
|
||||
Expanded(
|
||||
child: locations.isEmpty
|
||||
? EmptyLocationsState(message: i18n.preferred_locations.empty_state)
|
||||
: LocationsList(
|
||||
locations: locations,
|
||||
isSaving: isSaving,
|
||||
removeTooltip: i18n.preferred_locations.remove_tooltip,
|
||||
onRemove: (String loc) => _removeLocation(loc, bloc),
|
||||
),
|
||||
),
|
||||
|
||||
// Save button
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: UiButton.primary(
|
||||
text: isSaving ? null : i18n.preferred_locations.save_button,
|
||||
fullWidth: true,
|
||||
onPressed: isSaving ? null : () => _save(context, bloc, state),
|
||||
child: isSaving
|
||||
? const SizedBox(
|
||||
height: UiConstants.iconMd,
|
||||
width: UiConstants.iconMd,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
UiColors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (isSaving)
|
||||
Container(
|
||||
color: UiColors.black.withValues(alpha: 0.3),
|
||||
child: const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: UiColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<String> _currentLocations(PersonalInfoState state) {
|
||||
final dynamic raw = state.formValues['preferredLocations'];
|
||||
final dynamic raw = state.personalInfo?.preferredLocations;
|
||||
if (raw is List<String>) return raw;
|
||||
if (raw is List) return raw.map((dynamic e) => e.toString()).toList();
|
||||
return <String>[];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final TranslationsStaffOnboardingPersonalInfoEn i18n =
|
||||
Translations.of(context).staff.onboarding.personal_info;
|
||||
|
||||
return BlocProvider<PersonalInfoBloc>.value(
|
||||
value: Modular.get<PersonalInfoBloc>(),
|
||||
child: BlocBuilder<PersonalInfoBloc, PersonalInfoState>(
|
||||
builder: (BuildContext context, PersonalInfoState state) {
|
||||
final PersonalInfoBloc bloc = BlocProvider.of<PersonalInfoBloc>(context);
|
||||
final List<String> locations = _currentLocations(state);
|
||||
|
||||
return Scaffold(
|
||||
appBar: UiAppBar(
|
||||
title: i18n.preferred_locations.title,
|
||||
showBackButton: true,
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
i18n.preferred_locations.description,
|
||||
style: UiTypography.body2r.textSecondary,
|
||||
),
|
||||
const SizedBox(height: UiConstants.space5),
|
||||
|
||||
// Search field (Mock autocomplete)
|
||||
TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: i18n.preferred_locations.search_hint,
|
||||
suffixIcon: IconButton(
|
||||
icon: const Icon(UiIcons.add),
|
||||
onPressed: () => _onAddLocation(_searchController.text, bloc),
|
||||
),
|
||||
),
|
||||
onSubmitted: (String val) => _onAddLocation(val, bloc),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: UiConstants.space5),
|
||||
itemCount: locations.length,
|
||||
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final String loc = locations[index];
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(UiIcons.mapPin, color: UiColors.primary),
|
||||
title: Text(loc, style: UiTypography.body2m.textPrimary),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(UiIcons.close, size: 20),
|
||||
onPressed: () => _onRemoveLocation(loc, bloc),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(UiConstants.space5),
|
||||
child: UiButton.primary(
|
||||
text: i18n.save_button,
|
||||
onPressed: state.status == PersonalInfoStatus.loading
|
||||
? null
|
||||
: () => bloc.add(const PersonalInfoFormSubmitted()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,4 +98,4 @@ class PersonalInfoForm extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
apps/mobile/packages/features/staff/shifts/analyze_output.txt
Normal file
BIN
apps/mobile/packages/features/staff/shifts/analyze_output.txt
Normal file
Binary file not shown.
@@ -221,25 +221,15 @@ class _MyShiftCardState extends State<MyShiftCard> {
|
||||
color: UiColors.primary.withValues(alpha: 0.09),
|
||||
),
|
||||
),
|
||||
child: widget.shift.logoUrl != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
UiConstants.radiusBase,
|
||||
),
|
||||
child: Image.network(
|
||||
widget.shift.logoUrl!,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
)
|
||||
: const Center(
|
||||
child: Icon(
|
||||
UiIcons.briefcase,
|
||||
color: UiColors.primary,
|
||||
size: UiConstants.iconMd,
|
||||
),
|
||||
),
|
||||
child: const Center(
|
||||
child: Icon(
|
||||
UiIcons.briefcase,
|
||||
color: UiColors.primary,
|
||||
size: UiConstants.iconMd,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: UiConstants.space3),
|
||||
|
||||
// Consensed Details
|
||||
|
||||
@@ -2,8 +2,6 @@ import 'package:core_localization/core_localization.dart';
|
||||
import 'package:design_system/design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_modular/flutter_modular.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:krow_core/core.dart';
|
||||
import 'package:krow_domain/krow_domain.dart';
|
||||
import 'package:staff_shifts/src/presentation/widgets/available_order_card.dart';
|
||||
@@ -34,124 +32,15 @@ class FindShiftsTab extends StatefulWidget {
|
||||
class _FindShiftsTabState extends State<FindShiftsTab> {
|
||||
String _searchQuery = '';
|
||||
String _jobType = 'all';
|
||||
double? _maxDistance; // miles
|
||||
Position? _currentPosition;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
|
||||
|
||||
String _formatTime(DateTime dt) => DateFormat('h:mm a').format(dt);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _initLocation() async {
|
||||
try {
|
||||
final LocationPermission permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.always ||
|
||||
permission == LocationPermission.whileInUse) {
|
||||
final Position pos = await Geolocator.getCurrentPosition();
|
||||
if (mounted) {
|
||||
setState(() => _currentPosition = pos);
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
double _calculateDistance(double lat, double lng) {
|
||||
if (_currentPosition == null) return -1;
|
||||
final double distMeters = Geolocator.distanceBetween(
|
||||
_currentPosition!.latitude,
|
||||
_currentPosition!.longitude,
|
||||
lat,
|
||||
lng,
|
||||
);
|
||||
return distMeters / 1609.34; // meters to miles
|
||||
}
|
||||
|
||||
void _showDistanceFilter() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
backgroundColor: UiColors.bgPopup,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
builder: (BuildContext context) {
|
||||
return StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setModalState) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
context.t.staff_shifts.find_shifts.radius_filter_title,
|
||||
style: UiTypography.headline4m.textPrimary,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
_maxDistance == null
|
||||
? context.t.staff_shifts.find_shifts.unlimited_distance
|
||||
: context.t.staff_shifts.find_shifts.within_miles(
|
||||
miles: _maxDistance!.round().toString(),
|
||||
),
|
||||
style: UiTypography.body2m.textSecondary,
|
||||
),
|
||||
Slider(
|
||||
value: _maxDistance ?? 100,
|
||||
min: 5,
|
||||
max: 100,
|
||||
divisions: 19,
|
||||
activeColor: UiColors.primary,
|
||||
onChanged: (double val) {
|
||||
setModalState(() => _maxDistance = val);
|
||||
setState(() => _maxDistance = val);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: UiButton.secondary(
|
||||
text: context.t.staff_shifts.find_shifts.clear,
|
||||
onPressed: () {
|
||||
setModalState(() => _maxDistance = null);
|
||||
setState(() => _maxDistance = null);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: UiButton.primary(
|
||||
text: context.t.staff_shifts.find_shifts.apply,
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDate(DateTime date) {
|
||||
final DateTime now = DateTime.now();
|
||||
final DateTime today = DateTime(now.year, now.month, now.day);
|
||||
final DateTime tomorrow = today.add(const Duration(days: 1));
|
||||
final DateTime d = DateTime(date.year, date.month, date.day);
|
||||
if (d == today) return 'Today';
|
||||
if (d == tomorrow) return 'Tomorrow';
|
||||
return DateFormat('EEE, MMM d').format(date);
|
||||
}
|
||||
|
||||
/// Builds a filter tab chip.
|
||||
Widget _buildFilterTab(String id, String label) {
|
||||
final bool isSelected = _jobType == id;
|
||||
|
||||
Reference in New Issue
Block a user