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,19 @@
class MutateKitDto {
String id;
String? photo;
MutateKitDto({
required this.id,
this.photo,
});
Map<String, String> toJson() => {
'id': id,
if (photo != null) 'photo': photo!,
};
@override
String toString() {
return toJson().toString();
}
}

View File

@@ -0,0 +1,20 @@
const String confirmStaffUniformsMutation = '''
mutation ConfirmStaffUniforms(\$skillId: String!, \$uniforms: [ConfirmStaffUniformInput!]!) {
confirm_staff_uniforms(skill_id: \$skillId, uniforms: \$uniforms)
}
''';
const String confirmStaffEquipmentsMutation = '''
mutation ConfirmStaffEquipments(\$skillId: String!, \$equipments: [ConfirmStaffEquipmentInput!]!) {
confirm_staff_equipments(skill_id: \$skillId, equipments: \$equipments)
}
''';
const String uploadImageMutation = '''
mutation UploadImage(\$file: Upload!) {
upload_file(file: \$file) {
token
url
}
}
''';

View File

@@ -0,0 +1,85 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:injectable/injectable.dart';
import 'package:krow/core/application/clients/api/api_client.dart';
import 'package:krow/core/application/clients/api/api_exception.dart';
import 'package:krow/core/application/clients/api/gql.dart';
import 'package:krow/core/data/models/staff_role.dart';
import 'package:krow/features/profile/role_kit/data/models/mutate_kit_dto.dart';
import 'package:krow/features/profile/role_kit/data/staf_kit_gql.dart';
@injectable
class StaffRoleKitApiProvider {
final ApiClient _apiClient;
StaffRoleKitApiProvider(this._apiClient);
Future<List<StaffRole>> fetchStaffRoles() async {
var result = await _apiClient.query(schema: getStaffRolesQuery);
if (result.hasException) {
throw parseBackendError(result.exception);
}
return result.data!['staff_roles'].map<StaffRole>((e) {
return StaffRole.fromJson(e);
}).toList();
}
Future<void> putUniform(String skillId, List<MutateKitDto> kits) async {
final Map<String, dynamic> variables = {
'skillId': skillId,
'uniforms': kits,
};
var result = await _apiClient.mutate(
schema: confirmStaffUniformsMutation, body: variables);
if (result.hasException) {
throw parseBackendError(result.exception);
}
}
Future<void> putEquipment(String skillId, List<MutateKitDto> kits) async {
final Map<String, dynamic> variables = {
'skillId': skillId,
'equipments': kits,
};
var result = await _apiClient.mutate(
schema: confirmStaffEquipmentsMutation, body: variables);
if (result.hasException) {
throw parseBackendError(result.exception);
}
}
Future<Map<String, dynamic>> uploadImage(String imagePath) async {
final ApiClient apiClient = ApiClient();
var byteData = File(imagePath).readAsBytesSync();
var multipartFile = MultipartFile.fromBytes(
'photo',
byteData,
filename: '${DateTime.now().millisecondsSinceEpoch}.jpg',
contentType: MediaType('image', 'jpg'),
);
final Map<String, dynamic> variables = {
'file': multipartFile,
};
final result = await apiClient.mutate(
schema: uploadImageMutation,
body: variables,
);
if (result.hasException) {
debugPrint(result.exception.toString());
} else {
return result.data!['upload_file'];
}
return <String, dynamic>{};
}
}

View File

@@ -0,0 +1,13 @@
import 'package:krow/core/data/models/staff_role.dart';
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
import 'models/mutate_kit_dto.dart';
abstract class StaffRoleKitRepository {
Future<List<StaffRole>> getStaffRole();
Future<void> putKit(
String skillId, List<MutateKitDto> kits, RoleKitType type);
Future<Map<String, dynamic>> uploadImage(String imagePath);
}

View File

