feat: Refactor code structure and optimize performance across multiple modules
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/core/application/clients/api/api_client.dart';
|
||||
import 'package:krow/core/data/models/staff/staff.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
import 'package:krow/features/profile/profile_main/data/profile_gql.dart';
|
||||
|
||||
@injectable
|
||||
class ProfileApiProvider {
|
||||
final ApiClient _apiClient;
|
||||
|
||||
ProfileApiProvider({required ApiClient apiClient}) : _apiClient = apiClient;
|
||||
|
||||
Future<List<StaffRole>> fetchUserProfileRoles() async {
|
||||
var result = await _apiClient.query(schema: getStaffProfileRolesQuery);
|
||||
if (result.hasException) {
|
||||
throw Exception(result.exception.toString());
|
||||
}
|
||||
|
||||
return result.data!['staff_roles'].map<StaffRole>((e) {
|
||||
return StaffRole.fromJson(e);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Stream<Staff> getMeWithCache() async* {
|
||||
await for (var response in _apiClient.queryWithCache(schema: getMeQuery)) {
|
||||
if (response == null || response.data == null) continue;
|
||||
|
||||
if (response.hasException) {
|
||||
throw Exception(response.exception.toString());
|
||||
}
|
||||
|
||||
try {
|
||||
final staffData = response.data?['me'] as Map<String, dynamic>? ?? {};
|
||||
if (staffData.isEmpty) continue;
|
||||
|
||||
yield Staff.fromJson(staffData);
|
||||
} catch (except) {
|
||||
log(
|
||||
'Exception in StaffApi on getMeWithCache()',
|
||||
error: except,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:krow/core/application/clients/api/gql.dart';
|
||||
|
||||
const String getMeQuery = '''
|
||||
$staffFragment
|
||||
query GetMe {
|
||||
me {
|
||||
id
|
||||
...StaffFields
|
||||
}
|
||||
}
|
||||
''';
|
||||
|
||||
const String getStaffProfileRolesQuery = '''
|
||||
$skillFragment
|
||||
query GetStaffRoles {
|
||||
staff_roles {
|
||||
id
|
||||
skill {
|
||||
...SkillFragment
|
||||
}
|
||||
confirmed_uniforms {
|
||||
id
|
||||
skill_kit_id
|
||||
photo
|
||||
}
|
||||
confirmed_equipments {
|
||||
id
|
||||
skill_kit_id
|
||||
photo
|
||||
}
|
||||
level
|
||||
experience
|
||||
status
|
||||
}
|
||||
}
|
||||
''';
|
||||
@@ -0,0 +1,54 @@
|
||||
import 'dart:convert';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:krow/core/data/models/staff/staff.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
|
||||
@LazySingleton()
|
||||
class ProfileLocalProvider {
|
||||
Future<String> _getUserId() async {
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
if (user != null) {
|
||||
return user.uid;
|
||||
} else {
|
||||
|
||||
throw Exception('No user logged in');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveStaff(Staff staff) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final userId = await _getUserId();
|
||||
final staffJson = jsonEncode(staff.toJson());
|
||||
await prefs.setString('cached_staff_$userId', staffJson);
|
||||
}
|
||||
|
||||
Future<Staff?> loadStaff() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final userId = await _getUserId();
|
||||
final staffJson = prefs.getString('cached_staff_$userId');
|
||||
if (staffJson != null) {
|
||||
return Staff.fromJson(jsonDecode(staffJson));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> saveRoles(List<StaffRole> roles) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final userId = await _getUserId();
|
||||
final rolesJson = jsonEncode(roles.map((role) => role.toJson()).toList());
|
||||
await prefs.setString('cached_roles_$userId', rolesJson);
|
||||
}
|
||||
|
||||
Future<List<StaffRole>?> loadRoles() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final userId = await _getUserId();
|
||||
final rolesJson = prefs.getString('cached_roles_$userId');
|
||||
if (rolesJson != null) {
|
||||
final List<dynamic> rolesList = jsonDecode(rolesJson);
|
||||
return rolesList.map((json) => StaffRole.fromJson(json)).toList();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:krow/core/data/models/staff/staff.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
import 'package:krow/features/profile/profile_main/data/profile_api_provider.dart';
|
||||
import 'package:krow/features/profile/profile_main/data/profile_local_provider.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/profile_repository.dart';
|
||||
|
||||
@Injectable(as: ProfileRepository)
|
||||
class ProfileRepositoryImpl extends ProfileRepository{
|
||||
final ProfileApiProvider _apiProvider;
|
||||
final ProfileLocalProvider _cacheProvider;
|
||||
|
||||
ProfileRepositoryImpl(this._apiProvider, this._cacheProvider);
|
||||
|
||||
@override
|
||||
Future<List<StaffRole>> getUserProfileRoles() async{
|
||||
var roles = await _apiProvider.fetchUserProfileRoles();
|
||||
await cacheProfileRoles(roles);
|
||||
return roles;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> cacheProfileRoles(List<StaffRole> roles) async{
|
||||
await _cacheProvider.saveRoles(roles);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<StaffRole>?> getCachedProfileRoles() {
|
||||
return _cacheProvider.loadRoles();
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<Staff> getUserProfile() {
|
||||
return _apiProvider.getMeWithCache();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:krow/core/application/di/injectable.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_event.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_state.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/menu_tree.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/profile_repository.dart';
|
||||
|
||||
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
|
||||
final menuTree = MenuTree();
|
||||
|
||||
ProfileBloc()
|
||||
: super(ProfileState(
|
||||
menu: MenuRoutItem(),
|
||||
roles: const [],
|
||||
)) {
|
||||
on<ProfileEventInit>(_onInit);
|
||||
on<ProfileEventSelectMenu>(_onSelectMenu);
|
||||
on<ProfileSwitchAvailability>(_onSwitchAvailability);
|
||||
}
|
||||
|
||||
void _onInit(ProfileEventInit event, emit) async {
|
||||
var repo = getIt<ProfileRepository>();
|
||||
var menu = menuTree.buildMenuTree();
|
||||
emit(state.copyWith(
|
||||
menu: menu,
|
||||
));
|
||||
|
||||
emit(state.copyWith(
|
||||
roles: (await repo.getCachedProfileRoles())
|
||||
?.map(mapRoleToRoleState)
|
||||
.toList()));
|
||||
|
||||
await for (var staff in repo.getUserProfile()) {
|
||||
emit(state.copyWith(
|
||||
staff: staff,
|
||||
));
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
roles: (await repo.getUserProfileRoles())
|
||||
.map(mapRoleToRoleState)
|
||||
.toList()));
|
||||
}
|
||||
|
||||
RoleState mapRoleToRoleState(e) {
|
||||
return RoleState(
|
||||
name: e.skill!.name, experience: e.experience!, level: e.level!);
|
||||
}
|
||||
|
||||
void _onSelectMenu(ProfileEventSelectMenu event, emit) {
|
||||
emit(state.copyWith(menu: event.item));
|
||||
}
|
||||
|
||||
void _onSwitchAvailability(ProfileSwitchAvailability event, emit) {
|
||||
emit(state.copyWith(isAvailableNow: event.available));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_state.dart';
|
||||
|
||||
@immutable
|
||||
sealed class ProfileEvent {}
|
||||
|
||||
class ProfileEventInit extends ProfileEvent {}
|
||||
|
||||
class ProfileEventSelectMenu extends ProfileEvent {
|
||||
ProfileEventSelectMenu({required this.item});
|
||||
|
||||
final MenuRoutItem item;
|
||||
}
|
||||
|
||||
class ProfileSwitchAvailability extends ProfileEvent {
|
||||
ProfileSwitchAvailability({required this.available});
|
||||
|
||||
final bool available;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:krow/core/data/enums/staff_skill_enums.dart';
|
||||
import 'package:krow/core/data/models/staff/staff.dart';
|
||||
|
||||
@immutable
|
||||
class ProfileState {
|
||||
final List<RoleState> roles;
|
||||
final Staff? staff;
|
||||
final MenuRoutItem menu;
|
||||
final bool isAvailableNow;
|
||||
|
||||
const ProfileState({
|
||||
required this.menu,
|
||||
this.staff,
|
||||
this.roles = const [],
|
||||
this.isAvailableNow = false,
|
||||
});
|
||||
|
||||
ProfileState copyWith({
|
||||
MenuRoutItem? menu,
|
||||
List<RoleState>? roles,
|
||||
Staff? staff,
|
||||
bool? isAvailableNow,
|
||||
int? testField,
|
||||
}) {
|
||||
return ProfileState(
|
||||
menu: menu ?? this.menu,
|
||||
roles: roles ?? this.roles,
|
||||
staff: staff ?? this.staff,
|
||||
isAvailableNow: isAvailableNow ?? this.isAvailableNow,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//mock
|
||||
class RoleState {
|
||||
final String name;
|
||||
final int experience;
|
||||
final StaffSkillLevel level;
|
||||
|
||||
RoleState(
|
||||
{required this.name, required this.experience, required this.level});
|
||||
}
|
||||
|
||||
class MenuRoutItem {
|
||||
final String? title;
|
||||
final SvgPicture? icon;
|
||||
final bool showBadge;
|
||||
final PageRouteInfo? route;
|
||||
final VoidCallback? onTap;
|
||||
MenuRoutItem? parent;
|
||||
|
||||
List<MenuRoutItem> children;
|
||||
|
||||
MenuRoutItem({
|
||||
this.title,
|
||||
this.parent,
|
||||
this.icon,
|
||||
this.onTap,
|
||||
this.showBadge = false,
|
||||
this.route,
|
||||
}) : children = [];
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.dart';
|
||||
import 'package:krow/core/presentation/gen/assets.gen.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_state.dart';
|
||||
import 'package:krow/features/profile/role_kit/domain/staff_role_kit_repository_impl.dart';
|
||||
|
||||
class MenuTree {
|
||||
final MenuRoutItem root;
|
||||
final MenuRoutItem profileSettings;
|
||||
final MenuRoutItem personalInfo;
|
||||
final MenuRoutItem bankAccount;
|
||||
final MenuRoutItem workSettings;
|
||||
final MenuRoutItem workingArea;
|
||||
final MenuRoutItem schedule;
|
||||
final MenuRoutItem verificationCenter;
|
||||
final MenuRoutItem certification;
|
||||
final MenuRoutItem livePhoto;
|
||||
final MenuRoutItem wagesForm;
|
||||
final MenuRoutItem equipment;
|
||||
final MenuRoutItem uniform;
|
||||
final MenuRoutItem employeeResources;
|
||||
final MenuRoutItem training;
|
||||
final MenuRoutItem benefits;
|
||||
final MenuRoutItem helpSupport;
|
||||
final MenuRoutItem faq;
|
||||
final MenuRoutItem termsConditions;
|
||||
final MenuRoutItem contactSupport;
|
||||
|
||||
MenuTree()
|
||||
: root = MenuRoutItem(),
|
||||
profileSettings = MenuRoutItem(
|
||||
title: 'account_settings',
|
||||
icon: Assets.images.userProfile.menu.profileCircle.svg(),
|
||||
),
|
||||
personalInfo = MenuRoutItem(
|
||||
title: 'profile_settings',
|
||||
icon: Assets.images.userProfile.menu.userEdit.svg(),
|
||||
route: const ProfileSettingsFlowRoute(),
|
||||
),
|
||||
bankAccount = MenuRoutItem(
|
||||
title: 'bank_account',
|
||||
icon: Assets.images.userProfile.menu.emptyWallet.svg(),
|
||||
route: const BankAccountFlowRoute(),
|
||||
),
|
||||
workSettings = MenuRoutItem(
|
||||
title: 'work_settings',
|
||||
showBadge: true,
|
||||
icon: Assets.images.userProfile.menu.briefcase.svg(),
|
||||
),
|
||||
workingArea = MenuRoutItem(
|
||||
title: 'working_area',
|
||||
icon: Assets.images.userProfile.menu.map.svg(),
|
||||
route: WorkingAreaRoute(),
|
||||
),
|
||||
schedule = MenuRoutItem(
|
||||
title: 'schedule',
|
||||
icon: Assets.images.userProfile.menu.calendar.svg(),
|
||||
route: ScheduleRoute(),
|
||||
),
|
||||
verificationCenter = MenuRoutItem(
|
||||
title: 'verification_center',
|
||||
showBadge: true,
|
||||
icon: Assets.images.userProfile.menu.shieldTick.svg(),
|
||||
),
|
||||
certification = MenuRoutItem(
|
||||
title: 'certification',
|
||||
icon: Assets.images.userProfile.menu.medalStar.svg(),
|
||||
route: const CertificatesRoute(),
|
||||
),
|
||||
livePhoto = MenuRoutItem(
|
||||
title: 'live_photo',
|
||||
icon: Assets.images.userProfile.menu.gallery.svg(),
|
||||
route: const LivePhotoRoute(),
|
||||
),
|
||||
wagesForm = MenuRoutItem(
|
||||
title: 'wages_form',
|
||||
icon: Assets.images.userProfile.menu.note.svg(),
|
||||
route: const WagesFormsFlowRoute(),
|
||||
),
|
||||
equipment = MenuRoutItem(
|
||||
title: 'equipment',
|
||||
icon: Assets.images.userProfile.menu.pot.svg(),
|
||||
route: RoleKitFlowRoute(roleKitType: RoleKitType.equipment),
|
||||
),
|
||||
uniform = MenuRoutItem(
|
||||
title: 'uniform',
|
||||
icon: Assets.images.userProfile.menu.chef.svg(),
|
||||
route: RoleKitFlowRoute(roleKitType: RoleKitType.uniform),
|
||||
),
|
||||
employeeResources = MenuRoutItem(
|
||||
title: 'employee_resources',
|
||||
icon: Assets.images.userProfile.menu.star.svg(),
|
||||
),
|
||||
training = MenuRoutItem(
|
||||
title: 'training',
|
||||
icon: Assets.images.userProfile.menu.teacher.svg(),
|
||||
),
|
||||
benefits = MenuRoutItem(
|
||||
title: 'benefits',
|
||||
icon: Assets.images.userProfile.menu.star.svg(),
|
||||
route: const BenefitsRoute(),
|
||||
),
|
||||
helpSupport = MenuRoutItem(
|
||||
title: 'help_support',
|
||||
icon: Assets.images.userProfile.menu.message.svg(),
|
||||
),
|
||||
faq = MenuRoutItem(
|
||||
title: 'faq',
|
||||
icon: Assets.images.userProfile.menu.helpCircle.svg(),
|
||||
route: const FaqRoute(),
|
||||
),
|
||||
termsConditions = MenuRoutItem(
|
||||
title: 'terms_conditions',
|
||||
icon: Assets.images.userProfile.menu.securitySafe.svg(),
|
||||
),
|
||||
contactSupport = MenuRoutItem(
|
||||
title: 'contact_support',
|
||||
icon: Assets.images.userProfile.menu.headphone.svg(),
|
||||
route: const SupportRoute(),
|
||||
);
|
||||
|
||||
MenuRoutItem buildMenuTree() {
|
||||
profileSettings.children.addAll(
|
||||
[
|
||||
personalInfo..parent = profileSettings,
|
||||
bankAccount..parent = profileSettings
|
||||
],
|
||||
);
|
||||
workSettings.children.addAll(
|
||||
[workingArea..parent = workSettings, schedule..parent = workSettings],
|
||||
);
|
||||
verificationCenter.children.addAll(
|
||||
[
|
||||
certification..parent = verificationCenter,
|
||||
livePhoto..parent = verificationCenter,
|
||||
equipment..parent = verificationCenter,
|
||||
uniform..parent = verificationCenter
|
||||
],
|
||||
);
|
||||
employeeResources.children.addAll(
|
||||
[
|
||||
if (kDebugMode) benefits..parent = employeeResources
|
||||
],
|
||||
);
|
||||
helpSupport.children.addAll(
|
||||
[
|
||||
faq..parent = helpSupport,
|
||||
contactSupport..parent = helpSupport
|
||||
],
|
||||
);
|
||||
|
||||
root.children.addAll(
|
||||
[
|
||||
profileSettings..parent = root,
|
||||
workSettings..parent = root,
|
||||
verificationCenter..parent = root,
|
||||
employeeResources..parent = root,
|
||||
helpSupport..parent = root,
|
||||
],
|
||||
);
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:krow/core/data/models/staff/staff.dart';
|
||||
import 'package:krow/core/data/models/staff_role.dart';
|
||||
|
||||
abstract class ProfileRepository {
|
||||
|
||||
|
||||
Stream<Staff> getUserProfile();
|
||||
|
||||
Future<List<StaffRole>?> getCachedProfileRoles();
|
||||
|
||||
Future<void> cacheProfileRoles(List<StaffRole> roles);
|
||||
|
||||
Future<List<StaffRole>> getUserProfileRoles();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ProfileMainFlowScreen extends StatelessWidget {
|
||||
const ProfileMainFlowScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const AutoRouter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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/restart_widget.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_bloc.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_event.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_state.dart';
|
||||
import 'package:krow/features/profile/profile_main/presentation/widgets/profile_menu_widget.dart';
|
||||
import 'package:krow/features/profile/profile_main/presentation/widgets/user_profile_card.dart';
|
||||
|
||||
@RoutePage()
|
||||
class ProfileMainScreen extends StatefulWidget implements AutoRouteWrapper {
|
||||
const ProfileMainScreen({super.key});
|
||||
|
||||
@override
|
||||
State<ProfileMainScreen> createState() => _ProfileMainScreenState();
|
||||
|
||||
@override
|
||||
Widget wrappedRoute(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => ProfileBloc()..add(ProfileEventInit()),
|
||||
child: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProfileMainScreenState extends State<ProfileMainScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
bool _isScrolled = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_scrollController.addListener(_scrollListener);
|
||||
}
|
||||
|
||||
void _scrollListener() {
|
||||
if (_scrollController.offset > 50 && !_isScrolled) {
|
||||
setState(() {
|
||||
_isScrolled = true;
|
||||
});
|
||||
} else if (_scrollController.offset <= 50 && _isScrolled) {
|
||||
setState(() {
|
||||
_isScrolled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
body: Stack(
|
||||
children: [
|
||||
SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
child: BlocBuilder<ProfileBloc, ProfileState>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
children: [
|
||||
UserProfileCard(state: state),
|
||||
ProfileMenuWidget(state: state),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: AppBar(
|
||||
titleSpacing: 0,
|
||||
leadingWidth: 0,
|
||||
automaticallyImplyLeading: false,
|
||||
title: _buildAppBar(context),
|
||||
actionsPadding: EdgeInsets.zero,
|
||||
elevation: 0,
|
||||
backgroundColor: _isScrolled? AppColors.bgColorDark : Colors.transparent,
|
||||
scrolledUnderElevation: 0,
|
||||
|
||||
shadowColor: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAppBar(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text('Profile'.tr(),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(color: Colors.white)),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () async{
|
||||
context.setLocale(
|
||||
context.locale == Locale('es') ? Locale('en') : Locale('es'),
|
||||
);
|
||||
await Future.delayed(Duration(microseconds: 500));
|
||||
RestartWidget.restartApp(context);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Text(context.locale == Locale('en')?'ENG':'ESP',style: AppTextStyles.bodyMediumMed.copyWith(color: Colors.white)),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
alignment: Alignment.center,
|
||||
child: Assets.images.appBar.notification.svg(
|
||||
colorFilter:
|
||||
const ColorFilter.mode(Colors.white, BlendMode.srcIn)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/app.dart';
|
||||
import 'package:krow/core/application/clients/api/api_client.dart';
|
||||
import 'package:krow/core/application/di/injectable.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_text_styles.dart';
|
||||
import 'package:krow/core/presentation/styles/theme.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_bloc.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_event.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_state.dart';
|
||||
|
||||
class ProfileMenuWidget extends StatelessWidget {
|
||||
final ProfileState state;
|
||||
|
||||
const ProfileMenuWidget({super.key, required this.state});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: AnimatedSize(
|
||||
alignment: Alignment.topCenter,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 18),
|
||||
child: Column(
|
||||
children: [
|
||||
if (state.menu.parent != null && state.menu.title != null)
|
||||
_buildCurrentMenuTitle(context),
|
||||
for (var item in state.menu.children) ...[
|
||||
const Gap(6),
|
||||
_buildMenuItem(item, context),
|
||||
const Gap(6),
|
||||
],
|
||||
const Gap(18),
|
||||
if (state.menu.parent == null) ...[
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 16, right: 16, bottom: 24),
|
||||
child: Divider(color: AppColors.grayStroke),
|
||||
),
|
||||
_buildLogOutItem(context),
|
||||
const Gap(24),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCurrentMenuTitle(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 18.0, top: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (state.menu.parent != null) {
|
||||
context
|
||||
.read<ProfileBloc>()
|
||||
.add(ProfileEventSelectMenu(item: state.menu.parent!));
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(left: 16),
|
||||
color: Colors.transparent,
|
||||
height: 48,
|
||||
width: 48,
|
||||
child: Center(child: Assets.images.appBar.appbarLeading.svg()),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
state.menu.title!.tr(),
|
||||
|
||||
style: AppTextStyles.headingH2.copyWith(
|
||||
color: AppColors.blackBlack,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(64),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMenuItem(MenuRoutItem item, BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
if (item.children.isNotEmpty) {
|
||||
context.read<ProfileBloc>().add(ProfileEventSelectMenu(item: item));
|
||||
} else if (item.route != null) {
|
||||
appRouter.push(item.route!);
|
||||
return;
|
||||
} else {
|
||||
item.onTap?.call();
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
children: [
|
||||
_buildMenuIcon(icon: item.icon!, showBadge: item.showBadge),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.title?.tr() ?? '',
|
||||
style: AppTextStyles.headingH3.copyWith(
|
||||
color: AppColors.blackBlack,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
Assets.images.userProfile.chevron2.svg(),
|
||||
const Gap(16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLogOutItem(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
FirebaseAuth.instance.signOut();
|
||||
getIt<ApiClient>().dropCache();
|
||||
appRouter.replace(const WelcomeRoute());
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
_buildMenuIcon(
|
||||
icon: Assets.images.userProfile.menu.logOut.svg(),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'log_out'.tr(),
|
||||
style: AppTextStyles.headingH3.copyWith(
|
||||
color: AppColors.statusError,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Gap(16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Stack _buildMenuIcon({required SvgPicture icon, bool showBadge = false}) {
|
||||
return Stack(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
height: 48,
|
||||
width: 48,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.primaryYellow,
|
||||
),
|
||||
child: Center(
|
||||
child: icon,
|
||||
),
|
||||
),
|
||||
if (showBadge) _buildBadge(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBadge() {
|
||||
return Positioned(
|
||||
right: 16,
|
||||
child: Container(
|
||||
height: 12,
|
||||
width: 12,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.statusError,
|
||||
border: Border.all(color: AppColors.bgColorLight, width: 2),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.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_image_animated_placeholder.dart';
|
||||
|
||||
class UserAvatarWidget extends StatelessWidget {
|
||||
final String? imageUrl;
|
||||
final String userName;
|
||||
final double? rating;
|
||||
|
||||
const UserAvatarWidget({
|
||||
super.key,
|
||||
this.imageUrl,
|
||||
required this.userName,
|
||||
required this.rating,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
_buildUserPhoto(imageUrl, userName),
|
||||
_buildEditButton(context),
|
||||
if(rating!=null && rating! > 0)
|
||||
_buildUserRating(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Container _buildUserPhoto(String? imageUrl, String? userName) {
|
||||
return Container(
|
||||
width: 96,
|
||||
height: 96,
|
||||
decoration: const BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AppColors.darkBgPrimaryFrame,
|
||||
),
|
||||
child: ClipOval(
|
||||
child: Image.network(
|
||||
imageUrl ?? '',
|
||||
fit: BoxFit.cover,
|
||||
width: 96,
|
||||
height: 96,
|
||||
loadingBuilder: (context, child, chunkEvent) {
|
||||
if (chunkEvent?.expectedTotalBytes ==
|
||||
chunkEvent?.cumulativeBytesLoaded) {
|
||||
return child;
|
||||
}
|
||||
|
||||
return const KwAnimatedImagePlaceholder();
|
||||
},
|
||||
errorBuilder: (context, error, trace) {
|
||||
return Center(
|
||||
child: Text(
|
||||
getInitials(userName),
|
||||
style: AppTextStyles.headingH1.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Positioned _buildEditButton(BuildContext context) {
|
||||
return Positioned(
|
||||
bottom: 0,
|
||||
left: -5,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.router.push(PersonalInfoRoute(isInEditMode: true));
|
||||
},
|
||||
child: Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
border: Border.all(color: AppColors.bgColorDark, width: 2),
|
||||
),
|
||||
child: Center(
|
||||
child: Assets.images.userProfile.editPhoto.svg(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_buildUserRating() {
|
||||
return Positioned(
|
||||
bottom: 0,
|
||||
right: -20,
|
||||
child: Container(
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
color: Colors.white,
|
||||
border: Border.all(color: AppColors.bgColorDark, width: 2),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 8),
|
||||
Assets.images.userProfile.star.svg(width: 16, height: 16),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
rating.toString(),
|
||||
style: AppTextStyles.bodyTinyMed.copyWith(
|
||||
color: AppColors.bgColorDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String getInitials(String? name) {
|
||||
try {
|
||||
if (name == null || name.isEmpty) return ' ';
|
||||
List<String> nameParts = name.split(' ');
|
||||
if (nameParts.length == 1) {
|
||||
return nameParts[0].substring(0, 1).toUpperCase();
|
||||
}
|
||||
return (nameParts[0][0] + nameParts[1][0]).toUpperCase();
|
||||
} catch (e) {
|
||||
return ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/app.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/profile/profile_main/domain/bloc/user_profile_bloc.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_event.dart';
|
||||
import 'package:krow/features/profile/profile_main/domain/bloc/user_profile_state.dart';
|
||||
import 'package:krow/features/profile/profile_main/presentation/widgets/user_avatar_widget.dart';
|
||||
import 'package:krow/features/profile/profile_main/presentation/widgets/user_roles_widget.dart';
|
||||
|
||||
import '../../../../../core/presentation/widgets/restart_widget.dart';
|
||||
|
||||
class UserProfileCard extends StatefulWidget {
|
||||
final ProfileState state;
|
||||
|
||||
const UserProfileCard({super.key, required this.state});
|
||||
|
||||
@override
|
||||
State<UserProfileCard> createState() => _UserProfileCardState();
|
||||
}
|
||||
|
||||
class _UserProfileCardState extends State<UserProfileCard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
fit: StackFit.loose,
|
||||
children: [
|
||||
_buildProfileBackground(context),
|
||||
SafeArea(
|
||||
bottom: false,
|
||||
child: Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Gap(32+ kToolbarHeight),
|
||||
UserAvatarWidget(
|
||||
imageUrl: widget.state.staff?.avatar,
|
||||
userName:
|
||||
'${widget.state.staff?.firstName ?? ''} ${widget.state.staff?.lastName ?? ''}',
|
||||
rating: widget.state.staff?.averageRating ?? 5,
|
||||
),
|
||||
const Gap(16),
|
||||
_buildUserInfo(context),
|
||||
const Gap(24),
|
||||
_buildAvailabilitySwitcher(context),
|
||||
const Gap(8),
|
||||
UserRolesWidget(roles: widget.state.roles),
|
||||
const Gap(24),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Column _buildUserInfo(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
'${widget.state.staff?.firstName ?? ''} ${widget.state.staff?.lastName ?? ''}',
|
||||
style: AppTextStyles.headingH3.copyWith(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (widget.state.staff?.email?.isNotEmpty ?? false) ...[
|
||||
const Gap(8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Assets.images.userProfile.sms.svg(),
|
||||
const Gap(4),
|
||||
Text(
|
||||
widget.state.staff!.email!,
|
||||
style: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: AppColors.darkBgInactive),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
if (widget.state.staff?.phone != null) ...[
|
||||
const Gap(8),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Assets.images.userProfile.call.svg(),
|
||||
const Gap(4),
|
||||
Text(
|
||||
widget.state.staff!.phone!,
|
||||
style: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: AppColors.darkBgInactive),
|
||||
)
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAppBar(BuildContext context) {
|
||||
return Container(
|
||||
height: 48,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
// child: Row(
|
||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
||||
// children: [
|
||||
// Text('Profile'.tr(),
|
||||
// style: Theme.of(context)
|
||||
// .textTheme
|
||||
// .headlineSmall
|
||||
// ?.copyWith(color: Colors.white)),
|
||||
// const Spacer(),
|
||||
// GestureDetector(
|
||||
// onTap: () async{
|
||||
// context.setLocale(
|
||||
// context.locale == Locale('es') ? Locale('en') : Locale('es'),
|
||||
// );
|
||||
// await Future.delayed(Duration(microseconds: 500));
|
||||
// RestartWidget.restartApp(context);
|
||||
// },
|
||||
// child: Row(
|
||||
// children: [
|
||||
// Text(context.locale == Locale('en')?'ENG':'ESP',style: AppTextStyles.bodyMediumMed.copyWith(color: Colors.white)),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// Container(
|
||||
// width: 48,
|
||||
// height: 48,
|
||||
// alignment: Alignment.center,
|
||||
// child: Assets.images.appBar.notification.svg(
|
||||
// colorFilter:
|
||||
// const ColorFilter.mode(Colors.white, BlendMode.srcIn)),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfileBackground(BuildContext context) {
|
||||
return Positioned.fill(
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(12),
|
||||
bottomRight: Radius.circular(12),
|
||||
),
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
color: AppColors.bgColorDark,
|
||||
child: Assets.images.bg
|
||||
.svg(fit: BoxFit.fitWidth, alignment: Alignment.topCenter),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAvailabilitySwitcher(BuildContext context) {
|
||||
return Container(
|
||||
height: 52,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: KwBoxDecorations.primaryDark,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
'available_right_away'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg
|
||||
.copyWith(color: Colors.white, fontWeight: FontWeight.w500),
|
||||
),
|
||||
const Spacer(),
|
||||
CupertinoSwitch(
|
||||
value: widget.state.isAvailableNow,
|
||||
onChanged: (value) {
|
||||
BlocProvider.of<ProfileBloc>(context)
|
||||
.add(ProfileSwitchAvailability(available: value));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:krow/core/application/common/str_extensions.dart';
|
||||
import 'package:krow/core/application/routing/routes.gr.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/profile/profile_main/domain/bloc/user_profile_state.dart';
|
||||
|
||||
class UserRolesWidget extends StatefulWidget {
|
||||
final List<RoleState> roles;
|
||||
|
||||
const UserRolesWidget({super.key, required this.roles});
|
||||
|
||||
@override
|
||||
State<UserRolesWidget> createState() => _UserRolesWidgetState();
|
||||
}
|
||||
|
||||
class _UserRolesWidgetState extends State<UserRolesWidget> {
|
||||
int expandedIndex = -1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: KwBoxDecorations.primaryDark,
|
||||
child: Column(
|
||||
children: [
|
||||
const Gap(12),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'about_me'.tr(),
|
||||
style: AppTextStyles.bodyMediumReg.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
context.router.push(RoleRoute());
|
||||
},
|
||||
child: Assets.images.userProfile.editPhoto.svg(
|
||||
height: 16,
|
||||
width: 16,
|
||||
colorFilter:
|
||||
const ColorFilter.mode(Colors.white, BlendMode.srcIn),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.roles.isEmpty) const Gap(12),
|
||||
for (var i = 0; i < widget.roles.length; i++) ...[
|
||||
_buildRole(i, context),
|
||||
if (i != widget.roles.length - 1) ...[
|
||||
const Divider(color: AppColors.darkBgStroke),
|
||||
],
|
||||
]
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRole(int index, BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
_buildRoleHeader(index, context, widget.roles[index].name),
|
||||
_buildRoleExpandedInfo(expandedIndex == index, widget.roles[index]),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
GestureDetector _buildRoleHeader(
|
||||
int index,
|
||||
BuildContext context,
|
||||
String roleName,
|
||||
) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
expandedIndex = expandedIndex == index ? -1 : index;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(bottom: 12, top: 12),
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
roleName,
|
||||
style: (expandedIndex == index
|
||||
? AppTextStyles.captionBold
|
||||
: AppTextStyles.captionReg)
|
||||
.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
AnimatedRotation(
|
||||
turns: expandedIndex == index ? 0.5 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: (expandedIndex == index
|
||||
? Assets.images.userProfile.chevronDownSelected
|
||||
: Assets.images.userProfile.chevronDown)
|
||||
.svg(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRoleExpandedInfo(bool isExpanded, RoleState role) {
|
||||
return AnimatedSize(
|
||||
alignment: Alignment.topCenter,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
child: isExpanded
|
||||
? Column(
|
||||
children: [
|
||||
_buildRoleTextRow('${'role'.tr()}:', role.name),
|
||||
_buildRoleTextRow('${'experience'.tr()}:',
|
||||
'${role.experience} ${'years'.tr()}'),
|
||||
_buildRoleTextRow('level:', role.level.name.capitalize()),
|
||||
],
|
||||
)
|
||||
: SizedBox(
|
||||
height: 0,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRoleTextRow(String key, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Row(children: [
|
||||
Text(
|
||||
key,
|
||||
style: AppTextStyles.bodySmallReg.copyWith(
|
||||
color: AppColors.darkBgInactive,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.end,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: AppTextStyles.bodySmallMed.copyWith(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user