feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
const String attachStaffRolesMutation = r'''
|
||||
mutation AttachStaffRoles($roles: [AttachStaffSkillInput!]!) {
|
||||
attach_staff_roles(roles: $roles){}
|
||||
}
|
||||
''';
|
||||
|
||||
const String detachStaffRolesMutation = r'''
|
||||
mutation DetachStaffRoles($ids: [ID!]!) {
|
||||
detach_staff_roles(ids: $ids)
|
||||
}
|
||||
''';
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/core/application/clients/api/api_client.dart';
|
||||
import 'package:krow/core/application/clients/api/gql.dart';
|
||||
import 'package:krow/core/data/models/skill.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
import 'package:krow/features/profile/role/data/gql.dart';
|
||||
|
||||
@injectable
|
||||
class StaffRoleApiProvider {
|
||||
final ApiClient _apiClient;
|
||||
|
||||
StaffRoleApiProvider(this._apiClient);
|
||||
|
||||
Future<List<StaffRole>> fetchStaffRoles() async {
|
||||
var result = await _apiClient.query(schema: getStaffRolesQuery);
|
||||
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
|
||||
return result.data!['staff_roles'].map<StaffRole>((e) {
|
||||
return StaffRole.fromJson(e);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future<List<Skill>> fetchSkills() {
|
||||
return _apiClient.query(schema: getSkillsQuery).then((result) {
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
|
||||
return result.data!['skills'].map<Skill>((e) {
|
||||
return Skill.fromJson(e);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<String> saveStaffRole(StaffRole role) {
|
||||
return _apiClient.mutate(schema: attachStaffRolesMutation, body: {
|
||||
'roles': [
|
||||
{
|
||||
'level': role.level.toString().split('.').last,
|
||||
'experience': role.experience,
|
||||
'skill_id': role.skill!.id,
|
||||
}
|
||||
],
|
||||
}).then((result) {
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> deleteStaffRole(StaffRole role) {
|
||||
return _apiClient.mutate(schema: detachStaffRolesMutation, body: {
|
||||
'ids': [role.skill?.id],
|
||||
}).then((result) {
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/core/data/models/skill.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
import 'package:krow/features/profile/role/data/staff_role_api.dart';
|
||||
import 'package:krow/features/profile/role/domain/staff_role_repository.dart';
|
||||
|
||||
@Injectable(as: StaffRoleRepository)
|
||||
class StaffRoleRepositoryImpl extends StaffRoleRepository {
|
||||
final StaffRoleApiProvider _staffRoleApi;
|
||||
|
||||
StaffRoleRepositoryImpl(this._staffRoleApi);
|
||||
|
||||
@override
|
||||
Future<List<StaffRole>> getStaffRole() async {
|
||||
return _staffRoleApi.fetchStaffRoles();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Skill>> getSkills() {
|
||||
return _staffRoleApi.fetchSkills();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> saveStaffRole(StaffRole role) {
|
||||
return _staffRoleApi.saveStaffRole(role);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteStaffRole(StaffRole role) {
|
||||
return _staffRoleApi.deleteStaffRole(role);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/core/data/enums/staff_skill_enums.dart';
|
||||
import 'package:krow/core/data/models/skill.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
import 'package:krow/features/profile/role/domain/bloc/role_event.dart';
|
||||
import 'package:krow/features/profile/role/domain/bloc/role_state.dart';
|
||||
import 'package:krow/features/profile/role/domain/staff_role_repository.dart';
|
||||
|
||||
class RoleBloc extends Bloc<RoleEvent, RoleState> {
|
||||
List<Skill>? skills;
|
||||
List<StaffRole> staffRoles = [];
|
||||
|
||||
RoleBloc() : super(const RoleState(staffRoles: [], isLoading: true)) {
|
||||
on<RoleFetchEvent>(_onFetch);
|
||||
on<RoleSelectEvent>(_onSelect);
|
||||
on<RoleSpawnEvent>(_onSpawn);
|
||||
on<RoleDeleteEvent>(_onDelete);
|
||||
on<RoleUpdateEvent>(_onUpdate);
|
||||
on<RoleSaveChangesEvent>(_onSaveChanges);
|
||||
}
|
||||
|
||||
FutureOr<void> _onFetch(event, emit) async {
|
||||
staffRoles = await getIt<StaffRoleRepository>().getStaffRole();
|
||||
skills = await getIt<StaffRoleRepository>().getSkills();
|
||||
var availableSkills = skills!
|
||||
.where((e) => !staffRoles.any((r) => r.skill?.id == e.id))
|
||||
.toList();
|
||||
emit(RoleState(
|
||||
staffRoles: staffRoles, skills: availableSkills, isLoading: false));
|
||||
|
||||
if (staffRoles.isEmpty) {
|
||||
var staffRole = StaffRole(
|
||||
id: DateTime.now().toIso8601String(),
|
||||
level: StaffSkillLevel.beginner,
|
||||
experience: 1);
|
||||
emit(state.copyWith(editedRole: staffRole, staffRoles: [staffRole]));
|
||||
}
|
||||
}
|
||||
|
||||
FutureOr<void> _onSelect(RoleSelectEvent event, emit) async {
|
||||
emit(state.copyWith(editedRole: event.editedRole));
|
||||
}
|
||||
|
||||
FutureOr<void> _onSpawn(event, emit) async {
|
||||
var newRole = StaffRole(
|
||||
id: DateTime.now().toIso8601String(),
|
||||
level: StaffSkillLevel.beginner,
|
||||
experience: 1);
|
||||
emit(state.copyWith(
|
||||
editedRole: newRole, staffRoles: [...state.staffRoles, newRole]));
|
||||
}
|
||||
|
||||
FutureOr<void> _onDelete(RoleDeleteEvent event, emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
if (event.role.status != null) {
|
||||
await getIt<StaffRoleRepository>().deleteStaffRole(event.role);
|
||||
}
|
||||
var roles = state.staffRoles.where((e) => e != event.role).toList();
|
||||
var availableSkills =
|
||||
skills!.where((e) => !roles.any((r) => r.skill?.id == e.id)).toList();
|
||||
|
||||
emit(state.copyWith(
|
||||
editedRole:
|
||||
event.role.id == state.editedRole?.id ? null : state.editedRole,
|
||||
staffRoles: roles,
|
||||
skills: availableSkills,
|
||||
isLoading: false));
|
||||
}
|
||||
|
||||
FutureOr<void> _onUpdate(RoleUpdateEvent event, emit) async {
|
||||
var roles = state.staffRoles
|
||||
.map((e) => e.id == event.role.id ? event.role : e)
|
||||
.toList();
|
||||
|
||||
var availableSkills =
|
||||
skills!.where((e) => !roles.any((r) => r.skill?.id == e.id)).toList();
|
||||
emit(state.copyWith(
|
||||
staffRoles: roles, editedRole: event.role, skills: availableSkills));
|
||||
}
|
||||
|
||||
Future<void> _onSaveChanges(RoleSaveChangesEvent event, emit) async {
|
||||
emit(state.copyWith(isLoading: true));
|
||||
await getIt<StaffRoleRepository>().saveStaffRole(event.role);
|
||||
|
||||
List<StaffRole> roles = state.staffRoles
|
||||
.map((e) => e.id == event.role.id
|
||||
? event.role.copyWith(status: StaffSkillStatus.pending)
|
||||
: e)
|
||||
.toList();
|
||||
emit(state.copyWith(
|
||||
staffRoles: roles,
|
||||
isLoading: false,
|
||||
editedRole: StaffRole.empty(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
|
||||
@immutable
|
||||
sealed class RoleEvent {}
|
||||
|
||||
class RoleFetchEvent extends RoleEvent {
|
||||
RoleFetchEvent();
|
||||
}
|
||||
|
||||
class RoleSelectEvent extends RoleEvent {
|
||||
final StaffRole editedRole;
|
||||
|
||||
RoleSelectEvent(this.editedRole);
|
||||
}
|
||||
|
||||
class RoleSpawnEvent extends RoleEvent {
|
||||
RoleSpawnEvent();
|
||||
}
|
||||
|
||||
class RoleDeleteEvent extends RoleEvent {
|
||||
final StaffRole role;
|
||||
|
||||
RoleDeleteEvent(this.role);
|
||||
}
|
||||
|
||||
class RoleUpdateEvent extends RoleEvent {
|
||||
final StaffRole role;
|
||||
|
||||
RoleUpdateEvent(this.role);
|
||||
}
|
||||
|
||||
class RoleSaveChangesEvent extends RoleEvent {
|
||||
final StaffRole role;
|
||||
|
||||
RoleSaveChangesEvent(this.role);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:krow/core/data/models/skill.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
|
||||
@immutable
|
||||
class RoleState {
|
||||
final bool isLoading;
|
||||
final List<StaffRole> staffRoles;
|
||||
final List<Skill> skills;
|
||||
final StaffRole? editedRole;
|
||||
|
||||
const RoleState(
|
||||
{required this.staffRoles,
|
||||
this.editedRole,
|
||||
this.isLoading = false,
|
||||
this.skills = const []});
|
||||
|
||||
copyWith({
|
||||
List<StaffRole>? staffRoles,
|
||||
StaffRole? editedRole,
|
||||
bool? isLoading,
|
||||
List<Skill>? skills,
|
||||
}) {
|
||||
return RoleState(
|
||||
staffRoles: staffRoles ?? this.staffRoles,
|
||||
editedRole: editedRole ?? this.editedRole,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
skills: skills ?? this.skills,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:krow/core/data/models/skill.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
|
||||
abstract class StaffRoleRepository {
|
||||
Future<List<StaffRole>> getStaffRole();
|
||||
|
||||
Future<List<Skill>> getSkills();
|
||||
|
||||
Future<String> saveStaffRole(StaffRole role);
|
||||
|
||||
Future<void> deleteStaffRole(StaffRole role);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
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/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/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/features/profile/role/domain/bloc/role_bloc.dart';
|
||||
import 'package:krow/features/profile/role/domain/bloc/role_event.dart';
|
||||
import 'package:krow/features/profile/role/domain/bloc/role_state.dart';
|
||||
import 'package:krow/features/profile/role/presentation/widgets/role_card_widget.dart';
|
||||
import 'package:krow/features/profile/role/presentation/widgets/role_edit_card_widget.dart';
|
||||
import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart';
|
||||
|
||||
@RoutePage()
|
||||
class RoleScreen extends StatelessWidget implements AutoRouteWrapper {
|
||||
final bool isInEditMode;
|
||||
|
||||
const RoleScreen({
|
||||
super.key,
|
||||
this.isInEditMode = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: KwAppBar(
|
||||
titleText: 'role'.tr(),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: BlocBuilder<RoleBloc, RoleState>(
|
||||
builder: (context, state) {
|
||||
return ModalProgressHUD(
|
||||
inAsyncCall: state.isLoading,
|
||||
child: ScrollLayoutHelper(
|
||||
upperWidget: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Gap(36),
|
||||
Text('select_u_role'.tr(),
|
||||
style: AppTextStyles.headingH1),
|
||||
const Gap(8),
|
||||
Text(
|
||||
'what_u_area'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: AppColors.blackGray)),
|
||||
const SizedBox(height: 12),
|
||||
Column(
|
||||
children: [
|
||||
...state.staffRoles.indexed.toList().map(
|
||||
(e) {
|
||||
var (index, role) = e;
|
||||
return Container(
|
||||
decoration: KwBoxDecorations.primaryLight12,
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12, left: 12, right: 12),
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
child: AnimatedSize(
|
||||
alignment: Alignment.topCenter,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: state.editedRole == role
|
||||
? RoleEditCardWidget(
|
||||
role: role,
|
||||
index: index,
|
||||
skills: state.skills,
|
||||
mustShowWarningDialog: isInEditMode,
|
||||
canDelete:
|
||||
state.staffRoles.length > 1)
|
||||
: RoleCard(
|
||||
role: role,
|
||||
index: index,
|
||||
canDelete:
|
||||
state.staffRoles.length > 1)));
|
||||
},
|
||||
),
|
||||
const Gap(12),
|
||||
if (!state.isLoading)
|
||||
KwButton.outlinedPrimary(
|
||||
label: 'add_role'.tr(),
|
||||
onPressed: () {
|
||||
context.read<RoleBloc>().add(RoleSpawnEvent());
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
lowerWidget: isInEditMode
|
||||
? const SizedBox.shrink()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 20, top: 24),
|
||||
child: KwButton.primary(
|
||||
label: 'save_and_continue'.tr(), onPressed: () {
|
||||
context.router.replace(const SplashRoute());
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => RoleBloc()..add(RoleFetchEvent()),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_xlider/flutter_xlider.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/presentation/styles/kw_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
|
||||
class ExpSliderWidget extends StatefulWidget {
|
||||
final int initialValue;
|
||||
final ValueChanged<int>? onChanged;
|
||||
|
||||
const ExpSliderWidget({
|
||||
super.key,
|
||||
this.initialValue = 1,
|
||||
this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ExpSliderWidget> createState() => _ExpSliderWidgetState();
|
||||
}
|
||||
|
||||
class _ExpSliderWidgetState extends State<ExpSliderWidget> {
|
||||
late double _value;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_value = widget.initialValue.toDouble();
|
||||
_value = _value.clamp(1, 20);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant ExpSliderWidget oldWidget) {
|
||||
_value = widget.initialValue.toDouble();
|
||||
_value = _value.clamp(1, 20);
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 12, bottom: 12),
|
||||
margin: const EdgeInsets.only(bottom: 24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: AppColors.grayStroke,
|
||||
width: 1,
|
||||
),
|
||||
color: AppColors.graySecondaryFrame,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Gap(24),
|
||||
FlutterSlider(
|
||||
min: 1,
|
||||
max: 20,
|
||||
handlerHeight: 26,
|
||||
handlerWidth: 26,
|
||||
tooltip: FlutterSliderTooltip(
|
||||
alwaysShowTooltip: true,
|
||||
custom: (value) {
|
||||
return _SliderTooltip(
|
||||
text: value.toStringAsFixed(0),
|
||||
);
|
||||
}),
|
||||
handler: FlutterSliderHandler(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.grayWhite,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Container(
|
||||
height: 16,
|
||||
width: 16,
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.bgColorDark,
|
||||
shape: BoxShape.circle,
|
||||
)),
|
||||
),
|
||||
)),
|
||||
trackBar: FlutterSliderTrackBar(
|
||||
activeTrackBarHeight: 12,
|
||||
inactiveTrackBarHeight: 12,
|
||||
inactiveTrackBar: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: AppColors.grayTintStroke,
|
||||
),
|
||||
activeTrackBar: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: AppColors.bgColorDark,
|
||||
),
|
||||
),
|
||||
values: [_value],
|
||||
onDragging: (handlerIndex, lowerValue, upperValue) {
|
||||
// setState(() {
|
||||
// _value = lowerValue;
|
||||
// });
|
||||
widget.onChanged?.call(lowerValue.toInt());
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'1 ${'year'.tr()}',
|
||||
style: AppTextStyles.bodySmallMed
|
||||
.copyWith(color: AppColors.blackCaptionGreen),
|
||||
),
|
||||
Text(
|
||||
'20 ${'years'.tr()}',
|
||||
style: AppTextStyles.bodySmallMed
|
||||
.copyWith(color: AppColors.blackCaptionGreen),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SliderTooltip extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
const _SliderTooltip({
|
||||
required this.text,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Transform.translate(
|
||||
offset: const Offset(0, -30),
|
||||
child: Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
width: 50,
|
||||
height: 36,
|
||||
child: CustomPaint(
|
||||
painter: TooltipPainter(),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Text(
|
||||
'$text y',
|
||||
textAlign: TextAlign.center,
|
||||
style: AppTextStyles.bodySmallMed.copyWith(
|
||||
color: AppColors.grayWhite,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TooltipPainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()..color = const Color(0xFF001F2D);
|
||||
final path = Path();
|
||||
|
||||
path.addRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
const Rect.fromLTWH(0, 0, 48, 30),
|
||||
const Radius.circular(15),
|
||||
),
|
||||
);
|
||||
|
||||
const double triangleWidth = 10;
|
||||
const double triangleHeight = 6;
|
||||
const double centerX = 24;
|
||||
|
||||
path.moveTo(centerX - triangleWidth / 2, 30);
|
||||
path.quadraticBezierTo(
|
||||
centerX,
|
||||
40 + triangleHeight / 3,
|
||||
centerX + triangleWidth / 2,
|
||||
30,
|
||||
);
|
||||
path.close();
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
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/int_extensions.dart';
|
||||
import 'package:krow/core/application/common/str_extensions.dart';
|
||||
import 'package:krow/core/data/enums/staff_skill_enums.dart';
|
||||
import 'package:krow/core/data/models/staff_role.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/features/profile/role/domain/bloc/role_bloc.dart';
|
||||
import 'package:krow/features/profile/role/domain/bloc/role_event.dart';
|
||||
|
||||
class RoleCard extends StatelessWidget {
|
||||
final StaffRole role;
|
||||
final int index;
|
||||
|
||||
final bool canDelete;
|
||||
|
||||
const RoleCard({
|
||||
super.key,
|
||||
required this.role,
|
||||
required this.index,
|
||||
required this.canDelete,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${(index + 1).toOrdinal()} ${'role_details'.tr()}',
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
),
|
||||
if (role.status != null) _buildStatus(),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_buildRoleTextInfo(context, '${'role'.tr()}:', role.skill?.name),
|
||||
_buildRoleTextInfo(
|
||||
context, '${'experience'.tr()}:', '${role.experience} ${'years'.tr()}'),
|
||||
_buildRoleTextInfo(context, '${'level'.tr()}:', role.level?.name),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: KwButton.outlinedPrimary(
|
||||
label: 'edit_role'.tr(),
|
||||
onPressed: () {
|
||||
BlocProvider.of<RoleBloc>(context).add(
|
||||
RoleSelectEvent(role),
|
||||
);
|
||||
},
|
||||
leftIcon: Assets.images.icons.edit,
|
||||
),
|
||||
),
|
||||
if (canDelete) ...[
|
||||
const Gap(8),
|
||||
KwButton.outlinedPrimary(
|
||||
fit: KwButtonFit.circular,
|
||||
onPressed: () {
|
||||
BlocProvider.of<RoleBloc>(context).add(
|
||||
RoleDeleteEvent(role),
|
||||
);
|
||||
},
|
||||
leftIcon: Assets.images.icons.delete)
|
||||
.copyWith(color: AppColors.statusError),
|
||||
],
|
||||
],
|
||||
),
|
||||
const Gap(12),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Container _buildStatus() {
|
||||
Color color;
|
||||
switch (role.status) {
|
||||
case StaffSkillStatus.pending:
|
||||
color = AppColors.primaryBlue;
|
||||
break;
|
||||
case StaffSkillStatus.verified:
|
||||
color = AppColors.statusSuccess;
|
||||
break;
|
||||
case StaffSkillStatus.deactivated:
|
||||
color = AppColors.statusError;
|
||||
break;
|
||||
default:
|
||||
color = AppColors.bgColorDark;
|
||||
}
|
||||
return Container(
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 8, right: 8),
|
||||
child: Center(
|
||||
child: Text(
|
||||
role.status?.name.capitalize() ?? '',
|
||||
style: AppTextStyles.bodySmallMed.copyWith(color: Colors.white),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
Column _buildRoleTextInfo(BuildContext context, String title, String? value) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: AppTextStyles.captionReg.copyWith(
|
||||
color: AppColors.blackGray,
|
||||
),
|
||||
),
|
||||
const Gap(4),
|
||||
Text(value ?? '', style: AppTextStyles.bodyMediumMed)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
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/int_extensions.dart';
|
||||
import 'package:krow/core/application/common/validators/skill_exp_validator.dart';
|
||||
import 'package:krow/core/data/enums/staff_skill_enums.dart';
|
||||
import 'package:krow/core/data/models/skill.dart';
|
||||
import 'package:krow/core/data/models/staff_role.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/dialogs/kw_dialog.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/core/presentation/widgets/ui_kit/kw_option_selector.dart';
|
||||
import 'package:krow/features/profile/role/domain/bloc/role_bloc.dart';
|
||||
import 'package:krow/features/profile/role/domain/bloc/role_event.dart';
|
||||
import 'package:krow/features/profile/role/presentation/widgets/exp_slider_widget.dart';
|
||||
|
||||
class RoleEditCardWidget extends StatefulWidget {
|
||||
final StaffRole role;
|
||||
|
||||
final int index;
|
||||
|
||||
final List<Skill> skills;
|
||||
|
||||
final bool canDelete;
|
||||
|
||||
final bool mustShowWarningDialog;
|
||||
|
||||
const RoleEditCardWidget(
|
||||
{super.key,
|
||||
required this.role,
|
||||
required this.index,
|
||||
required this.skills,
|
||||
required this.canDelete,
|
||||
this.mustShowWarningDialog = false});
|
||||
|
||||
@override
|
||||
State<RoleEditCardWidget> createState() => _RoleEditCardWidgetState();
|
||||
}
|
||||
|
||||
class _RoleEditCardWidgetState extends State<RoleEditCardWidget> {
|
||||
String? experienceError;
|
||||
late TextEditingController textEditingController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
textEditingController =
|
||||
TextEditingController(text: widget.role.experience.toString());
|
||||
|
||||
textEditingController.addListener(() {
|
||||
final value = textEditingController.text;
|
||||
setState(() {
|
||||
experienceError = SkillExpValidator.validate(value);
|
||||
});
|
||||
|
||||
var exp = int.tryParse(value);
|
||||
if (exp != null && experienceError == null) {
|
||||
BlocProvider.of<RoleBloc>(context).add(RoleUpdateEvent(
|
||||
widget.role.copyWith(experience: exp),
|
||||
));
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_header(),
|
||||
const Gap(16),
|
||||
_skillDropDown(context),
|
||||
const Gap(16),
|
||||
KwOptionSelector(
|
||||
selectedIndex: widget.role.level?.index ?? 0,
|
||||
backgroundColor: AppColors.graySecondaryFrame,
|
||||
onChanged: (index) {
|
||||
BlocProvider.of<RoleBloc>(context).add(RoleUpdateEvent(
|
||||
widget.role.copyWith(level: StaffSkillLevel.values[index]),
|
||||
));
|
||||
},
|
||||
title: 'level'.tr(),
|
||||
items: [
|
||||
'Beginner'.tr(),
|
||||
'Skilled'.tr(),
|
||||
'Professional'.tr(),
|
||||
]),
|
||||
const Gap(16),
|
||||
KwTextInput(
|
||||
controller: textEditingController,
|
||||
title: 'years_of_exp'.tr(),
|
||||
helperText: experienceError,
|
||||
showError: experienceError != null,
|
||||
onChanged: (value) {}),
|
||||
const Gap(12),
|
||||
ExpSliderWidget(
|
||||
initialValue: widget.role.experience ?? 1,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
textEditingController.text = value.toString();
|
||||
});
|
||||
},
|
||||
),
|
||||
KwButton.primary(
|
||||
disabled: widget.role.skill == null,
|
||||
label: 'save_changes'.tr(),
|
||||
onPressed: _onSaveChanges,
|
||||
),
|
||||
const Gap(12),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _skillDropDown(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
ignoring: widget.role.status != null,
|
||||
child: KwDropdown<Skill>(
|
||||
horizontalPadding: 28,
|
||||
title: 'role'.tr(),
|
||||
hintText: 'select_role'.tr(),
|
||||
selectedItem: widget.role.skill == null
|
||||
? null
|
||||
: KwDropDownItem(
|
||||
data: widget.role.skill!,
|
||||
title: widget.role.skill?.name ?? ''),
|
||||
items: widget.skills
|
||||
.map((skill) => KwDropDownItem(data: skill, title: skill.name)),
|
||||
onSelected: (skill) {
|
||||
BlocProvider.of<RoleBloc>(context).add(RoleUpdateEvent(
|
||||
widget.role.copyWith(skill: skill),
|
||||
));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Row _header() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${(widget.index + 1).toOrdinal()} ${'role_details'.tr()}:',
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
),
|
||||
if (widget.canDelete)
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
BlocProvider.of<RoleBloc>(context)
|
||||
.add(RoleDeleteEvent(widget.role));
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: Center(
|
||||
child: Assets.images.icons.delete.svg(
|
||||
height: 16,
|
||||
width: 16,
|
||||
colorFilter: const ColorFilter.mode(
|
||||
AppColors.statusError, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onSaveChanges() {
|
||||
if (widget.mustShowWarningDialog &&
|
||||
widget.role.status != StaffSkillStatus.pending) {
|
||||
_showWarningReviewDialog(context).then(
|
||||
(value) {
|
||||
if (value == true && context.mounted) {
|
||||
BlocProvider.of<RoleBloc>(context)
|
||||
.add(RoleSaveChangesEvent(widget.role));
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
BlocProvider.of<RoleBloc>(context).add(RoleSaveChangesEvent(widget.role));
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool?> _showWarningReviewDialog(BuildContext context) {
|
||||
return KwDialog.show<bool>(
|
||||
context: context,
|
||||
icon: Assets.images.icons.alertTriangle,
|
||||
state: KwDialogState.neutral,
|
||||
title: 'role_change'.tr(),
|
||||
message: 'change_u_role'.tr(),
|
||||
child: Text(
|
||||
'wont_to_proceed'.tr(),
|
||||
style: AppTextStyles.bodyMediumMed,
|
||||
),
|
||||
primaryButtonLabel: 'Continue'.tr(),
|
||||
secondaryButtonLabel: 'cancel'.tr(),
|
||||
onPrimaryButtonPressed: (BuildContext dialogContext) {
|
||||
dialogContext.maybePop(true);
|
||||
},
|
||||
onSecondaryButtonPressed: (BuildContext dialogContext) {
|
||||
dialogContext.maybePop(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user