@@ -0,0 +1,133 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:krow/core/application/clients/api/api_exception.dart';
import 'package:krow/core/application/di/injectable.dart';
import 'package:krow/features/profile/role_kit/data/models/mutate_kit_dto.dart';
import 'package:krow/features/profile/role_kit/data/staff_role_kit_repository.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_event.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_state.dart';
import 'package:krow/features/profile/role_kit/domain/role_kit_entity.dart';
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
class RoleKitBloc extends Bloc<RoleKitEvent, RoleKitState> {
RoleKitBloc() : super(RoleKitState()) {
on<RoleKitEventFetch>(_onFetch);
on<RoleKitEventSelectRole>(_onSelectRole);
on<RoleKitEventSubmit>(_onSubmit);
on<RoleKiEventChangeState>(_onChangeState);
on<RoleKitEventUploadPhoto>(_onUploadPhoto);
on<RoleKitEventDeletePhoto>(_onDeletePhoto);
}
Future<void> _onFetch(
RoleKitEventFetch event, Emitter<RoleKitState> emit) async {
emit(state.copyWith(roleKitType: event.type, loading: true));
var roles = await getIt<StaffRoleKitRepository>().getStaffRole();
emit(state.copyWith(roles: roles, loading: false));
}
Future<void> _onSelectRole(
RoleKitEventSelectRole event, Emitter<RoleKitState> emit) async {
List<RoleKitEntity> roleKitViewModels = [];
var confirmedKits = state.roleKitType == RoleKitType.equipment
? event.role.confirmedEquipments
: event.role.confirmedUniforms;
(state.roleKitType == RoleKitType.equipment
? event.role.skill?.equipments
: event.role.skill?.uniforms)
?.forEach((item) {
var confirmed = confirmedKits
?.where((element) => element.skillKitId == item.id)
.firstOrNull;
roleKitViewModels.add(RoleKitEntity(
id: item.id,
title: item.name ?? '',
confirmed: confirmed != null,
imageUrl: confirmed?.photo,
isMandatory: (item.isRequired ?? false) || (item.photoRequired ?? false),
needPhoto: item.photoRequired ?? false));
});
emit(state.copyWith(
selectedRole: event.role, roleKitItems: roleKitViewModels));
}
void _onChangeState(
RoleKiEventChangeState event, Emitter<RoleKitState> emit) {
event.item.confirmed = !event.item.confirmed;
emit(state.copyWith(showError: false));
}
void _onUploadPhoto(
RoleKitEventUploadPhoto event, Emitter<RoleKitState> emit) async {
event.item.uploading = true;
emit(state.copyWith(showError: false));
try {
var image = await ImagePicker().pickImage(source: ImageSource.gallery);
if (image != null) {
var imagePath = image.path;
var result =
await getIt<StaffRoleKitRepository>().uploadImage(imagePath);
event.item.uploading = false;
event.item.imageToken = result['token'];
event.item.imageUrl = result['url'];
event.item.localImage = imagePath;
}
} catch(e){
if (e is DisplayableException) {
emit(state.copyWith(apiError: e.message,loading: false));
}
}finally {
event.item.uploading = false;
emit(state.copyWith());
}
}
void _onSubmit(RoleKitEventSubmit event, Emitter<RoleKitState> emit) async {
final allMandatoryItemsChecked = state.roleKitItems
.where((element) => element.isMandatory)
.every((element) => element.confirmed);
final allPhotoUploaded = state.roleKitItems
.where((element) => element.needPhoto)
.every((element) =>
element.imageUrl != null || element.localImage != null);
if (allMandatoryItemsChecked && allPhotoUploaded) {
emit(state.copyWith(loading: true));
var confirmed = state.roleKitItems
.where((e) => e.confirmed)
.map((e) => MutateKitDto(id: e.id, photo: e.imageToken ?? e.imageUrl))
.toList();
try {
await getIt<StaffRoleKitRepository>()
.putKit(
state.selectedRole!.skill!.id, confirmed, state.roleKitType);
}catch (e) {
if (e is DisplayableException) {
emit(state.copyWith(apiError: e.message));
}
return;
} finally {
emit(state.copyWith(loading: false));
}
emit(state.copyWith(success: true));
} else {
emit(state.copyWith(showError: true));
}
}
void _onDeletePhoto(
RoleKitEventDeletePhoto event, Emitter<RoleKitState> emit) {
event.item.imageUrl = null;
event.item.localImage = null;
event.item.imageToken = null;
emit(state.copyWith());
}
}

