feat: Refactor ProfileHeader and introduce ProfileLevelBadge for improved structure and functionality
This commit is contained in:
@@ -9,7 +9,7 @@ import 'package:krow_domain/krow_domain.dart';
|
|||||||
import '../blocs/profile_cubit.dart';
|
import '../blocs/profile_cubit.dart';
|
||||||
import '../blocs/profile_state.dart';
|
import '../blocs/profile_state.dart';
|
||||||
import '../widgets/logout_button.dart';
|
import '../widgets/logout_button.dart';
|
||||||
import '../widgets/profile_header.dart';
|
import '../widgets/header/profile_header.dart';
|
||||||
import '../widgets/reliability_score_bar.dart';
|
import '../widgets/reliability_score_bar.dart';
|
||||||
import '../widgets/reliability_stats_card.dart';
|
import '../widgets/reliability_stats_card.dart';
|
||||||
import '../widgets/sections/index.dart';
|
import '../widgets/sections/index.dart';
|
||||||
@@ -25,19 +25,6 @@ class StaffProfilePage extends StatelessWidget {
|
|||||||
/// Creates a [StaffProfilePage].
|
/// Creates a [StaffProfilePage].
|
||||||
const StaffProfilePage({super.key});
|
const StaffProfilePage({super.key});
|
||||||
|
|
||||||
String _mapStatusToLevel(StaffStatus status) {
|
|
||||||
switch (status) {
|
|
||||||
case StaffStatus.active:
|
|
||||||
case StaffStatus.verified:
|
|
||||||
return 'Krower I';
|
|
||||||
case StaffStatus.pending:
|
|
||||||
case StaffStatus.completedProfile:
|
|
||||||
return 'Pending';
|
|
||||||
default:
|
|
||||||
return 'New';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final ProfileCubit cubit = Modular.get<ProfileCubit>();
|
final ProfileCubit cubit = Modular.get<ProfileCubit>();
|
||||||
@@ -106,7 +93,6 @@ class StaffProfilePage extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ProfileHeader(
|
ProfileHeader(
|
||||||
fullName: profile.name,
|
fullName: profile.name,
|
||||||
level: _mapStatusToLevel(profile.status),
|
|
||||||
photoUrl: profile.avatar,
|
photoUrl: profile.avatar,
|
||||||
),
|
),
|
||||||
Transform.translate(
|
Transform.translate(
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'profile_level_badge.dart';
|
||||||
|
|
||||||
|
/// The header section of the staff profile page, containing avatar, name, and level.
|
||||||
|
///
|
||||||
|
/// Uses design system tokens for all colors, typography, and spacing.
|
||||||
|
class ProfileHeader extends StatelessWidget {
|
||||||
|
/// Creates a [ProfileHeader].
|
||||||
|
const ProfileHeader({
|
||||||
|
super.key,
|
||||||
|
required this.fullName,
|
||||||
|
this.photoUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The staff member's full name
|
||||||
|
final String fullName;
|
||||||
|
|
||||||
|
/// Optional photo URL for the avatar
|
||||||
|
final String? photoUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.fromLTRB(
|
||||||
|
UiConstants.space5,
|
||||||
|
UiConstants.space5,
|
||||||
|
UiConstants.space5,
|
||||||
|
UiConstants.space16,
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: UiColors.primary,
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
bottom: Radius.circular(UiConstants.space6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
// Avatar Section
|
||||||
|
Container(
|
||||||
|
width: 112,
|
||||||
|
height: 112,
|
||||||
|
padding: const EdgeInsets.all(UiConstants.space1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: <Color>[
|
||||||
|
UiColors.accent,
|
||||||
|
UiColors.accent.withValues(alpha: 0.5),
|
||||||
|
UiColors.primaryForeground,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
boxShadow: <BoxShadow>[
|
||||||
|
BoxShadow(
|
||||||
|
color: UiColors.foreground.withValues(alpha: 0.2),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(
|
||||||
|
color: UiColors.primaryForeground.withValues(alpha: 0.2),
|
||||||
|
width: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: CircleAvatar(
|
||||||
|
backgroundColor: UiColors.background,
|
||||||
|
backgroundImage: photoUrl != null
|
||||||
|
? NetworkImage(photoUrl!)
|
||||||
|
: null,
|
||||||
|
child: photoUrl == null
|
||||||
|
? Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: <Color>[
|
||||||
|
UiColors.accent,
|
||||||
|
UiColors.accent.withValues(alpha: 0.7),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Text(
|
||||||
|
fullName.isNotEmpty
|
||||||
|
? fullName[0].toUpperCase()
|
||||||
|
: 'K',
|
||||||
|
style: UiTypography.displayM.primary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: UiConstants.space4),
|
||||||
|
Text(fullName, style: UiTypography.headline2m.white),
|
||||||
|
const SizedBox(height: UiConstants.space1),
|
||||||
|
const ProfileLevelBadge(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import 'package:design_system/design_system.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import '../../blocs/profile_cubit.dart';
|
||||||
|
import '../../blocs/profile_state.dart';
|
||||||
|
|
||||||
|
/// A widget that displays the staff member's level badge.
|
||||||
|
///
|
||||||
|
/// The level is calculated based on the staff status from ProfileCubit and displayed
|
||||||
|
/// in a styled container with the design system tokens.
|
||||||
|
class ProfileLevelBadge extends StatelessWidget {
|
||||||
|
/// Creates a [ProfileLevelBadge].
|
||||||
|
const ProfileLevelBadge({super.key});
|
||||||
|
|
||||||
|
/// Maps staff status to a user-friendly level string.
|
||||||
|
String _mapStatusToLevel(StaffStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case StaffStatus.active:
|
||||||
|
case StaffStatus.verified:
|
||||||
|
return 'Krower I';
|
||||||
|
case StaffStatus.pending:
|
||||||
|
case StaffStatus.completedProfile:
|
||||||
|
return 'Pending';
|
||||||
|
default:
|
||||||
|
return 'New';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<ProfileCubit, ProfileState>(
|
||||||
|
builder: (BuildContext context, ProfileState state) {
|
||||||
|
final Staff? profile = state.profile;
|
||||||
|
if (profile == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final String level = _mapStatusToLevel(profile.status);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: UiConstants.space3,
|
||||||
|
vertical: UiConstants.space1,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: UiColors.accent.withValues(alpha: 0.2),
|
||||||
|
borderRadius: BorderRadius.circular(UiConstants.space5),
|
||||||
|
),
|
||||||
|
child: Text(level, style: UiTypography.footnote1b.accent),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:core_localization/core_localization.dart';
|
|
||||||
import 'package:design_system/design_system.dart';
|
|
||||||
|
|
||||||
/// The header section of the staff profile page, containing avatar, name, and level.
|
|
||||||
///
|
|
||||||
/// Uses design system tokens for all colors, typography, and spacing.
|
|
||||||
class ProfileHeader extends StatelessWidget {
|
|
||||||
/// The staff member's full name
|
|
||||||
final String fullName;
|
|
||||||
|
|
||||||
/// The staff member's level (e.g., "Krower I")
|
|
||||||
final String level;
|
|
||||||
|
|
||||||
/// Optional photo URL for the avatar
|
|
||||||
final String? photoUrl;
|
|
||||||
|
|
||||||
/// Creates a [ProfileHeader].
|
|
||||||
const ProfileHeader({
|
|
||||||
super.key,
|
|
||||||
required this.fullName,
|
|
||||||
required this.level,
|
|
||||||
this.photoUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final TranslationsStaffProfileHeaderEn i18n = t.staff.profile.header;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: const EdgeInsets.fromLTRB(
|
|
||||||
UiConstants.space5,
|
|
||||||
UiConstants.space5,
|
|
||||||
UiConstants.space5,
|
|
||||||
UiConstants.space16,
|
|
||||||
),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: UiColors.primary,
|
|
||||||
borderRadius: BorderRadius.vertical(
|
|
||||||
bottom: Radius.circular(UiConstants.space6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: SafeArea(
|
|
||||||
bottom: false,
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
// Top Bar
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
i18n.title,
|
|
||||||
style: UiTypography.headline4m.textSecondary,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space8),
|
|
||||||
// Avatar Section
|
|
||||||
Stack(
|
|
||||||
alignment: Alignment.bottomRight,
|
|
||||||
children: <Widget>[
|
|
||||||
Container(
|
|
||||||
width: 112,
|
|
||||||
height: 112,
|
|
||||||
padding: const EdgeInsets.all(UiConstants.space1),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: <Color>[
|
|
||||||
UiColors.accent,
|
|
||||||
UiColors.accent.withValues(alpha: 0.5),
|
|
||||||
UiColors.primaryForeground,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
boxShadow: <BoxShadow>[
|
|
||||||
BoxShadow(
|
|
||||||
color: UiColors.foreground.withValues(alpha: 0.2),
|
|
||||||
blurRadius: 10,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(
|
|
||||||
color: UiColors.primaryForeground.withValues(alpha: 0.2),
|
|
||||||
width: 4,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: CircleAvatar(
|
|
||||||
backgroundColor: UiColors.background,
|
|
||||||
backgroundImage: photoUrl != null
|
|
||||||
? NetworkImage(photoUrl!)
|
|
||||||
: null,
|
|
||||||
child: photoUrl == null
|
|
||||||
? Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: <Color>[
|
|
||||||
UiColors.accent,
|
|
||||||
UiColors.accent.withValues(alpha: 0.7),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Text(
|
|
||||||
fullName.isNotEmpty
|
|
||||||
? fullName[0].toUpperCase()
|
|
||||||
: 'K',
|
|
||||||
style: UiTypography.displayM.primary,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: 36,
|
|
||||||
height: 36,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.primaryForeground,
|
|
||||||
shape: BoxShape.circle,
|
|
||||||
border: Border.all(color: UiColors.primary, width: 2),
|
|
||||||
boxShadow: <BoxShadow>[
|
|
||||||
BoxShadow(
|
|
||||||
color: UiColors.foreground.withValues(alpha: 0.1),
|
|
||||||
blurRadius: 4,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
UiIcons.camera,
|
|
||||||
size: 16,
|
|
||||||
color: UiColors.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space4),
|
|
||||||
Text(
|
|
||||||
fullName,
|
|
||||||
style: UiTypography.headline3m.textPlaceholder,
|
|
||||||
),
|
|
||||||
const SizedBox(height: UiConstants.space1),
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: UiConstants.space3,
|
|
||||||
vertical: UiConstants.space1,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: UiColors.accent.withValues(alpha: 0.2),
|
|
||||||
borderRadius: BorderRadius.circular(UiConstants.space5),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
level,
|
|
||||||
style: UiTypography.footnote1b.accent,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user