bharat_erp/lib/modules/settings/presentation/widgets/settings_widgets.dart
2026-07-16 15:18:59 +05:30

308 lines
8.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/utils/responsive_utils.dart';
import '../../../../shared/widgets/app_card.dart';
import '../../../../shared/widgets/app_dropdown.dart';
import '../../../../shared/widgets/app_form_toggle_field.dart';
import '../../../../shared/widgets/app_searchable_dropdown.dart';
import '../../../../shared/widgets/page_header.dart';
import '../../domain/entities/app_settings.dart';
class SettingsPageLayout extends StatelessWidget {
const SettingsPageLayout({
super.key,
required this.title,
this.subtitle,
required this.child,
this.actions,
});
final String title;
final String? subtitle;
final Widget child;
final List<Widget>? actions;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Material(
color: theme.colorScheme.surface,
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 900),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
IconButton(
icon: const Icon(Icons.arrow_back),
tooltip: 'Back to Settings',
onPressed: () => context.go('/settings'),
),
Expanded(
child: PageHeader(
title: title,
subtitle: subtitle,
actions: actions,
),
),
],
),
child,
],
),
),
),
),
);
}
}
class SettingsFormCard extends StatelessWidget {
const SettingsFormCard({
super.key,
required this.title,
required this.children,
this.subtitle,
});
final String title;
final String? subtitle;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return AppCard(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: Theme.of(context).textTheme.titleMedium),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
const SizedBox(height: 20),
...children,
],
),
),
);
}
}
class SettingsSwitchTile extends StatelessWidget {
const SettingsSwitchTile({
super.key,
required this.title,
required this.value,
required this.onChanged,
this.subtitle,
});
final String title;
final String? subtitle;
final bool value;
final ValueChanged<bool> onChanged;
@override
Widget build(BuildContext context) {
return AppFormToggleField(
label: title,
subtitle: subtitle,
value: value,
onChanged: onChanged,
);
}
}
class SettingsDropdownField<T> extends StatelessWidget {
const SettingsDropdownField({
super.key,
required this.label,
required this.value,
required this.items,
required this.itemLabel,
required this.onChanged,
});
final String label;
final T value;
final List<T> items;
final String Function(T) itemLabel;
final ValueChanged<T?> onChanged;
@override
Widget build(BuildContext context) {
return AppSearchableDropdown<T>(
label: label,
value: value,
searchHint: 'Search ${label.toLowerCase()}...',
options: items
.map(
(item) => AppDropdownOption(
value: item,
label: itemLabel(item),
),
)
.toList(),
onChanged: onChanged,
);
}
}
class SettingsSectionTile extends StatelessWidget {
const SettingsSectionTile({
super.key,
required this.title,
required this.subtitle,
required this.icon,
required this.onTap,
this.badge,
this.enabled = true,
});
final String title;
final String subtitle;
final IconData icon;
final VoidCallback onTap;
final String? badge;
final bool enabled;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return AppCard(
clipBehavior: Clip.antiAlias,
onTap: enabled ? onTap : null,
enableHover: enabled,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: theme.colorScheme.primary.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: theme.colorScheme.primary),
),
const Spacer(),
Icon(
enabled ? Icons.chevron_right : Icons.lock_outline,
color: theme.colorScheme.onSurfaceVariant,
),
],
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: Text(
title,
style: theme.textTheme.titleSmall,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (badge != null) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: theme.colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(8),
),
child: Text(
badge!,
style: theme.textTheme.labelSmall,
),
),
],
],
),
const SizedBox(height: 6),
Expanded(
child: Text(
subtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
],
),
),
);
}
}
class SettingsSectionGrid extends StatelessWidget {
const SettingsSectionGrid({
super.key,
required this.sections,
required this.onSectionTap,
this.enabled = true,
this.badge,
});
final List<SettingsSection> sections;
final void Function(SettingsSection section) onSectionTap;
final bool enabled;
final String? badge;
int _crossAxisCount(BuildContext context) {
if (context.isMobile) return 1;
if (context.isTablet) return 2;
return 3;
}
@override
Widget build(BuildContext context) {
return GridView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: sections.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: _crossAxisCount(context),
crossAxisSpacing: 16,
mainAxisSpacing: 16,
mainAxisExtent: 168,
),
itemBuilder: (context, index) {
final section = sections[index];
return SettingsSectionTile(
title: section.title,
subtitle: section.subtitle,
icon: section.icon,
badge: badge,
enabled: enabled,
onTap: () => onSectionTap(section),
);
},
);
}
}