feat: Refactor code structure and optimize performance across multiple modules

This commit is contained in:
Achintha Isuru
2025-11-17 23:29:28 -05:00
parent 831570f2e0
commit a64cbd9edf
1508 changed files with 105319 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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