add alot
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widget_previews.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:kooltab2/viewmodels/pin_lock_view_model.dart';
|
||||
import 'package:kooltab2/viewmodels/product_list_view_model.dart';
|
||||
@@ -43,6 +41,12 @@ class BarScreenView extends StatelessWidget {
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
IconButton(
|
||||
tooltip: 'Settings',
|
||||
onPressed: () => context.go('/settings'),
|
||||
icon: const Icon(Icons.settings),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
IconButton(
|
||||
tooltip: 'logout',
|
||||
onPressed: (){
|
||||
@@ -234,12 +238,12 @@ class _ProductGrid extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: scheme.onSurface.withOpacity(0.05),
|
||||
color: scheme.onSurface.withValues(alpha: 0.05),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.inventory_2_outlined,
|
||||
size: 40,
|
||||
color: scheme.onSurface.withOpacity(0.3),
|
||||
color: scheme.onSurface.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -280,7 +284,7 @@ class _ProductGrid extends StatelessWidget {
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.onSurface.withOpacity(0.06),
|
||||
color: scheme.onSurface.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Row(
|
||||
@@ -289,7 +293,7 @@ class _ProductGrid extends StatelessWidget {
|
||||
Icon(
|
||||
Icons.info_outline_rounded,
|
||||
size: 14,
|
||||
color: scheme.onSurface.withOpacity(0.5),
|
||||
color: scheme.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
@@ -369,7 +373,7 @@ class _ProductTile extends StatelessWidget {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: scheme.onSurface.withOpacity(0.08), width: 1),
|
||||
border: Border.all(color: scheme.onSurface.withValues(alpha: 0.08), width: 1),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
@@ -396,7 +400,7 @@ class _ProductTile extends StatelessWidget {
|
||||
Icon(
|
||||
Icons.image_not_supported_outlined,
|
||||
size: 34,
|
||||
color: scheme.onSurface.withOpacity(0.3),
|
||||
color: scheme.onSurface.withValues(alpha: 0.3),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
@@ -406,9 +410,6 @@ class _ProductTile extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
// Scrim stays black regardless of theme — it's for legibility
|
||||
// of the (usually light/photographic) image beneath it, not
|
||||
// themed UI chrome.
|
||||
if (_hasImage)
|
||||
Positioned.fill(
|
||||
child: DecoratedBox(
|
||||
@@ -418,7 +419,7 @@ class _ProductTile extends StatelessWidget {
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.transparent,
|
||||
Colors.black.withOpacity(0.35),
|
||||
Colors.black.withValues(alpha: 0.35),
|
||||
],
|
||||
stops: const [0.6, 1.0],
|
||||
),
|
||||
@@ -541,7 +542,7 @@ class _TabPanelState extends State<_TabPanel> {
|
||||
vertical: 2,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.onSurface.withOpacity(0.06),
|
||||
color: scheme.onSurface.withValues(alpha: 0.06),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
@@ -577,7 +578,7 @@ class _TabPanelState extends State<_TabPanel> {
|
||||
Icon(
|
||||
Icons.receipt_long_outlined,
|
||||
size: 36,
|
||||
color: scheme.onSurface.withOpacity(0.25),
|
||||
color: scheme.onSurface.withValues(alpha: 0.25),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
@@ -627,7 +628,7 @@ class _OpenTabsList extends StatelessWidget {
|
||||
|
||||
return ListView.separated(
|
||||
itemCount: tabs.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 8),
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final tab = tabs[index];
|
||||
final selected = tab.id == selectedTabId;
|
||||
@@ -665,13 +666,13 @@ class _OpenTabsList extends StatelessWidget {
|
||||
duration: const Duration(milliseconds: 150),
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? primary.withOpacity(0.14)
|
||||
: scheme.onSurface.withOpacity(0.04),
|
||||
? primary.withValues(alpha: 0.14)
|
||||
: scheme.onSurface.withValues(alpha: 0.04),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(
|
||||
color: selected
|
||||
? primary.withOpacity(0.6)
|
||||
: scheme.onSurface.withOpacity(0.06),
|
||||
? primary.withValues(alpha: 0.6)
|
||||
: scheme.onSurface.withValues(alpha: 0.06),
|
||||
width: selected ? 1.4 : 1,
|
||||
),
|
||||
),
|
||||
@@ -764,7 +765,7 @@ class _SelectedTabDetails extends StatelessWidget {
|
||||
Icon(
|
||||
Icons.local_bar_outlined,
|
||||
size: 32,
|
||||
color: scheme.onSurface.withOpacity(0.25),
|
||||
color: scheme.onSurface.withValues(alpha: 0.25),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
@@ -776,7 +777,7 @@ class _SelectedTabDetails extends StatelessWidget {
|
||||
)
|
||||
: ListView.separated(
|
||||
itemCount: tab.items.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
separatorBuilder: (_, _) => const Divider(height: 1),
|
||||
itemBuilder: (context, index) {
|
||||
final item = tab.items[index];
|
||||
|
||||
@@ -791,10 +792,10 @@ class _SelectedTabDetails extends StatelessWidget {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.12),
|
||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.3),
|
||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
@@ -858,7 +859,7 @@ class _TabItemRow extends StatelessWidget {
|
||||
),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.onSurface.withOpacity(0.05),
|
||||
color: scheme.onSurface.withValues(alpha: 0.05),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Row(
|
||||
|
||||
@@ -118,7 +118,7 @@ class _HistoryScreenViewState extends State<HistoryScreenView> with RouteAware {
|
||||
: ListView.separated(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 20),
|
||||
itemCount: viewModel.closedTabs.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 10),
|
||||
itemBuilder: (context, index) {
|
||||
final closedTab = viewModel.closedTabs[index];
|
||||
|
||||
@@ -147,12 +147,12 @@ class _EmptyState extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: scheme.onSurface.withOpacity(0.05),
|
||||
color: scheme.onSurface.withValues(alpha: 0.05),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.history_rounded,
|
||||
size: 40,
|
||||
color: scheme.onSurface.withOpacity(0.3),
|
||||
color: scheme.onSurface.withValues(alpha: 0.3),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -191,9 +191,9 @@ class _ClosedTabCardState extends State<_ClosedTabCard> {
|
||||
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.onSurface.withOpacity(0.04),
|
||||
color: scheme.onSurface.withValues(alpha: 0.04),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: scheme.onSurface.withOpacity(0.06)),
|
||||
border: Border.all(color: scheme.onSurface.withValues(alpha: 0.06)),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Material(
|
||||
@@ -249,7 +249,7 @@ class _ClosedTabCardState extends State<_ClosedTabCard> {
|
||||
_expanded
|
||||
? Icons.expand_less_rounded
|
||||
: Icons.expand_more_rounded,
|
||||
color: scheme.onSurface.withOpacity(0.5),
|
||||
color: scheme.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -210,7 +210,7 @@ class _PinDots extends StatelessWidget {
|
||||
shape: BoxShape.circle,
|
||||
color: isFilled ? scheme.primary : Colors.transparent,
|
||||
border: Border.all(
|
||||
color: isFilled ? scheme.primary : scheme.onSurface.withOpacity(0.3),
|
||||
color: isFilled ? scheme.primary : scheme.onSurface.withValues(alpha: 0.3),
|
||||
width: 1.4,
|
||||
),
|
||||
),
|
||||
@@ -305,21 +305,21 @@ class _KeypadButton extends StatelessWidget {
|
||||
width: 72,
|
||||
height: 72,
|
||||
child: Material(
|
||||
color: scheme.onSurface.withOpacity(0.04),
|
||||
color: scheme.onSurface.withValues(alpha: 0.04),
|
||||
shape: const CircleBorder(),
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: onTap,
|
||||
child: Center(
|
||||
child: icon != null
|
||||
? Icon(icon, color: scheme.onSurface.withOpacity(0.8))
|
||||
? Icon(icon, color: scheme.onSurface.withValues(alpha: 0.8))
|
||||
: Text(
|
||||
label!,
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: onTap == null
|
||||
? scheme.onSurface.withOpacity(0.3)
|
||||
? scheme.onSurface.withValues(alpha: 0.3)
|
||||
: scheme.onSurface,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -6,9 +6,24 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import '../viewmodels/product_list_view_model.dart';
|
||||
|
||||
class ProductListView extends StatelessWidget {
|
||||
class ProductListView extends StatefulWidget {
|
||||
const ProductListView({super.key});
|
||||
|
||||
@override
|
||||
State<ProductListView> createState() => _ProductListViewState();
|
||||
}
|
||||
|
||||
class _ProductListViewState extends State<ProductListView> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<ProductListViewModel>().loadProducts();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final viewModel = context.watch<ProductListViewModel>();
|
||||
@@ -53,7 +68,7 @@ class ProductListView extends StatelessWidget {
|
||||
return ListView.separated(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: viewModel.products.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 8),
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
final product = viewModel.products[index];
|
||||
|
||||
|
||||
@@ -0,0 +1,345 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../models/settings.dart';
|
||||
import '../viewmodels/pin_lock_view_model.dart';
|
||||
import '../viewmodels/settings_view_model.dart';
|
||||
|
||||
class SettingsScreenView extends StatefulWidget {
|
||||
const SettingsScreenView({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsScreenView> createState() => _SettingsScreenViewState();
|
||||
}
|
||||
|
||||
class _SettingsScreenViewState extends State<SettingsScreenView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<SettingsViewModel>().ensureLoaded();
|
||||
context.read<PinLockViewModel>().ensureLoaded();
|
||||
});
|
||||
}
|
||||
|
||||
Future<String?> _promptPin(String title, {String hint = 'Enter PIN'}) {
|
||||
final controller = TextEditingController();
|
||||
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(title),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
obscureText: true,
|
||||
keyboardType: TextInputType.number,
|
||||
maxLength: 6,
|
||||
decoration: InputDecoration(hintText: hint),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, controller.text),
|
||||
child: const Text('Confirm'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showError(String message) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message)));
|
||||
}
|
||||
|
||||
Future<void> _enablePin() async {
|
||||
final pin = await _promptPin('Set PIN Code', hint: '4–6 digits');
|
||||
if (pin == null) return;
|
||||
|
||||
if (pin.length < 4) {
|
||||
_showError('PIN must be at least 4 digits');
|
||||
return;
|
||||
}
|
||||
|
||||
final pinLockViewModel = context.read<PinLockViewModel>();
|
||||
final success = await pinLockViewModel.setPin(pin);
|
||||
|
||||
if (!success) {
|
||||
_showError(pinLockViewModel.errorMessage ?? 'Could not set PIN.');
|
||||
return;
|
||||
}
|
||||
|
||||
await context.read<SettingsViewModel>().updatePinRequired(true);
|
||||
}
|
||||
|
||||
Future<void> _changePin() async {
|
||||
final current = await _promptPin('Enter Current PIN');
|
||||
if (current == null) return;
|
||||
|
||||
final newPin = await _promptPin('Enter New PIN', hint: '4–6 digits');
|
||||
if (newPin == null) return;
|
||||
|
||||
if (newPin.length < 4) {
|
||||
_showError('PIN must be at least 4 digits');
|
||||
return;
|
||||
}
|
||||
|
||||
final pinLockViewModel = context.read<PinLockViewModel>();
|
||||
final success = await pinLockViewModel.changePin(
|
||||
currentPin: current,
|
||||
newPin: newPin,
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
_showError(pinLockViewModel.errorMessage ?? 'Could not change PIN.');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _disablePin() async {
|
||||
final current = await _promptPin('Enter Current PIN to Disable');
|
||||
if (current == null) return;
|
||||
|
||||
final pinLockViewModel = context.read<PinLockViewModel>();
|
||||
final success = await pinLockViewModel.disablePin(current);
|
||||
|
||||
if (!success) {
|
||||
_showError(pinLockViewModel.errorMessage ?? 'Could not disable PIN.');
|
||||
return;
|
||||
}
|
||||
|
||||
await context.read<SettingsViewModel>().updatePinRequired(false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final settingsViewModel = context.watch<SettingsViewModel>();
|
||||
final pinLockViewModel = context.watch<PinLockViewModel>();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => context.go('/bar'),
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
const Text('Settings'),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
final isLoading = settingsViewModel.isLoading || pinLockViewModel.isLoading;
|
||||
final hasLoaded = settingsViewModel.hasLoaded && pinLockViewModel.hasLoaded;
|
||||
|
||||
if (isLoading && !hasLoaded) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(strokeWidth: 2.5),
|
||||
);
|
||||
}
|
||||
|
||||
final settings = settingsViewModel.settings;
|
||||
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
children: [
|
||||
_SettingsSection(
|
||||
title: 'Security',
|
||||
children: [
|
||||
_SettingsSwitchTile(
|
||||
icon: Icons.lock_outline_rounded,
|
||||
title: 'PIN Required',
|
||||
value: settings.pinRequired,
|
||||
onChanged: (value) {
|
||||
if (value) {
|
||||
_enablePin();
|
||||
} else {
|
||||
_disablePin();
|
||||
}
|
||||
},
|
||||
),
|
||||
if (settings.pinRequired)
|
||||
_SettingsTile(
|
||||
icon: Icons.pin_rounded,
|
||||
title: 'Change PIN',
|
||||
onTap: _changePin,
|
||||
),
|
||||
],
|
||||
),
|
||||
_SettingsSection(
|
||||
title: 'Appearance',
|
||||
children: [
|
||||
_SettingsTile(
|
||||
icon: Icons.brightness_6_rounded,
|
||||
title: 'Theme',
|
||||
subtitle: switch (settings.themeMode) {
|
||||
AppThemeMode.system => 'System',
|
||||
AppThemeMode.light => 'Light',
|
||||
AppThemeMode.dark => 'Dark',
|
||||
},
|
||||
onTap: () async {
|
||||
final selected = await showModalBottomSheet<AppThemeMode>(
|
||||
context: context,
|
||||
builder: (context) => SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: AppThemeMode.values.map((mode) {
|
||||
return RadioListTile<AppThemeMode>(
|
||||
value: mode,
|
||||
groupValue: settings.themeMode,
|
||||
title: Text(switch (mode) {
|
||||
AppThemeMode.system => 'System',
|
||||
AppThemeMode.light => 'Light',
|
||||
AppThemeMode.dark => 'Dark',
|
||||
}),
|
||||
onChanged: (value) => Navigator.pop(context, value),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (selected != null) {
|
||||
await settingsViewModel.updateThemeMode(selected);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingsSection extends StatelessWidget {
|
||||
final String title;
|
||||
final List<Widget> children;
|
||||
|
||||
const _SettingsSection({required this.title, required this.children});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title.toUpperCase(),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w700,
|
||||
letterSpacing: 0.5,
|
||||
color: scheme.onSurface.withValues(alpha: 0.5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: scheme.onSurface.withValues(alpha: 0.04),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: scheme.onSurface.withValues(alpha: 0.06)),
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(children: children),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingsTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const _SettingsTile({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 22, color: scheme.onSurface.withValues(alpha: 0.7)),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, color: scheme.onSurface),
|
||||
),
|
||||
),
|
||||
if (subtitle != null) ...[
|
||||
Text(subtitle!, style: Theme.of(context).textTheme.bodySmall),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
if (onTap != null)
|
||||
Icon(Icons.chevron_right_rounded, color: scheme.onSurface.withValues(alpha: 0.3)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingsSwitchTile extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final bool value;
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
const _SettingsSwitchTile({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 22, color: scheme.onSurface.withValues(alpha: 0.7)),
|
||||
const SizedBox(width: 14),
|
||||
Expanded(
|
||||
child: Text(
|
||||
title,
|
||||
style: TextStyle(fontWeight: FontWeight.w600, color: scheme.onSurface),
|
||||
),
|
||||
),
|
||||
Switch(value: value, onChanged: onChanged),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user