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_state.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_stats_card.dart';
|
||||
import '../widgets/sections/index.dart';
|
||||
@@ -25,19 +25,6 @@ class StaffProfilePage extends StatelessWidget {
|
||||
/// Creates a [StaffProfilePage].
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final ProfileCubit cubit = Modular.get<ProfileCubit>();
|
||||
@@ -106,7 +93,6 @@ class StaffProfilePage extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
ProfileHeader(
|
||||
fullName: profile.name,
|
||||
level: _mapStatusToLevel(profile.status),
|
||||
photoUrl: profile.avatar,
|
||||
),
|
||||
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