View File

@@ -0,0 +1,38 @@
import 'package:krow/core/data/models/staff_role.dart';
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
import 'package:krow/features/profile/role_kit/domain/role_kit_entity.dart';
sealed class RoleKitEvent {}
class RoleKitEventFetch extends RoleKitEvent {
final RoleKitType type;
RoleKitEventFetch({required this.type});
}
class RoleKitEventSubmit extends RoleKitEvent {}
class RoleKiEventChangeState extends RoleKitEvent {
final RoleKitEntity item;
RoleKiEventChangeState(this.item);
}
class RoleKitEventUploadPhoto extends RoleKitEvent {
final RoleKitEntity item;
final String photoPath;
RoleKitEventUploadPhoto({required this.item, required this.photoPath});
}
class RoleKitEventDeletePhoto extends RoleKitEvent {
final RoleKitEntity item;
RoleKitEventDeletePhoto(this.item);
}
class RoleKitEventSelectRole extends RoleKitEvent {
final StaffRole role;
RoleKitEventSelectRole({required this.role});
}

View File

@@ -0,0 +1,46 @@
import 'package:krow/core/data/models/staff_role.dart';
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
import 'package:krow/features/profile/role_kit/domain/role_kit_entity.dart';
class RoleKitState {
final bool showError;
final bool loading;
final bool success;
final RoleKitType roleKitType;
final List<RoleKitEntity> roleKitItems;
final List<StaffRole> roles;
final StaffRole? selectedRole;
final String? apiError;
RoleKitState(
{this.showError = false,
this.roleKitItems = const [],
this.roles = const [],
this.loading = false,
this.selectedRole,
this.roleKitType = RoleKitType.uniform,
this.success = false,
this.apiError});
copyWith(
{bool? showError,
List<RoleKitEntity>? roleKitItems,
RoleKitType? roleKitType,
bool? loading,
bool? success,
List<StaffRole>? roles,
StaffRole? selectedRole,
String? apiError}) {
return RoleKitState(
showError: showError ?? this.showError,
roleKitItems: roleKitItems ?? this.roleKitItems,
loading: loading ?? false,
success: success ?? false,
roleKitType: roleKitType ?? this.roleKitType,
roles: roles ?? this.roles,
selectedRole: selectedRole ?? this.selectedRole,
apiError: apiError,
);
}
}

View File

@@ -0,0 +1,23 @@
class RoleKitEntity {
final String id;
final String title;
final bool isMandatory;
final bool needPhoto;
bool uploading;
bool confirmed;
String? imageUrl;
String? localImage;
String? imageToken;
RoleKitEntity({
required this.id,
required this.title,
this.isMandatory = false,
this.confirmed = false,
this.imageUrl,
this.needPhoto = false,
this.uploading = false,
this.localImage,
});
}

View File

@@ -0,0 +1,37 @@
import 'package:injectable/injectable.dart';
import 'package:krow/core/data/models/staff_role.dart';
import 'package:krow/features/profile/role_kit/data/models/mutate_kit_dto.dart';
import 'package:krow/features/profile/role_kit/data/staff_role_kit_api.dart';
import 'package:krow/features/profile/role_kit/data/staff_role_kit_repository.dart';
enum RoleKitType {
uniform,
equipment,
}
@Injectable(as: StaffRoleKitRepository)
class StaffRoleKitRepositoryImpl extends StaffRoleKitRepository {
final StaffRoleKitApiProvider _staffRoleApi;
StaffRoleKitRepositoryImpl(this._staffRoleApi);
@override
Future<List<StaffRole>> getStaffRole() async {
return _staffRoleApi.fetchStaffRoles();
}
@override
Future<void> putKit(
String skillId, List<MutateKitDto> kits, RoleKitType type) async {
if (type == RoleKitType.uniform) {
return _staffRoleApi.putUniform(skillId, kits);
} else {
return _staffRoleApi.putEquipment(skillId, kits);
}
}
@override
Future<Map<String, dynamic>> uploadImage(String imagePath) async {
return _staffRoleApi.uploadImage(imagePath);
}
}

