feat: legacy mobile apps created

This commit is contained in:
Achintha Isuru
2025-12-02 23:51:04 -05:00
parent 850441ca64
commit 8e7753b324
1519 changed files with 0 additions and 16 deletions

View File

@@ -0,0 +1,24 @@
const String getStaffInclusivityInfoSchema = '''
query GetPersonalInfo {
me {
id
accessibility {
has_car
can_relocate
requires_accommodations
accommodation_details
}
}
}
''';
const String updateStaffInclusivityMutationSchema = '''
mutation UpdateStaffAccessibilityInfo(\$input: UpdateStaffAccessibilitiesInput!) {
update_staff_accessibilities(input: \$input) {
accessibility {
requires_accommodations
accommodation_details
}
}
}
''';

View File

@@ -0,0 +1,27 @@
import 'package:flutter/foundation.dart';
@immutable
class InclusiveInfoModel {
const InclusiveInfoModel({
required this.areAccommodationsRequired,
required this.accommodationsDetails,
});
factory InclusiveInfoModel.fromJson(Map<String, dynamic> json) {
return InclusiveInfoModel(
areAccommodationsRequired:
json['requires_accommodations'] as bool? ?? false,
accommodationsDetails: json['accommodation_details'] as String? ?? '',
);
}
final bool areAccommodationsRequired;
final String accommodationsDetails;
Map<String, dynamic> toJson() {
return {
'requires_accommodations': areAccommodationsRequired,
'accommodation_details': accommodationsDetails,
};
}
}

View File

@@ -0,0 +1,62 @@
import 'dart:developer';
import 'package:injectable/injectable.dart';
import 'package:krow/core/application/clients/api/api_client.dart';
import 'package:krow/features/profile/inclusive/data/gql_shemas.dart';
import 'package:krow/features/profile/inclusive/data/models/inclusive_info_model.dart';
@injectable
class StaffInclusivityApiProvider {
StaffInclusivityApiProvider(this._client);
final ApiClient _client;
Stream<InclusiveInfoModel> getStaffInclusivityInfoWithCache() async* {
await for (var response in _client.queryWithCache(
schema: getStaffInclusivityInfoSchema,
)) {
if (response == null || response.data == null) continue;
if (response.hasException) {
throw Exception(response.exception.toString());
}
try {
yield InclusiveInfoModel.fromJson(
(response.data?['me'] as Map<String, dynamic>?)?['accessibility'] ??
{},
);
} catch (except) {
log(
'Exception in StaffInclusivityApiProvider '
'on getStaffInclusivityInfoWithCache()',
error: except,
);
continue;
}
}
}
Future<InclusiveInfoModel?> updateStaffInclusivityInfoInfo(
InclusiveInfoModel data,
) async {
var result = await _client.mutate(
schema: updateStaffInclusivityMutationSchema,
body: {
'input': data.toJson(),
},
);
if (result.hasException) {
throw Exception(result.exception.toString());
}
if (result.data == null || result.data!.isEmpty) return null;
return InclusiveInfoModel.fromJson(
(result.data?['update_staff_accessibilities']
as Map<String, dynamic>?)?['accessibility'] ??
{},
);
}
}

View File

@@ -0,0 +1,9 @@
import 'package:krow/features/profile/inclusive/data/models/inclusive_info_model.dart';
abstract class StaffInclusivityRepository {
Stream<InclusiveInfoModel> getStaffInclusivityInfo();
Future<InclusiveInfoModel?> updateStaffMobilityInfo(
InclusiveInfoModel data,
);
}

View File

@@ -0,0 +1,85 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/core/data/enums/state_status.dart';
import 'package:krow/features/profile/inclusive/data/models/inclusive_info_model.dart';
import 'package:krow/features/profile/inclusive/data/staff_inclusivity_repository.dart';
part 'inclusive_info_event.dart';
part 'inclusive_info_state.dart';
class InclusiveInfoBloc extends Bloc<InclusiveInfoEvent, InclusiveInfoState> {
InclusiveInfoBloc() : super(const InclusiveInfoState()) {
on<InitializeInclusiveInfoEvent>((event, emit) async {
emit(
state.copyWith(
isInEditMode: event.isInEditMode,
status: event.isInEditMode ? StateStatus.loading : StateStatus.idle,
),
);
if (!state.isInEditMode) {
emit(
state.copyWith(
areAccommodationsRequired: true,
),
);
return;
}
try {
await for (final inclusivityData
in getIt<StaffInclusivityRepository>().getStaffInclusivityInfo()) {
emit(
state.copyWith(
areAccommodationsRequired:
inclusivityData.areAccommodationsRequired,
accommodationsDetails: inclusivityData.accommodationsDetails,
status: StateStatus.idle,
),
);
}
} catch (except) {
log(except.toString());
}
emit(
state.copyWith(
areAccommodationsRequired: state.areAccommodationsRequired ?? true,
status: state.status == StateStatus.loading
? StateStatus.idle
: state.status,
),
);
});
on<ToggleRequiresAccommodations>((event, emit) {
emit(state.copyWith(areAccommodationsRequired: event.index == 0));
});
on<ChangeAccommodationsDetails>((event, emit) {
emit(state.copyWith(accommodationsDetails: event.details));
});
on<SaveInclusiveInfoChanges>((event, emit) async {
emit(state.copyWith(status: StateStatus.loading));
try {
await getIt<StaffInclusivityRepository>().updateStaffMobilityInfo(
InclusiveInfoModel(
areAccommodationsRequired: state.areAccommodationsRequired ?? false,
accommodationsDetails: state.accommodationsDetails,
),
);
} catch (except) {
emit(state.copyWith(status: StateStatus.idle));
log(except.toString());
}
emit(state.copyWith(status: StateStatus.success));
});
}
}

