feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
const String updateStaffRatingMutationSchema = '''
|
||||
mutation updateStaffRating(\$input: RateClientStaffInput!) {
|
||||
rate_client_staff(input: \$input) {
|
||||
id
|
||||
}
|
||||
}
|
||||
''';
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:graphql_flutter/graphql_flutter.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/core/application/clients/api/api_client.dart';
|
||||
import 'package:krow/features/rate_staff/data/gql.dart';
|
||||
|
||||
@singleton
|
||||
class ClientProfileApiProvider {
|
||||
ClientProfileApiProvider(this._client);
|
||||
|
||||
final ApiClient _client;
|
||||
|
||||
Future<void> updateStaffRating(
|
||||
{required String positionId,
|
||||
required int rating,
|
||||
String? reason,
|
||||
String? details,
|
||||
bool? isBlocked ,
|
||||
bool? isFavorite }) async {
|
||||
final QueryResult result = await _client.mutate(
|
||||
schema: updateStaffRatingMutationSchema,
|
||||
body: {
|
||||
'input': {
|
||||
'position_staff_id': positionId,
|
||||
'rating': rating,
|
||||
if (reason != null && reason.isNotEmpty) 'reason': reason,
|
||||
if (details != null && details.isNotEmpty) 'details': details.trim(),
|
||||
'is_blocked': isBlocked??false,
|
||||
'is_favorite': isFavorite??false,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
abstract class RatingStaffRepository {
|
||||
Future<void> rateStaff(
|
||||
{required String positionId,
|
||||
required int rating,
|
||||
String? reason,
|
||||
String? details,
|
||||
bool isBlocked = false,
|
||||
bool isFavorite = false});
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/core/entity/staff_contact_entity.dart';
|
||||
import 'package:krow/features/rate_staff/data/rating_staff_repository.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
part 'rating_staff_event.dart';
|
||||
part 'rating_staff_state.dart';
|
||||
|
||||
class RatingStaffBloc extends Bloc<RatingStaffEvent, RatingStaffState> {
|
||||
RatingStaffBloc(StaffContact staff) : super(RatingStaffState(staff: staff, favorite: staff.isFavorite, blackListed: staff.isBlackListed)) {
|
||||
on<RatingChangedEvent>(_onRatingChanged);
|
||||
on<StaffFavoriteToggleEvent>(_onStaffFavoriteToggle);
|
||||
on<StaffBlacklistToggleEvent>(_onStaffBlacklistToggle);
|
||||
on<StaffReasonSelectedEvent>(_onStaffReasonSelected);
|
||||
on<StaffReasonDetailsChangeEvent>(_onStaffReasonDetailsChange);
|
||||
on<RatingSubmitEvent>(_onRatingSubmit);
|
||||
}
|
||||
|
||||
void _onRatingChanged(RatingChangedEvent event, emit) {
|
||||
emit(state.copyWith(rating: event.rating));
|
||||
}
|
||||
|
||||
void _onStaffFavoriteToggle(StaffFavoriteToggleEvent event, emit) {
|
||||
emit(state.copyWith(favorite: !state.favorite));
|
||||
}
|
||||
|
||||
void _onStaffBlacklistToggle(StaffBlacklistToggleEvent event, emit) {
|
||||
emit(state.copyWith(blackListed: !state.blackListed));
|
||||
}
|
||||
|
||||
void _onStaffReasonSelected(StaffReasonSelectedEvent event, emit) {
|
||||
emit(state.copyWith(reason: event.reason));
|
||||
}
|
||||
|
||||
void _onStaffReasonDetailsChange(
|
||||
StaffReasonDetailsChangeEvent event, emit) {
|
||||
emit(state.copyWith(comment: event.details));
|
||||
}
|
||||
|
||||
void _onRatingSubmit(RatingSubmitEvent event, emit) async {
|
||||
//todo change staff entity;
|
||||
try {
|
||||
await getIt<RatingStaffRepository>().rateStaff(
|
||||
positionId: state.staff.id,
|
||||
rating: state.rating,
|
||||
reason: state.reason,
|
||||
details: state.comment,
|
||||
isBlocked: state.blackListed,
|
||||
isFavorite: state.favorite,
|
||||
);
|
||||
} catch (e) {
|
||||
emit(state.copyWith(error: e.toString()));
|
||||
return;
|
||||
}
|
||||
state.staff.rating.value = state.rating.toDouble();
|
||||
emit(state.copyWith(success: true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
part of 'rating_staff_bloc.dart';
|
||||
|
||||
@immutable
|
||||
sealed class RatingStaffEvent {}
|
||||
|
||||
class RatingChangedEvent extends RatingStaffEvent {
|
||||
final int rating;
|
||||
|
||||
RatingChangedEvent(this.rating);
|
||||
}
|
||||
|
||||
class StaffFavoriteToggleEvent extends RatingStaffEvent {}
|
||||
|
||||
class StaffBlacklistToggleEvent extends RatingStaffEvent {}
|
||||
|
||||
class StaffReasonSelectedEvent extends RatingStaffEvent {
|
||||
final String reason;
|
||||
|
||||
StaffReasonSelectedEvent(this.reason);
|
||||
}
|
||||
|
||||
class StaffReasonDetailsChangeEvent extends RatingStaffEvent {
|
||||
final String details;
|
||||
|
||||
StaffReasonDetailsChangeEvent(this.details);
|
||||
}
|
||||
|
||||
class RatingSubmitEvent extends RatingStaffEvent {}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
part of 'rating_staff_bloc.dart';
|
||||
|
||||
class RatingStaffState {
|
||||
StaffContact staff;
|
||||
int rating;
|
||||
bool favorite = false;
|
||||
bool blackListed = false;
|
||||
String? reason;
|
||||
String comment = '';
|
||||
String error = '';
|
||||
bool success = false;
|
||||
|
||||
RatingStaffState(
|
||||
{required this.staff,
|
||||
this.rating = 5,
|
||||
this.favorite = false,
|
||||
this.blackListed = false,
|
||||
this.reason,
|
||||
this.comment = '',
|
||||
this.success = false,
|
||||
this.error = ''});
|
||||
|
||||
copyWith({
|
||||
StaffContact? staff,
|
||||
int? rating,
|
||||
bool? favorite,
|
||||
bool? blackListed,
|
||||
String? reason,
|
||||
String? comment,
|
||||
String? error,
|
||||
bool? success,
|
||||
}) {
|
||||
return RatingStaffState(
|
||||
staff: staff ?? this.staff,
|
||||
rating: rating ?? this.rating,
|
||||
favorite: favorite ?? this.favorite,
|
||||
blackListed: blackListed ?? this.blackListed,
|
||||
reason: reason ?? this.reason,
|
||||
comment: comment ?? this.comment,
|
||||
error: error ?? '',
|
||||
success: success ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/features/rate_staff/data/rating_staff_provider.dart';
|
||||
import 'package:krow/features/rate_staff/data/rating_staff_repository.dart';
|
||||
|
||||
@Singleton(as: RatingStaffRepository)
|
||||
class RatingStaffRepositoryImpl implements RatingStaffRepository {
|
||||
RatingStaffRepositoryImpl({
|
||||
required ClientProfileApiProvider apiProvider,
|
||||
}) : _apiProvider = apiProvider;
|
||||
|
||||
final ClientProfileApiProvider _apiProvider;
|
||||
|
||||
@override
|
||||
Future<void> rateStaff(
|
||||
{required String positionId,
|
||||
required int rating,
|
||||
String? reason,
|
||||
String? details,
|
||||
bool isBlocked = false,
|
||||
bool isFavorite = false}) {
|
||||
return _apiProvider.updateStaffRating(
|
||||
positionId: positionId,
|
||||
rating: rating,
|
||||
reason: reason,
|
||||
details: details,
|
||||
isBlocked: isBlocked,
|
||||
isFavorite: isFavorite,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/entity/staff_contact_entity.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/staff_position_details_widget.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/check_box.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_dropdown.dart';
|
||||
import 'package:krow/core/presentation/widgets/ui_kit/kw_input.dart';
|
||||
import 'package:krow/features/rate_staff/domain/bloc/rating_staff_bloc.dart';
|
||||
import 'package:krow/features/rate_staff/presentation/widgets/rating_widget.dart';
|
||||
|
||||
@RoutePage()
|
||||
class RateStaffScreen extends StatelessWidget implements AutoRouteWrapper {
|
||||
final StaffContact staff;
|
||||
final TextEditingController _reasonController = TextEditingController();
|
||||
|
||||
RateStaffScreen({super.key, required this.staff});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: 'Rate Staff',
|
||||
),
|
||||
body: BlocConsumer<RatingStaffBloc, RatingStaffState>(
|
||||
listener: (context, state) {
|
||||
if (state.error.isNotEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.error),
|
||||
backgroundColor: AppColors.statusError,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (state.success) {
|
||||
context.router.maybePop();
|
||||
}
|
||||
},
|
||||
builder: (context, state) {
|
||||
return ScrollLayoutHelper(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||||
upperWidget: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Gap(16),
|
||||
StaffPositionAvatar(
|
||||
imageUrl: staff.photoUrl,
|
||||
userName: '${staff.firstName} ${staff.lastName}'),
|
||||
const Gap(16),
|
||||
StaffContactsWidget(staff: staff),
|
||||
const Gap(12),
|
||||
StaffPositionDetailsWidget(staff: staff),
|
||||
const Gap(12),
|
||||
const RatingWidget(),
|
||||
if (state.rating < 3) ..._blackListCheckbox(context, state),
|
||||
const Gap(24),
|
||||
],
|
||||
),
|
||||
lowerWidget: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 36, left: 16, right: 16),
|
||||
child: KwButton.primary(
|
||||
disabled: state.rating < 3 && state.reason == null,
|
||||
onPressed: () {
|
||||
BlocProvider.of<RatingStaffBloc>(context).add(
|
||||
RatingSubmitEvent(),
|
||||
);
|
||||
},
|
||||
label: 'Submit Review'),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _blackListCheckbox(
|
||||
BuildContext context, RatingStaffState state) {
|
||||
return [
|
||||
const Gap(12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
BlocProvider.of<RatingStaffBloc>(context)
|
||||
.add(StaffBlacklistToggleEvent());
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const Gap(32),
|
||||
KWCheckBox(
|
||||
value: state.blackListed,
|
||||
style: CheckBoxStyle.black,
|
||||
),
|
||||
const Gap(6),
|
||||
Text(
|
||||
'Don\'t assign to my Events anymore',
|
||||
style: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: AppColors.blackGray),
|
||||
),
|
||||
const Gap(16),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 24, left: 16, right: 16),
|
||||
child: KwDropdown<String>(
|
||||
title: 'Reason',
|
||||
items: ['Lack of responsibility', 'Unpreparedness', 'Poor communication','Unprofessional behavior','Ignoring Responsibilities']
|
||||
.map((e) => KwDropDownItem(data: e, title: e))
|
||||
.toList(),
|
||||
horizontalPadding: 16,
|
||||
hintText: 'Select a reason',
|
||||
selectedItem: state.reason != null
|
||||
? KwDropDownItem(data: state.reason!, title: state.reason!)
|
||||
: null,
|
||||
onSelected: (String item) {
|
||||
BlocProvider.of<RatingStaffBloc>(context)
|
||||
.add(StaffReasonSelectedEvent(item));
|
||||
},
|
||||
),
|
||||
),
|
||||
const Gap(8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0, left: 16, right: 16),
|
||||
child: KwTextInput(
|
||||
onChanged: (String value) {
|
||||
BlocProvider.of<RatingStaffBloc>(context)
|
||||
.add(StaffReasonDetailsChangeEvent(value));
|
||||
},
|
||||
minHeight: 144,
|
||||
maxLength: 300,
|
||||
showCounter: true,
|
||||
radius: 12,
|
||||
title: 'Additional Details',
|
||||
hintText: 'Enter your reason here...',
|
||||
controller: _reasonController,
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => RatingStaffBloc(staff),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
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/features/rate_staff/domain/bloc/rating_staff_bloc.dart';
|
||||
|
||||
class RatingWidget extends StatelessWidget {
|
||||
const RatingWidget({super.key,});
|
||||
|
||||
final double maxRating = 5.0; // Maximum rating, e.g., 5
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<RatingStaffBloc, RatingStaffState>(
|
||||
builder: (context, state) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
margin: const EdgeInsets.only(left: 16, right: 16, top: 8),
|
||||
decoration: KwBoxDecorations.primaryLight12,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Client’s rate'.toUpperCase(),
|
||||
style: AppTextStyles.captionReg
|
||||
.copyWith(color: AppColors.blackCaptionText),
|
||||
),
|
||||
state.rating >= 3
|
||||
? GestureDetector(
|
||||
onTap: () {
|
||||
BlocProvider.of<RatingStaffBloc>(context)
|
||||
.add(StaffFavoriteToggleEvent());
|
||||
},
|
||||
child: state.favorite
|
||||
? Assets.images.icons.heart.svg()
|
||||
: Assets.images.icons.heartEmpty.svg(),
|
||||
)
|
||||
: const SizedBox(height: 20)
|
||||
],
|
||||
),
|
||||
const Gap(4),
|
||||
Center(
|
||||
child: Text(state.rating.toStringAsFixed(1),
|
||||
style: AppTextStyles.headingH0),
|
||||
),
|
||||
const Gap(4),
|
||||
_buildRating(state.rating, context),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_buildRating(int rating, BuildContext context) {
|
||||
List<Widget> stars = [];
|
||||
for (int i = 1; i <= maxRating; i++) {
|
||||
var star;
|
||||
if (i <= rating) {
|
||||
star = (Assets.images.icons.ratingStar.star.svg());
|
||||
} else {
|
||||
star = (Assets.images.icons.ratingStar.starEmpty.svg());
|
||||
}
|
||||
stars.add(GestureDetector(
|
||||
onTap: () {
|
||||
BlocProvider.of<RatingStaffBloc>(context).add(RatingChangedEvent(i));
|
||||
},
|
||||
child: star,
|
||||
));
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [const Gap(28), ...stars, const Gap(28)],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user