View File

@@ -0,0 +1,21 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_bloc.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_event.dart';
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
@RoutePage()
class RoleKitFlowScreen extends StatelessWidget {
final RoleKitType roleKitType;
const RoleKitFlowScreen({super.key, required this.roleKitType});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
RoleKitBloc()..add(RoleKitEventFetch(type: roleKitType)),
child: const AutoRouter());
}
}

View File

@@ -0,0 +1,197 @@
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/presentation/gen/assets.gen.dart';
import 'package:krow/core/presentation/styles/kw_box_decorations.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/check_box_card.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/uploud_image_card.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_bloc.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_event.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_state.dart';
import 'package:krow/features/profile/role_kit/domain/role_kit_entity.dart';
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
@RoutePage()
class RoleKitScreen extends StatelessWidget {
const RoleKitScreen({super.key});
@override
Widget build(BuildContext context) {
return BlocConsumer<RoleKitBloc, RoleKitState>(
listenWhen: (oldState, newState) =>
oldState.success != newState.success ||
oldState.apiError != newState.apiError,
listener: (context, state) {
if (state.success) {
context.router.maybePop();
}
if (state.apiError != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(state.apiError ?? ''),
));
}
},
builder: (context, state) {
return ModalProgressHUD(
inAsyncCall: state.loading,
child: Scaffold(
appBar: KwAppBar(
titleText: state.roleKitType == RoleKitType.uniform
? '${state.selectedRole?.skill?.name} ${'uniform'.tr()}'
: '${state.selectedRole?.skill?.name} ${'equipment'.tr()}',
),
body: ScrollLayoutHelper(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
upperWidget: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'${'please_indicate'.tr()} ${state.roleKitType == RoleKitType.uniform ? 'uniform'.tr().toLowerCase() : 'equipment'.tr().toLowerCase()}:',
style: AppTextStyles.bodyTinyReg
.copyWith(color: AppColors.blackGray),
),
if (state.showError) _buildErrorMessage(),
const Gap(24),
if (state.roleKitItems
.where((item) => item.isMandatory)
.isNotEmpty) ...[
_buildSectionLabel('mandatory_items'.tr()),
_buildItems(
context, state.roleKitItems, true, state.showError),
const Gap(4),
],
if (state.roleKitItems
.where((item) => !item.isMandatory)
.isNotEmpty) ...[
_buildSectionLabel('optional_items'.tr()),
_buildItems(
context, state.roleKitItems, false, state.showError),
],
if (state.roleKitItems
.where((item) => item.needPhoto)
.isNotEmpty) ...[
const Gap(32),
_buildSectionLabel('confirm_availability'.tr()),
_buildUploadProofItems(
context, state.roleKitItems, state.showError),
]
],
),
lowerWidget: KwButton.primary(
label: 'confirm'.tr(),
onPressed: () {
BlocProvider.of<RoleKitBloc>(context)
.add(RoleKitEventSubmit());
}),
),
),
);
});
}
Widget _buildSectionLabel(String text) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(text.toUpperCase(),
style: AppTextStyles.captionReg.copyWith(
color: AppColors.blackCaptionGreen,
)),
);
}
_buildItems(
context, List<RoleKitEntity> items, bool required, bool showError) {
return Column(
children: items.where((item) => item.isMandatory == required).map((item) {
return CheckBoxCard(
isChecked: item.confirmed,
title: item.title,
errorMessage: showError && !item.confirmed && required
? 'please_confirm_availability'.tr()
: null,
message: item.confirmed
? 'availability_confirmed'.tr()
: item.needPhoto
? 'confirm_availability_photo'.tr()
: null,
onTap: () {
BlocProvider.of<RoleKitBloc>(context)
.add(RoleKiEventChangeState(item));
},
padding: const EdgeInsets.only(bottom: 8),
);
}).toList(),
);
}
_buildUploadProofItems(context, List<RoleKitEntity> items, bool showError) {
return Column(
children: items.where((item) => item.needPhoto).map((item) {
return UploadImageCard(
title: item.title,
onSelectImage: () {
BlocProvider.of<RoleKitBloc>(context)
.add(RoleKitEventUploadPhoto(item: item, photoPath: ''));
},
onDeleteTap: () {
BlocProvider.of<RoleKitBloc>(context)
.add(RoleKitEventDeletePhoto(item));
},
onTap: () {},
imageUrl: item.imageUrl,
localImagePath: item.localImage,
inUploading: item.uploading,
statusColor: item.imageUrl == null
? AppColors.statusError
: AppColors.statusSuccess,
message: (item.imageUrl == null)
? showError
? 'availability_requires_confirmation'.tr()
: null
: 'availability_confirmed'.tr(),
hasError: showError,
padding: const EdgeInsets.only(bottom: 8),
);
}).toList(),
);
}
Container _buildErrorMessage() {
return Container(
margin: const EdgeInsets.only(top: 12),
padding: const EdgeInsets.all(8),
decoration: KwBoxDecorations.primaryLight8,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 28,
width: 28,
decoration: const BoxDecoration(
shape: BoxShape.circle, color: AppColors.tintRed),
child: Center(
child: Assets.images.icons.alertCircle.svg(),
),
),
const Gap(8),
Expanded(
child: Text(
'item_checked_in_box'.tr(),
style: AppTextStyles.bodyTinyMed
.copyWith(color: AppColors.statusError),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,95 @@
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/data/models/staff_role.dart';
import 'package:krow/core/presentation/gen/assets.gen.dart';
import 'package:krow/core/application/routing/routes.gr.dart';
import 'package:krow/core/presentation/styles/kw_box_decorations.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_app_bar.dart';
import 'package:krow/core/presentation/widgets/scroll_layout_helper.dart';
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_bloc.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_event.dart';
import 'package:krow/features/profile/role_kit/domain/bloc/role_kit_state.dart';
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
@RoutePage()
class RolesKitListScreen extends StatefulWidget {
const RolesKitListScreen({super.key, });
@override
State<RolesKitListScreen> createState() => _RolesKitListScreenState();
}
class _RolesKitListScreenState extends State<RolesKitListScreen> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<RoleKitBloc, RoleKitState>(
builder: (context, state) {
return Scaffold(
appBar: KwAppBar(
titleText: state.roleKitType == RoleKitType.uniform
? 'uniform'.tr()
: 'equipment'.tr(),
),
body: ModalProgressHUD(
inAsyncCall: state.loading,
child: ScrollLayoutHelper(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
upperWidget: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'roles'.tr().toUpperCase(),
style: AppTextStyles.captionReg
.copyWith(color: AppColors.blackCaptionGreen),
),
...state.roles.map((role) => _buildKitWidget(role,state.roleKitType)),
const Gap(24),
]),
lowerWidget: const SizedBox.shrink(),
),
),
);
},
);
}
Widget _buildKitWidget(StaffRole role, RoleKitType roleKitType ) {
return GestureDetector(
onTap: () async{
context.read<RoleKitBloc>().add(RoleKitEventSelectRole(role: role));
await context.router.push(const RoleKitRoute());
context.read<RoleKitBloc>().add(RoleKitEventFetch(type: roleKitType));
},
child: Container(
height: 56,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.only(top: 8),
decoration: KwBoxDecorations.primaryLight8,
child: Row(
children: [
Expanded(
child: Text(
role.skill?.name ?? '',
style: AppTextStyles.headingH3,
),
),
const Gap(16),
Assets.images.icons.caretRight.svg()
],
),
),
);
}
}