feat: Add post-save navigation to staff profile for emergency contact and experience, remove a placeholder page, and refine bloc usage and UI rendering.
This commit is contained in:
@@ -64,7 +64,7 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
/// This is typically called after successful phone verification for new
|
/// This is typically called after successful phone verification for new
|
||||||
/// staff members. Uses pushReplacement to prevent going back to verification.
|
/// staff members. Uses pushReplacement to prevent going back to verification.
|
||||||
void toProfileSetup() {
|
void toProfileSetup() {
|
||||||
pushReplacementNamed(StaffPaths.profileSetup);
|
pushNamed(StaffPaths.profileSetup);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -76,7 +76,7 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
/// This is the main landing page for authenticated staff members.
|
/// This is the main landing page for authenticated staff members.
|
||||||
/// Displays shift cards, quick actions, and notifications.
|
/// Displays shift cards, quick actions, and notifications.
|
||||||
void toStaffHome() {
|
void toStaffHome() {
|
||||||
pushNamed(StaffPaths.home);
|
pushNamedAndRemoveUntil(StaffPaths.home, (_) => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigates to the staff main shell.
|
/// Navigates to the staff main shell.
|
||||||
@@ -84,7 +84,7 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
/// This is the container with bottom navigation. Navigates to home tab
|
/// This is the container with bottom navigation. Navigates to home tab
|
||||||
/// by default. Usually you'd navigate to a specific tab instead.
|
/// by default. Usually you'd navigate to a specific tab instead.
|
||||||
void toStaffMain() {
|
void toStaffMain() {
|
||||||
navigate('${StaffPaths.main}/home/');
|
pushNamedAndRemoveUntil('${StaffPaths.main}/home/', (_) => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -113,8 +113,9 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
if (refreshAvailable == true) {
|
if (refreshAvailable == true) {
|
||||||
args['refreshAvailable'] = true;
|
args['refreshAvailable'] = true;
|
||||||
}
|
}
|
||||||
navigate(
|
pushNamedAndRemoveUntil(
|
||||||
StaffPaths.shifts,
|
StaffPaths.shifts,
|
||||||
|
(_) => false,
|
||||||
arguments: args.isEmpty ? null : args,
|
arguments: args.isEmpty ? null : args,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -123,21 +124,21 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
///
|
///
|
||||||
/// View payment history, earnings breakdown, and tax information.
|
/// View payment history, earnings breakdown, and tax information.
|
||||||
void toPayments() {
|
void toPayments() {
|
||||||
navigate(StaffPaths.payments);
|
pushNamedAndRemoveUntil(StaffPaths.payments, (_) => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigates to the Clock In tab.
|
/// Navigates to the Clock In tab.
|
||||||
///
|
///
|
||||||
/// Access time tracking interface for active shifts.
|
/// Access time tracking interface for active shifts.
|
||||||
void toClockIn() {
|
void toClockIn() {
|
||||||
navigate(StaffPaths.clockIn);
|
pushNamedAndRemoveUntil(StaffPaths.clockIn, (_) => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Navigates to the Profile tab.
|
/// Navigates to the Profile tab.
|
||||||
///
|
///
|
||||||
/// Manage personal information, documents, and preferences.
|
/// Manage personal information, documents, and preferences.
|
||||||
void toProfile() {
|
void toProfile() {
|
||||||
navigate(StaffPaths.profile);
|
pushNamedAndRemoveUntil(StaffPaths.profile, (_) => false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
@@ -155,10 +156,7 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
/// The shift object is passed as an argument and can be retrieved
|
/// The shift object is passed as an argument and can be retrieved
|
||||||
/// in the details page.
|
/// in the details page.
|
||||||
void toShiftDetails(Shift shift) {
|
void toShiftDetails(Shift shift) {
|
||||||
navigate(
|
navigate(StaffPaths.shiftDetails(shift.id), arguments: shift);
|
||||||
StaffPaths.shiftDetails(shift.id),
|
|
||||||
arguments: shift,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes the shift details page (alternative method).
|
/// Pushes the shift details page (alternative method).
|
||||||
@@ -167,10 +165,7 @@ extension StaffNavigator on IModularNavigator {
|
|||||||
/// Use this when you want to add the details page to the stack rather
|
/// Use this when you want to add the details page to the stack rather
|
||||||
/// than replacing the current route.
|
/// than replacing the current route.
|
||||||
void pushShiftDetails(Shift shift) {
|
void pushShiftDetails(Shift shift) {
|
||||||
pushNamed(
|
pushNamed(StaffPaths.shiftDetails(shift.id), arguments: shift);
|
||||||
StaffPaths.shiftDetails(shift.id),
|
|
||||||
arguments: shift,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ class HomeCubit extends Cubit<HomeState> with BlocErrorHandler<HomeState> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (String errorKey) {
|
onError: (String errorKey) {
|
||||||
if (isClosed) return state; // Avoid state emission if closed, though emit handles it gracefully usually
|
if (isClosed)
|
||||||
|
return state; // Avoid state emission if closed, though emit handles it gracefully usually
|
||||||
return state.copyWith(status: HomeStatus.error, errorMessage: errorKey);
|
return state.copyWith(status: HomeStatus.error, errorMessage: errorKey);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void toggleAutoMatch(bool enabled) {
|
void toggleAutoMatch(bool enabled) {
|
||||||
emit(state.copyWith(autoMatchEnabled: enabled));
|
emit(state.copyWith(autoMatchEnabled: enabled));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,6 @@ class WorkerHomePage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
BlocBuilder<HomeCubit, HomeState>(
|
BlocBuilder<HomeCubit, HomeState>(
|
||||||
buildWhen: (previous, current) =>
|
|
||||||
previous.isProfileComplete !=
|
|
||||||
current.isProfileComplete,
|
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.isProfileComplete) return const SizedBox();
|
if (state.isProfileComplete) return const SizedBox();
|
||||||
return PlaceholderBanner(
|
return PlaceholderBanner(
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ class EmergencyContactScreen extends StatelessWidget {
|
|||||||
body: BlocProvider(
|
body: BlocProvider(
|
||||||
create: (context) => Modular.get<EmergencyContactBloc>(),
|
create: (context) => Modular.get<EmergencyContactBloc>(),
|
||||||
child: BlocConsumer<EmergencyContactBloc, EmergencyContactState>(
|
child: BlocConsumer<EmergencyContactBloc, EmergencyContactState>(
|
||||||
|
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.status == EmergencyContactStatus.failure) {
|
if (state.status == EmergencyContactStatus.failure) {
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
|
|||||||
@@ -2,13 +2,17 @@ import 'package:core_localization/core_localization.dart';
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:krow_core/core.dart';
|
||||||
import '../blocs/emergency_contact_bloc.dart';
|
import '../blocs/emergency_contact_bloc.dart';
|
||||||
|
|
||||||
class EmergencyContactSaveButton extends StatelessWidget {
|
class EmergencyContactSaveButton extends StatelessWidget {
|
||||||
const EmergencyContactSaveButton({super.key});
|
const EmergencyContactSaveButton({super.key});
|
||||||
|
|
||||||
void _onSave(BuildContext context) {
|
void _onSave(BuildContext context) {
|
||||||
context.read<EmergencyContactBloc>().add(EmergencyContactsSaved());
|
BlocProvider.of<EmergencyContactBloc>(
|
||||||
|
context,
|
||||||
|
).add(EmergencyContactsSaved());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -19,10 +23,13 @@ class EmergencyContactSaveButton extends StatelessWidget {
|
|||||||
if (state.status == EmergencyContactStatus.saved) {
|
if (state.status == EmergencyContactStatus.saved) {
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
context,
|
context,
|
||||||
message: t.staff.profile.menu_items.emergency_contact_page.save_success,
|
message:
|
||||||
|
t.staff.profile.menu_items.emergency_contact_page.save_success,
|
||||||
type: UiSnackbarType.success,
|
type: UiSnackbarType.success,
|
||||||
margin: const EdgeInsets.only(bottom: 150, left: 16, right: 16),
|
margin: const EdgeInsets.only(bottom: 150, left: 16, right: 16),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Modular.to.toProfile();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -36,8 +43,9 @@ class EmergencyContactSaveButton extends StatelessWidget {
|
|||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: UiButton.primary(
|
child: UiButton.primary(
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
onPressed:
|
onPressed: state.isValid && !isLoading
|
||||||
state.isValid && !isLoading ? () => _onSave(context) : null,
|
? () => _onSave(context)
|
||||||
|
: null,
|
||||||
child: isLoading
|
child: isLoading
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
height: 20.0,
|
height: 20.0,
|
||||||
@@ -49,7 +57,14 @@ class EmergencyContactSaveButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Text(t.staff.profile.menu_items.emergency_contact_page.save_continue),
|
: Text(
|
||||||
|
t
|
||||||
|
.staff
|
||||||
|
.profile
|
||||||
|
.menu_items
|
||||||
|
.emergency_contact_page
|
||||||
|
.save_continue,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:design_system/design_system.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
import '../blocs/experience_bloc.dart';
|
import '../blocs/experience_bloc.dart';
|
||||||
@@ -13,34 +14,57 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
|
|
||||||
String _getIndustryLabel(dynamic node, Industry industry) {
|
String _getIndustryLabel(dynamic node, Industry industry) {
|
||||||
switch (industry) {
|
switch (industry) {
|
||||||
case Industry.hospitality: return node.hospitality;
|
case Industry.hospitality:
|
||||||
case Industry.foodService: return node.food_service;
|
return node.hospitality;
|
||||||
case Industry.warehouse: return node.warehouse;
|
case Industry.foodService:
|
||||||
case Industry.events: return node.events;
|
return node.food_service;
|
||||||
case Industry.retail: return node.retail;
|
case Industry.warehouse:
|
||||||
case Industry.healthcare: return node.healthcare;
|
return node.warehouse;
|
||||||
case Industry.other: return node.other;
|
case Industry.events:
|
||||||
|
return node.events;
|
||||||
|
case Industry.retail:
|
||||||
|
return node.retail;
|
||||||
|
case Industry.healthcare:
|
||||||
|
return node.healthcare;
|
||||||
|
case Industry.other:
|
||||||
|
return node.other;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getSkillLabel(dynamic node, ExperienceSkill skill) {
|
String _getSkillLabel(dynamic node, ExperienceSkill skill) {
|
||||||
switch (skill) {
|
switch (skill) {
|
||||||
case ExperienceSkill.foodService: return node.food_service;
|
case ExperienceSkill.foodService:
|
||||||
case ExperienceSkill.bartending: return node.bartending;
|
return node.food_service;
|
||||||
case ExperienceSkill.eventSetup: return node.event_setup;
|
case ExperienceSkill.bartending:
|
||||||
case ExperienceSkill.hospitality: return node.hospitality;
|
return node.bartending;
|
||||||
case ExperienceSkill.warehouse: return node.warehouse;
|
case ExperienceSkill.eventSetup:
|
||||||
case ExperienceSkill.customerService: return node.customer_service;
|
return node.event_setup;
|
||||||
case ExperienceSkill.cleaning: return node.cleaning;
|
case ExperienceSkill.hospitality:
|
||||||
case ExperienceSkill.security: return node.security;
|
return node.hospitality;
|
||||||
case ExperienceSkill.retail: return node.retail;
|
case ExperienceSkill.warehouse:
|
||||||
case ExperienceSkill.driving: return node.driving;
|
return node.warehouse;
|
||||||
case ExperienceSkill.cooking: return node.cooking;
|
case ExperienceSkill.customerService:
|
||||||
case ExperienceSkill.cashier: return node.cashier;
|
return node.customer_service;
|
||||||
case ExperienceSkill.server: return node.server;
|
case ExperienceSkill.cleaning:
|
||||||
case ExperienceSkill.barista: return node.barista;
|
return node.cleaning;
|
||||||
case ExperienceSkill.hostHostess: return node.host_hostess;
|
case ExperienceSkill.security:
|
||||||
case ExperienceSkill.busser: return node.busser;
|
return node.security;
|
||||||
|
case ExperienceSkill.retail:
|
||||||
|
return node.retail;
|
||||||
|
case ExperienceSkill.driving:
|
||||||
|
return node.driving;
|
||||||
|
case ExperienceSkill.cooking:
|
||||||
|
return node.cooking;
|
||||||
|
case ExperienceSkill.cashier:
|
||||||
|
return node.cashier;
|
||||||
|
case ExperienceSkill.server:
|
||||||
|
return node.server;
|
||||||
|
case ExperienceSkill.barista:
|
||||||
|
return node.barista;
|
||||||
|
case ExperienceSkill.hostHostess:
|
||||||
|
return node.host_hostess;
|
||||||
|
case ExperienceSkill.busser:
|
||||||
|
return node.busser;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +75,7 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: UiAppBar(
|
appBar: UiAppBar(
|
||||||
title: i18n.title,
|
title: i18n.title,
|
||||||
onLeadingPressed: () => Modular.to.pop(),
|
onLeadingPressed: () => Modular.to.toProfile(),
|
||||||
),
|
),
|
||||||
body: BlocProvider<ExperienceBloc>(
|
body: BlocProvider<ExperienceBloc>(
|
||||||
create: (context) => Modular.get<ExperienceBloc>(),
|
create: (context) => Modular.get<ExperienceBloc>(),
|
||||||
@@ -68,7 +92,6 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
right: UiConstants.space4,
|
right: UiConstants.space4,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Modular.to.pop();
|
|
||||||
} else if (state.status == ExperienceStatus.failure) {
|
} else if (state.status == ExperienceStatus.failure) {
|
||||||
UiSnackbar.show(
|
UiSnackbar.show(
|
||||||
context,
|
context,
|
||||||
@@ -106,13 +129,13 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
.map(
|
.map(
|
||||||
(i) => UiChip(
|
(i) => UiChip(
|
||||||
label: _getIndustryLabel(i18n.industries, i),
|
label: _getIndustryLabel(i18n.industries, i),
|
||||||
isSelected:
|
isSelected: state.selectedIndustries.contains(
|
||||||
state.selectedIndustries.contains(i),
|
i,
|
||||||
onTap: () =>
|
),
|
||||||
BlocProvider.of<ExperienceBloc>(context)
|
onTap: () => BlocProvider.of<ExperienceBloc>(
|
||||||
.add(ExperienceIndustryToggled(i)),
|
context,
|
||||||
variant:
|
).add(ExperienceIndustryToggled(i)),
|
||||||
state.selectedIndustries.contains(i)
|
variant: state.selectedIndustries.contains(i)
|
||||||
? UiChipVariant.primary
|
? UiChipVariant.primary
|
||||||
: UiChipVariant.secondary,
|
: UiChipVariant.secondary,
|
||||||
),
|
),
|
||||||
@@ -133,11 +156,12 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
.map(
|
.map(
|
||||||
(s) => UiChip(
|
(s) => UiChip(
|
||||||
label: _getSkillLabel(i18n.skills, s),
|
label: _getSkillLabel(i18n.skills, s),
|
||||||
isSelected:
|
isSelected: state.selectedSkills.contains(
|
||||||
state.selectedSkills.contains(s.value),
|
s.value,
|
||||||
onTap: () =>
|
),
|
||||||
BlocProvider.of<ExperienceBloc>(context)
|
onTap: () => BlocProvider.of<ExperienceBloc>(
|
||||||
.add(ExperienceSkillToggled(s.value)),
|
context,
|
||||||
|
).add(ExperienceSkillToggled(s.value)),
|
||||||
variant:
|
variant:
|
||||||
state.selectedSkills.contains(s.value)
|
state.selectedSkills.contains(s.value)
|
||||||
? UiChipVariant.primary
|
? UiChipVariant.primary
|
||||||
@@ -177,10 +201,7 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
spacing: UiConstants.space2,
|
spacing: UiConstants.space2,
|
||||||
runSpacing: UiConstants.space2,
|
runSpacing: UiConstants.space2,
|
||||||
children: customSkills.map((skill) {
|
children: customSkills.map((skill) {
|
||||||
return UiChip(
|
return UiChip(label: skill, variant: UiChipVariant.accent);
|
||||||
label: skill,
|
|
||||||
variant: UiChipVariant.accent,
|
|
||||||
);
|
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -202,8 +223,9 @@ class ExperiencePage extends StatelessWidget {
|
|||||||
child: UiButton.primary(
|
child: UiButton.primary(
|
||||||
onPressed: state.status == ExperienceStatus.loading
|
onPressed: state.status == ExperienceStatus.loading
|
||||||
? null
|
? null
|
||||||
: () => BlocProvider.of<ExperienceBloc>(context)
|
: () => BlocProvider.of<ExperienceBloc>(
|
||||||
.add(ExperienceSubmitted()),
|
context,
|
||||||
|
).add(ExperienceSubmitted()),
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
text: state.status == ExperienceStatus.loading
|
text: state.status == ExperienceStatus.loading
|
||||||
? null
|
? null
|
||||||
|
|||||||
@@ -12,13 +12,18 @@ class StaffMainCubit extends Cubit<StaffMainState> implements Disposable {
|
|||||||
super(const StaffMainState()) {
|
super(const StaffMainState()) {
|
||||||
Modular.to.addListener(_onRouteChanged);
|
Modular.to.addListener(_onRouteChanged);
|
||||||
_onRouteChanged();
|
_onRouteChanged();
|
||||||
_loadProfileCompletion();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final GetProfileCompletionUseCase _getProfileCompletionUsecase;
|
final GetProfileCompletionUseCase _getProfileCompletionUsecase;
|
||||||
|
bool _isLoadingCompletion = false;
|
||||||
|
|
||||||
void _onRouteChanged() {
|
void _onRouteChanged() {
|
||||||
if (isClosed) return;
|
if (isClosed) return;
|
||||||
|
|
||||||
|
// Refresh completion status whenever route changes to catch profile updates
|
||||||
|
// only if it's not already complete.
|
||||||
|
refreshProfileCompletion();
|
||||||
|
|
||||||
final String path = Modular.to.path;
|
final String path = Modular.to.path;
|
||||||
int newIndex = state.currentIndex;
|
int newIndex = state.currentIndex;
|
||||||
|
|
||||||
@@ -41,7 +46,10 @@ class StaffMainCubit extends Cubit<StaffMainState> implements Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Loads the profile completion status.
|
/// Loads the profile completion status.
|
||||||
Future<void> _loadProfileCompletion() async {
|
Future<void> refreshProfileCompletion() async {
|
||||||
|
if (_isLoadingCompletion || isClosed) return;
|
||||||
|
|
||||||
|
_isLoadingCompletion = true;
|
||||||
try {
|
try {
|
||||||
final isComplete = await _getProfileCompletionUsecase();
|
final isComplete = await _getProfileCompletionUsecase();
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
@@ -53,6 +61,8 @@ class StaffMainCubit extends Cubit<StaffMainState> implements Disposable {
|
|||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
emit(state.copyWith(isProfileComplete: true));
|
emit(state.copyWith(isProfileComplete: true));
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
_isLoadingCompletion = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class PlaceholderPage extends StatelessWidget {
|
|
||||||
const PlaceholderPage({required this.title, super.key});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(title: Text(title)),
|
|
||||||
body: Center(child: Text('$title Page')),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user