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,7 @@
const String updateStaffRatingMutationSchema = '''
mutation updateStaffRating(\$input: RateClientStaffInput!) {
rate_client_staff(input: \$input) {
id
}
}
''';

View File

@@ -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());
}
}
}

View File

@@ -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});
}

View File

@@ -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));
}
}

View File

@@ -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 {}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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(
'Clients 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)],
);
}
}