View File

@@ -0,0 +1,24 @@
part of 'inclusive_info_bloc.dart';
@immutable
sealed class InclusiveInfoEvent {}
class InitializeInclusiveInfoEvent extends InclusiveInfoEvent {
InitializeInclusiveInfoEvent({required this.isInEditMode});
final bool isInEditMode;
}
class ToggleRequiresAccommodations extends InclusiveInfoEvent {
ToggleRequiresAccommodations(this.index);
final int index;
}
class ChangeAccommodationsDetails extends InclusiveInfoEvent {
ChangeAccommodationsDetails(this.details);
final String details;
}
class SaveInclusiveInfoChanges extends InclusiveInfoEvent {}

View File

@@ -0,0 +1,37 @@
part of 'inclusive_info_bloc.dart';
@immutable
class InclusiveInfoState {
const InclusiveInfoState({
this.areAccommodationsRequired,
this.accommodationsDetails = '',
this.isInEditMode = true,
this.status = StateStatus.idle,
});
final bool? areAccommodationsRequired;
final String accommodationsDetails;
final bool isInEditMode;
final StateStatus status;
bool get isFilled {
return (areAccommodationsRequired != null && !areAccommodationsRequired!) ||
accommodationsDetails.isNotEmpty;
}
InclusiveInfoState copyWith({
bool? areAccommodationsRequired,
String? accommodationsDetails,
bool? isInEditMode,
StateStatus? status,
}) {
return InclusiveInfoState(
areAccommodationsRequired:
areAccommodationsRequired ?? this.areAccommodationsRequired,
accommodationsDetails:
accommodationsDetails ?? this.accommodationsDetails,
isInEditMode: isInEditMode ?? this.isInEditMode,
status: status ?? this.status,
);
}
}

View File

@@ -0,0 +1,25 @@
import 'package:injectable/injectable.dart';
import 'package:krow/features/profile/inclusive/data/models/inclusive_info_model.dart';
import 'package:krow/features/profile/inclusive/data/staff_inclusivity_api_provider.dart';
import 'package:krow/features/profile/inclusive/data/staff_inclusivity_repository.dart';
@Injectable(as: StaffInclusivityRepository)
class StaffMobilityRepositoryImpl extends StaffInclusivityRepository {
StaffMobilityRepositoryImpl({
required StaffInclusivityApiProvider apiProvider,
}) : _apiProvider = apiProvider;
final StaffInclusivityApiProvider _apiProvider;
@override
Stream<InclusiveInfoModel> getStaffInclusivityInfo() {
return _apiProvider.getStaffInclusivityInfoWithCache();
}
@override
Future<InclusiveInfoModel?> updateStaffMobilityInfo(
InclusiveInfoModel data,
) {
return _apiProvider.updateStaffInclusivityInfoInfo(data);
}
}

View File

