feat: legacy mobile apps created
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import 'dart:io';
|
||||
|
||||
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/data/models/pagination_wrapper/pagination_wrapper.dart';
|
||||
import 'package:krow/features/profile/certificates/data/models/certificate_model.dart';
|
||||
import 'package:krow/features/profile/certificates/data/models/staff_certificate.dart';
|
||||
|
||||
import 'certificates_gql.dart';
|
||||
|
||||
@injectable
|
||||
class CertificatesApiProvider {
|
||||
final ApiClient _apiClient;
|
||||
|
||||
CertificatesApiProvider(this._apiClient);
|
||||
|
||||
Future<List<CertificateModel>> fetchCertificates() async {
|
||||
var result = await _apiClient.query(schema: getCertificatesQuery);
|
||||
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
|
||||
return result.data!['certificates'].map<CertificateModel>((e) {
|
||||
return CertificateModel.fromJson(e);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<PaginationWrapper<StaffCertificate>> fetchStaffCertificates() async {
|
||||
var result = await _apiClient.query(schema: getStaffCertificatesQuery);
|
||||
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
|
||||
return PaginationWrapper.fromJson(result.data!['staff_certificates'],
|
||||
(json) => StaffCertificate.fromJson(json));
|
||||
}
|
||||
|
||||
Future<StaffCertificate> putStaffCertificate(
|
||||
String certificateId, String imagePath, String certificateDate) async {
|
||||
var byteData = File(imagePath).readAsBytesSync();
|
||||
|
||||
var multipartFile = MultipartFile.fromBytes(
|
||||
'file',
|
||||
byteData,
|
||||
filename: '${DateTime.now().millisecondsSinceEpoch}.jpg',
|
||||
contentType: MediaType('image', 'jpg'),
|
||||
);
|
||||
|
||||
final Map<String, dynamic> variables = {
|
||||
'certificate_id': certificateId,
|
||||
'expiration_date': certificateDate,
|
||||
'file': multipartFile,
|
||||
};
|
||||
var result = await _apiClient.mutate(
|
||||
schema: putStaffCertificateMutation, body: {'input': variables});
|
||||
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
} else {
|
||||
return StaffCertificate.fromJson(
|
||||
result.data!['upload_staff_certificate']);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteStaffCertificate(String certificateId) async {
|
||||
final Map<String, dynamic> variables = {
|
||||
'id': certificateId,
|
||||
};
|
||||
var result = await _apiClient.mutate(
|
||||
schema: deleteStaffCertificateMutation, body: variables);
|
||||
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
const String getCertificatesQuery = '''
|
||||
{
|
||||
certificates {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const String getStaffCertificatesQuery = '''
|
||||
query fetchStaffCertificates () {
|
||||
staff_certificates(first: 10) {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
expiration_date
|
||||
status
|
||||
file
|
||||
certificate {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const String putStaffCertificateMutation = '''
|
||||
mutation UploadStaffCertificate(\$input: UploadStaffCertificateInput!) {
|
||||
upload_staff_certificate(input: \$input) {
|
||||
id
|
||||
expiration_date
|
||||
status
|
||||
file
|
||||
certificate {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const String deleteStaffCertificateMutation = '''
|
||||
mutation DeleteStaffCertificate(\$id: ID!) {
|
||||
delete_staff_certificate(id: \$id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
''';
|
||||
@@ -0,0 +1,24 @@
|
||||
class CertificateModel {
|
||||
final String id;
|
||||
final String name;
|
||||
|
||||
|
||||
CertificateModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
factory CertificateModel.fromJson(Map<String, dynamic> json) {
|
||||
return CertificateModel(
|
||||
id: json['id'],
|
||||
name: json['name'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/features/profile/certificates/data/certificates_api_provider.dart';
|
||||
import 'package:krow/features/profile/certificates/domain/certificates_repository.dart';
|
||||
import 'package:krow/features/profile/certificates/data/models/certificate_model.dart';
|
||||
import 'package:krow/features/profile/certificates/data/models/staff_certificate.dart';
|
||||
|
||||
@Injectable(as: CertificatesRepository)
|
||||
class CertificatesRepositoryImpl extends CertificatesRepository {
|
||||
final CertificatesApiProvider _certificatesApiProvider;
|
||||
|
||||
CertificatesRepositoryImpl(this._certificatesApiProvider);
|
||||
|
||||
@override
|
||||
Future<List<CertificateModel>> getCertificates() async {
|
||||
return _certificatesApiProvider.fetchCertificates();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<StaffCertificate> putStaffCertificate(
|
||||
String certificateId, String imagePath, String certificateDate) {
|
||||
return _certificatesApiProvider.putStaffCertificate(
|
||||
certificateId, imagePath, certificateDate);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<StaffCertificate>> getStaffCertificates() async{
|
||||
return (await _certificatesApiProvider.fetchStaffCertificates()).edges.map((e) => e.node).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteStaffCertificate(String certificateId) {
|
||||
return _certificatesApiProvider.deleteStaffCertificate(certificateId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:krow/features/profile/certificates/data/models/certificate_model.dart';
|
||||
|
||||
part 'staff_certificate.g.dart';
|
||||
|
||||
enum CertificateStatus {
|
||||
verified,
|
||||
pending,
|
||||
declined,
|
||||
}
|
||||
|
||||
@JsonSerializable(fieldRename: FieldRename.snake)
|
||||
class StaffCertificate {
|
||||
final String id;
|
||||
CertificateModel certificate;
|
||||
final String expirationDate;
|
||||
final CertificateStatus status;
|
||||
final String file;
|
||||
|
||||
StaffCertificate(
|
||||
{required this.id,
|
||||
required this.certificate,
|
||||
required this.expirationDate,
|
||||
required this.status,
|
||||
required this.file});
|
||||
|
||||
factory StaffCertificate.fromJson(Map<String, dynamic> json) {
|
||||
return _$StaffCertificateFromJson(json);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => _$StaffCertificateToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/features/profile/certificates/domain/certificates_repository.dart';
|
||||
import 'package:krow/features/profile/certificates/data/models/staff_certificate.dart';
|
||||
import 'package:krow/features/profile/certificates/domain/bloc/certificates_event.dart';
|
||||
import 'package:krow/features/profile/certificates/domain/bloc/certificates_state.dart';
|
||||
|
||||
class CertificatesBloc extends Bloc<CertificatesEvent, CertificatesState> {
|
||||
CertificatesBloc() : super(CertificatesState()) {
|
||||
on<CertificatesEventFetch>(_onFetch);
|
||||
on<CertificatesEventSubmit>(_onSubmit);
|
||||
on<CertificatesEventUpload>(_onUploadPhoto);
|
||||
on<CertificatesEventDelete>(_onDeleteCertificate);
|
||||
}
|
||||
|
||||
void _onFetch(
|
||||
CertificatesEventFetch event, Emitter<CertificatesState> emit) async {
|
||||
emit(state.copyWith(loading: true));
|
||||
var certificates = await getIt<CertificatesRepository>().getCertificates();
|
||||
List<StaffCertificate> staffCertificates =
|
||||
await getIt<CertificatesRepository>().getStaffCertificates();
|
||||
var items = certificates.map((certificate) {
|
||||
var staffCertificate = staffCertificates.firstWhereOrNull(
|
||||
(e) => certificate.id == e.certificate.id,
|
||||
);
|
||||
|
||||
return CertificatesViewModel(
|
||||
id: staffCertificate?.id,
|
||||
certificateId: certificate.id,
|
||||
title: certificate.name,
|
||||
imageUrl: staffCertificate?.file,
|
||||
expirationDate: staffCertificate?.expirationDate,
|
||||
status: staffCertificate?.status,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
emit(state.copyWith(
|
||||
loading: false,
|
||||
certificatesItems: items,
|
||||
));
|
||||
}
|
||||
|
||||
void _onUploadPhoto(
|
||||
CertificatesEventUpload event, Emitter<CertificatesState> emit) async {
|
||||
event.item.uploading = true;
|
||||
emit(state.copyWith());
|
||||
|
||||
try {
|
||||
var split = event.expirationDate.split('.').reversed;
|
||||
split = [split.elementAt(0), split.elementAt(2), split.elementAt(1)];
|
||||
var formattedDate = split.join('-');
|
||||
var newCertificate = await getIt<CertificatesRepository>()
|
||||
.putStaffCertificate(
|
||||
event.item.certificateId, event.path, formattedDate);
|
||||
event.item.applyNewStaffCertificate(newCertificate);
|
||||
} finally {
|
||||
event.item.uploading = false;
|
||||
emit(state.copyWith());
|
||||
}
|
||||
}
|
||||
|
||||
void _onSubmit(
|
||||
CertificatesEventSubmit event, Emitter<CertificatesState> emit) async {
|
||||
final allCertUploaded = state.certificatesItems.every((element) {
|
||||
return element.certificateId == '3' ||
|
||||
element.status == CertificateStatus.pending ||
|
||||
element.status == CertificateStatus.verified;
|
||||
});
|
||||
|
||||
if (allCertUploaded) {
|
||||
emit(state.copyWith(success: true));
|
||||
} else {
|
||||
emit(state.copyWith(showError: true));
|
||||
}
|
||||
}
|
||||
|
||||
void _onDeleteCertificate(
|
||||
CertificatesEventDelete event, Emitter<CertificatesState> emit) async {
|
||||
emit(state.copyWith(loading: true));
|
||||
try {
|
||||
await getIt<CertificatesRepository>()
|
||||
.deleteStaffCertificate(event.item.id ?? '0');
|
||||
state.certificatesItems
|
||||
.firstWhere((element) => element.id == event.item.id)
|
||||
.clear();
|
||||
} finally {
|
||||
emit(state.copyWith(loading: false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:krow/features/profile/certificates/domain/bloc/certificates_state.dart';
|
||||
|
||||
sealed class CertificatesEvent {}
|
||||
|
||||
class CertificatesEventFetch extends CertificatesEvent {}
|
||||
|
||||
class CertificatesEventSubmit extends CertificatesEvent {}
|
||||
|
||||
class CertificatesEventUpload extends CertificatesEvent {
|
||||
final String path;
|
||||
final String expirationDate;
|
||||
final CertificatesViewModel item;
|
||||
|
||||
CertificatesEventUpload(
|
||||
{required this.path, required this.expirationDate, required this.item});
|
||||
}
|
||||
|
||||
class CertificatesEventDelete extends CertificatesEvent {
|
||||
final CertificatesViewModel item;
|
||||
|
||||
CertificatesEventDelete(this.item);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import 'package:krow/features/profile/certificates/data/models/staff_certificate.dart';
|
||||
|
||||
class CertificatesState {
|
||||
final bool showError;
|
||||
final bool loading;
|
||||
final bool success;
|
||||
List<CertificatesViewModel> certificatesItems = [];
|
||||
|
||||
CertificatesState(
|
||||
{this.showError = false,
|
||||
this.certificatesItems = const [],
|
||||
this.loading = false,
|
||||
this.success = false});
|
||||
|
||||
copyWith(
|
||||
{bool? showError,
|
||||
List<CertificatesViewModel>? certificatesItems,
|
||||
bool? loading,
|
||||
bool? success}) {
|
||||
return CertificatesState(
|
||||
showError: showError ?? this.showError,
|
||||
certificatesItems: certificatesItems ?? this.certificatesItems,
|
||||
loading: loading ?? false,
|
||||
success: success ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CertificatesViewModel {
|
||||
String? id;
|
||||
String certificateId;
|
||||
final String title;
|
||||
bool uploading;
|
||||
String? imageUrl;
|
||||
String? expirationDate;
|
||||
CertificateStatus? status;
|
||||
|
||||
CertificatesViewModel({
|
||||
this.id,
|
||||
required this.certificateId,
|
||||
required this.title,
|
||||
this.expirationDate,
|
||||
this.imageUrl,
|
||||
this.uploading = false,
|
||||
this.status,
|
||||
});
|
||||
|
||||
void clear() {
|
||||
id = null;
|
||||
imageUrl = null;
|
||||
expirationDate = null;
|
||||
status = null;
|
||||
}
|
||||
|
||||
void applyNewStaffCertificate(StaffCertificate newCertificate) {
|
||||
id = newCertificate.id;
|
||||
imageUrl = newCertificate.file;
|
||||
expirationDate = newCertificate.expirationDate;
|
||||
status = newCertificate.status;
|
||||
}
|
||||
|
||||
bool get isExpired {
|
||||
if (expirationDate == null) return false;
|
||||
DateTime expiration = DateTime.parse(expirationDate!);
|
||||
DateTime now = DateTime.now();
|
||||
return now.isAfter(expiration);
|
||||
}
|
||||
|
||||
String getExpirationInfo() {
|
||||
if (expirationDate == null) return '';
|
||||
DateTime expiration = DateTime.parse(expirationDate!);
|
||||
DateTime now = DateTime.now();
|
||||
Duration difference = expiration.difference(now);
|
||||
String formatted =
|
||||
'${expiration.month.toString().padLeft(2, '0')}.${expiration.day.toString().padLeft(2, '0')}.${expiration.year}';
|
||||
|
||||
if (difference.inDays <= 0) {
|
||||
return '$formatted (expired)';
|
||||
} else if (difference.inDays < 14) {
|
||||
return '$formatted (expires in ${difference.inDays} days)';
|
||||
} else {
|
||||
return formatted;
|
||||
}
|
||||
}
|
||||
|
||||
bool expireSoon() {
|
||||
if (expirationDate == null) return false;
|
||||
DateTime expiration = DateTime.parse(expirationDate!);
|
||||
DateTime now = DateTime.now();
|
||||
Duration difference = expiration.difference(now);
|
||||
return difference.inDays <= 14;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:krow/features/profile/certificates/data/models/certificate_model.dart';
|
||||
import 'package:krow/features/profile/certificates/data/models/staff_certificate.dart';
|
||||
|
||||
abstract class CertificatesRepository {
|
||||
Future<List<CertificateModel>> getCertificates();
|
||||
|
||||
Future<List<StaffCertificate>> getStaffCertificates();
|
||||
|
||||
Future<StaffCertificate> putStaffCertificate(
|
||||
String certificateId, String imagePath, String certificateDate);
|
||||
|
||||
Future<void> deleteStaffCertificate(String certificateId);
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
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:image_picker/image_picker.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/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/certificates/data/models/staff_certificate.dart';
|
||||
import 'package:krow/features/profile/certificates/domain/bloc/certificates_bloc.dart';
|
||||
import 'package:krow/features/profile/certificates/domain/bloc/certificates_event.dart';
|
||||
import 'package:krow/features/profile/certificates/domain/bloc/certificates_state.dart';
|
||||
import 'package:krow/features/profile/certificates/presentation/screen/certificates_upload_dialog.dart';
|
||||
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
|
||||
|
||||
@RoutePage()
|
||||
class CertificatesScreen extends StatelessWidget implements AutoRouteWrapper {
|
||||
const CertificatesScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => CertificatesBloc()..add(CertificatesEventFetch()),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: 'certificates'.tr(),
|
||||
),
|
||||
body: BlocConsumer<CertificatesBloc, CertificatesState>(
|
||||
listener: (context, state) {
|
||||
if (state.success) {
|
||||
context.router.maybePop();
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return ModalProgressHUD(
|
||||
inAsyncCall: state.loading,
|
||||
child: ScrollLayoutHelper(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
upperWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'please_indicate_certificates'.tr(),
|
||||
style: AppTextStyles.bodyTinyMed
|
||||
.copyWith(color: AppColors.blackGray),
|
||||
),
|
||||
if (state.showError) _buildErrorMessage(),
|
||||
const Gap(24),
|
||||
_buildUploadProofItems(
|
||||
context,
|
||||
state.certificatesItems,
|
||||
state.showError,
|
||||
),
|
||||
],
|
||||
),
|
||||
lowerWidget: KwButton.primary(
|
||||
label: 'confirm'.tr(),
|
||||
onPressed: () {
|
||||
BlocProvider.of<CertificatesBloc>(context)
|
||||
.add(CertificatesEventSubmit());
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildUploadProofItems(
|
||||
context,
|
||||
List<CertificatesViewModel> items,
|
||||
bool showError,
|
||||
) {
|
||||
return Column(
|
||||
children: items.map(
|
||||
(item) {
|
||||
Color? statusColor = _getStatusColor(showError, item);
|
||||
|
||||
return UploadImageCard(
|
||||
title: item.title,
|
||||
onSelectImage: () {
|
||||
ImagePicker()
|
||||
.pickImage(source: ImageSource.gallery)
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
_showSelectCertificateDialog(context, item, value.path);
|
||||
}
|
||||
});
|
||||
},
|
||||
onDeleteTap: () {
|
||||
BlocProvider.of<CertificatesBloc>(context)
|
||||
.add(CertificatesEventDelete(item));
|
||||
},
|
||||
onTap: () {},
|
||||
imageUrl: item.imageUrl,
|
||||
inUploading: item.uploading,
|
||||
statusColor: statusColor,
|
||||
message: showError && item.imageUrl == null
|
||||
? 'availability_requires_confirmation'.tr()
|
||||
: item.status == null
|
||||
? 'supported_format'.tr()
|
||||
: (item.status!.name[0].toUpperCase() +
|
||||
item.status!.name.substring(1).toLowerCase()),
|
||||
hasError: showError,
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildExpirationRow(item),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Color? _getStatusColor(bool showError, CertificatesViewModel item) {
|
||||
var statusColor = (showError && item.status == null) ||
|
||||
item.status == CertificateStatus.declined
|
||||
? AppColors.statusError
|
||||
: item.status == CertificateStatus.verified
|
||||
? AppColors.statusSuccess
|
||||
: item.status == CertificateStatus.pending
|
||||
? AppColors.primaryBlue
|
||||
: null;
|
||||
return statusColor;
|
||||
}
|
||||
|
||||
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(
|
||||
'listed_certificates_mandatory'.tr(),
|
||||
style: AppTextStyles.bodyTinyMed
|
||||
.copyWith(color: AppColors.statusError),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExpirationRow(CertificatesViewModel item) {
|
||||
var bgColor = item.isExpired
|
||||
? AppColors.tintRed
|
||||
: item.expireSoon()
|
||||
? AppColors.tintYellow
|
||||
: AppColors.tintGray;
|
||||
|
||||
var textColor = item.isExpired
|
||||
? AppColors.statusError
|
||||
: item.expireSoon()
|
||||
? AppColors.statusWarningBody
|
||||
: null;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 6, right: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 6, right: 6, top: 3, bottom: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor, borderRadius: BorderRadius.circular(4)),
|
||||
child: Text(
|
||||
'${'expiration_date'.tr()} ',
|
||||
style: AppTextStyles.bodyTinyReg.copyWith(color: textColor),
|
||||
)),
|
||||
const Gap(7),
|
||||
if (item.expirationDate != null)
|
||||
Text(
|
||||
item.getExpirationInfo(),
|
||||
style: AppTextStyles.bodyTinyMed
|
||||
.copyWith(color: textColor ?? AppColors.blackGray),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showSelectCertificateDialog(
|
||||
context, CertificatesViewModel item, imagePath) {
|
||||
CertificatesUploadDialog.show(context, imagePath).then((expireDate) {
|
||||
if (expireDate != null) {
|
||||
BlocProvider.of<CertificatesBloc>(context).add(CertificatesEventUpload(
|
||||
item: item, path: imagePath, expirationDate: expireDate));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/application/common/text_formatters/expiration_date_formatter.dart';
|
||||
import 'package:krow/core/application/common/validators/certificate_date_validator.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.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_button.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_input.dart';
|
||||
|
||||
class CertificatesUploadDialog extends StatefulWidget {
|
||||
final String path;
|
||||
|
||||
const CertificatesUploadDialog({
|
||||
super.key,
|
||||
required this.path,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CertificatesUploadDialog> createState() =>
|
||||
_CertificatesUploadDialogState();
|
||||
|
||||
static Future<String?> show(BuildContext context, String path) {
|
||||
return showDialog<String?>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CertificatesUploadDialog(
|
||||
path: path,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CertificatesUploadDialogState extends State<CertificatesUploadDialog> {
|
||||
final TextEditingController expiryDateController = TextEditingController();
|
||||
String? inputError;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
expiryDateController.addListener(() {
|
||||
if(expiryDateController.text.length == 10) {
|
||||
inputError =
|
||||
CertificateDateValidator.validate(expiryDateController.text);
|
||||
}else{
|
||||
inputError = null;
|
||||
}
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Center(
|
||||
child: Container(
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
..._buildDialogTitle(context),
|
||||
Material(
|
||||
type: MaterialType.transparency,
|
||||
child: KwTextInput(
|
||||
title: 'expiry_date_1'.tr(),
|
||||
hintText: 'enter_certificate_expiry_date'.tr(),
|
||||
controller: expiryDateController,
|
||||
maxLength: 10,
|
||||
inputFormatters: [DateTextFormatter()],
|
||||
keyboardType: TextInputType.datetime,
|
||||
helperText: inputError,
|
||||
showError: inputError != null,
|
||||
),
|
||||
),
|
||||
const Gap(12),
|
||||
_buildImage(context),
|
||||
const Gap(24),
|
||||
KwButton.primary(
|
||||
label: 'save_certificate'.tr(),
|
||||
disabled: expiryDateController.text.length != 10 || inputError != null,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(expiryDateController.text);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
ClipRRect _buildImage(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Image.file(
|
||||
File(widget.path),
|
||||
width: MediaQuery.of(context).size.width - 80,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildDialogTitle(BuildContext context) {
|
||||
return [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'add_certificate_expiry_date'.tr(),
|
||||
style: AppTextStyles.bodyLargeMed,
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Assets.images.icons.x.svg(),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'please_enter_expiry_date'.tr(),
|
||||
style: AppTextStyles.bodySmallReg.copyWith(color: AppColors.blackGray),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user