@@ -0,0 +1,128 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
import 'package:krow/core/application/common/bool_extension.dart';
import 'package:krow/core/application/routing/routes.gr.dart';
import 'package:krow/core/data/enums/state_status.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/core/presentation/styles/theme.dart';
import 'package:krow/core/presentation/widgets/scroll_layout_helper.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_app_bar.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_button.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_loading_overlay.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_option_selector.dart';
import 'package:krow/features/profile/inclusive/domain/bloc/inclusive_info_bloc.dart';
import 'package:krow/features/profile/inclusive/presentation/widgets/accessibility_details_widget.dart';
@RoutePage()
class InclusiveScreen extends StatelessWidget implements AutoRouteWrapper {
const InclusiveScreen({
super.key,
this.isInEditMode = true,
});
final bool isInEditMode;
@override
Widget wrappedRoute(BuildContext context) {
return BlocProvider(
create: (context) => InclusiveInfoBloc()
..add(InitializeInclusiveInfoEvent(isInEditMode: isInEditMode)),
child: this,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: KwAppBar(
titleText: 'inclusive'.tr(),
showNotification: false,
),
body: ScrollLayoutHelper(
padding: const EdgeInsets.all(16),
upperWidget: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Gap(4),
Text(
'inclusive_information'.tr(),
style: AppTextStyles.headingH1,
textAlign: TextAlign.start,
),
const Gap(8),
Text(
'providing_optional_information'.tr(),
style: AppTextStyles.bodyMediumReg.copyWith(
color: AppColors.blackGray,
),
textAlign: TextAlign.start,
),
Padding(
padding: const EdgeInsets.only(bottom: 16, top: 24),
child: Text(
'specific_accommodations_question'.tr(),
style: AppTextStyles.headingH3,
),
),
BlocSelector<InclusiveInfoBloc, InclusiveInfoState, bool?>(
selector: (state) => state.areAccommodationsRequired,
builder: (context, areAccommodationsRequired) {
return KwOptionSelector(
selectedIndex: areAccommodationsRequired?.toInt(),
selectedColor: AppColors.blackDarkBgBody,
itemBorder: const Border.fromBorderSide(
BorderSide(color: AppColors.grayStroke),
),
items: ['yes'.tr(), 'no'.tr()],
onChanged: (index) {
context
.read<InclusiveInfoBloc>()
.add(ToggleRequiresAccommodations(index));
},
);
},
),
const Gap(16),
const AccessibilityDetailsWidget(),
const Gap(90),
],
),
lowerWidget: BlocConsumer<InclusiveInfoBloc, InclusiveInfoState>(
buildWhen: (previous, current) =>
previous.status != current.status ||
previous.isFilled != current.isFilled,
listenWhen: (previous, current) => previous.status != current.status,
listener: (context, state) {
if (state.status == StateStatus.success) {
if (isInEditMode) {
Navigator.pop(context);
} else {
context.router.push(
AddressRoute(isInEditMode: false),
);
}
}
},
builder: (context, state) {
return KwLoadingOverlay(
shouldShowLoading: state.status == StateStatus.loading,
child: KwButton.primary(
label: isInEditMode ? 'save_changes'.tr() : 'save_and_continue'.tr(),
height: 52,
disabled: !state.isFilled,
onPressed: () {
context
.read<InclusiveInfoBloc>()
.add(SaveInclusiveInfoChanges());
},
),
);
},
),
),
);
}
}

View File

@@ -0,0 +1,73 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gap/gap.dart';
import 'package:krow/core/data/enums/state_status.dart';
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
import 'package:krow/core/presentation/styles/theme.dart';
import 'package:krow/core/presentation/widgets/ui_kit/kw_input.dart';
import 'package:krow/features/profile/inclusive/domain/bloc/inclusive_info_bloc.dart';
class AccessibilityDetailsWidget extends StatefulWidget {
const AccessibilityDetailsWidget({super.key});
@override
State<AccessibilityDetailsWidget> createState() =>
_AccessibilityDetailsWidgetState();
}
class _AccessibilityDetailsWidgetState
extends State<AccessibilityDetailsWidget> {
final _controller = TextEditingController();
@override
Widget build(BuildContext context) {
return BlocConsumer<InclusiveInfoBloc, InclusiveInfoState>(
buildWhen: (previous, current) =>
previous.areAccommodationsRequired !=
current.areAccommodationsRequired ||
previous.status != current.status,
listenWhen: (previous, current) => previous.status != current.status,
listener: (context, state) {
_controller.text = state.accommodationsDetails;
},
builder: (context, state) {
return AnimatedOpacity(
opacity: state.areAccommodationsRequired ?? false ? 1 : 0,
duration: Durations.medium2,
child: Column(
children: [
Text(
'describe_accommodations'.tr(),
style: AppTextStyles.bodyMediumReg.copyWith(
color: AppColors.blackGray,
),
textAlign: TextAlign.start,
),
const Gap(16),
KwTextInput(
enabled: state.areAccommodationsRequired ?? false,
controller: _controller,
minHeight: 160,
maxLength: 300,
showCounter: true,
radius: 12,
title: 'additional_details'.tr(),
hintText: 'enter_main_text'.tr(),
showError: state.status == StateStatus.error,
helperText: state.status == StateStatus.error
? 'required_to_fill'.tr()
: null,
onChanged: (details) {
context
.read<InclusiveInfoBloc>()
.add(ChangeAccommodationsDetails(details));
},
),
],
),
);
},
);
}
}