mirror of
https://github.com/vleeuwenmenno/supplements.git
synced 2025-09-11 18:29:12 +02:00
Refactor and enhance UI components across multiple screens
- Updated date and time formatting in debug notifications screen for clarity. - Wrapped context-dependent state updates in post-frame callbacks in history screen to ensure proper context usage. - Improved layout and styling in settings screen by reordering radio list tiles. - Enhanced logging in auto sync service for better error tracking. - Added context mounted checks in notification router to prevent errors during navigation. - Updated bulk take dialog to use new UI components from shadcn_ui package. - Refactored take supplement dialog to utilize shadcn_ui for a more modern look and feel. - Adjusted info chip and supplement card widgets to use updated color schemes and layouts. - Updated pubspec.yaml and pubspec.lock to include new dependencies and versions.
This commit is contained in:
14
.kilocode/mcp.json
Normal file
14
.kilocode/mcp.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"context7": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"@upstash/context7-mcp"
|
||||||
|
],
|
||||||
|
"env": {
|
||||||
|
"DEFAULT_MINIMUM_TOKENS": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; // Import this
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; // Import this
|
||||||
import 'package:supplements/logging.dart';
|
import 'package:supplements/logging.dart';
|
||||||
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||||
|
|
||||||
import 'providers/settings_provider.dart';
|
import 'providers/settings_provider.dart';
|
||||||
import 'providers/simple_sync_provider.dart';
|
import 'providers/simple_sync_provider.dart';
|
||||||
@@ -92,22 +93,16 @@ class MyApp extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return MaterialApp(
|
return ShadApp(
|
||||||
navigatorKey: navigatorKey,
|
navigatorKey: navigatorKey,
|
||||||
title: 'Supplements Tracker',
|
title: 'Supplements Tracker',
|
||||||
theme: ThemeData(
|
theme: ShadThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(
|
|
||||||
seedColor: Colors.blue,
|
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
|
colorScheme: const ShadBlueColorScheme.light(),
|
||||||
),
|
),
|
||||||
useMaterial3: true,
|
darkTheme: ShadThemeData(
|
||||||
),
|
|
||||||
darkTheme: ThemeData(
|
|
||||||
colorScheme: ColorScheme.fromSeed(
|
|
||||||
seedColor: Colors.blue,
|
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
),
|
colorScheme: const ShadBlueColorScheme.dark(),
|
||||||
useMaterial3: true,
|
|
||||||
),
|
),
|
||||||
themeMode: settingsProvider.themeMode,
|
themeMode: settingsProvider.themeMode,
|
||||||
home: const HomeScreen(),
|
home: const HomeScreen(),
|
||||||
|
@@ -166,7 +166,7 @@ class _AddSupplementScreenState extends State<AddSupplementScreen> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
value: controller.selectedUnit,
|
initialValue: controller.selectedUnit,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Unit',
|
labelText: 'Unit',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
@@ -317,7 +317,7 @@ class _AddSupplementScreenState extends State<AddSupplementScreen> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
value: _selectedUnitType,
|
initialValue: _selectedUnitType,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: 'Type',
|
labelText: 'Type',
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
|
@@ -164,7 +164,7 @@ class _ArchivedSupplementCard extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withValues(alpha: 0.3),
|
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.2),
|
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.2),
|
||||||
width: 1,
|
width: 1,
|
||||||
@@ -262,7 +262,7 @@ class _ArchivedSupplementCard extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withValues(alpha: 0.3),
|
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@@ -383,8 +383,8 @@ class _DebugNotificationsScreenState extends State<DebugNotificationsScreen> {
|
|||||||
final createdAt = DateTime.fromMillisecondsSinceEpoch(e.createdAtEpochMs).toLocal();
|
final createdAt = DateTime.fromMillisecondsSinceEpoch(e.createdAtEpochMs).toLocal();
|
||||||
|
|
||||||
// Format times more clearly
|
// Format times more clearly
|
||||||
final scheduledStr = '${scheduledAt.toString().substring(0, 16)}';
|
final scheduledStr = scheduledAt.toString().substring(0, 16);
|
||||||
final createdStr = '${createdAt.toString().substring(0, 16)}';
|
final createdStr = createdAt.toString().substring(0, 16);
|
||||||
|
|
||||||
// Show status and timing info
|
// Show status and timing info
|
||||||
final statusStr = inQueue ? '🟡 Pending' : '✅ Completed/Canceled';
|
final statusStr = inQueue ? '🟡 Pending' : '✅ Completed/Canceled';
|
||||||
|
@@ -55,7 +55,9 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||||||
_selectedMonth--;
|
_selectedMonth--;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
|
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.chevron_left),
|
icon: const Icon(Icons.chevron_left),
|
||||||
),
|
),
|
||||||
@@ -85,7 +87,9 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||||||
_selectedMonth++;
|
_selectedMonth++;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
|
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.chevron_right),
|
icon: const Icon(Icons.chevron_right),
|
||||||
@@ -187,12 +191,15 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
|
if (!context.mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedMonth = picked.month;
|
_selectedMonth = picked.month;
|
||||||
_selectedYear = picked.year;
|
_selectedYear = picked.year;
|
||||||
_selectedDay = picked; // Set the selected day to the picked date
|
_selectedDay = picked; // Set the selected day to the picked date
|
||||||
});
|
});
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
|
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,13 +217,16 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await context.read<SupplementProvider>().deleteIntake(intakeId);
|
await context.read<SupplementProvider>().deleteIntake(intakeId);
|
||||||
|
if (!context.mounted) return;
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
// Force refresh of the UI
|
// Force refresh of the UI
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
||||||
// Force refresh of the current view data
|
// Force refresh of the current view data
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
|
context.read<SupplementProvider>().loadMonthlyIntakes(_selectedYear, _selectedMonth);
|
||||||
|
});
|
||||||
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
@@ -252,7 +262,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||||||
final calendarHeight = isWideScreen ? 400.0 : calendarContentHeight;
|
final calendarHeight = isWideScreen ? 400.0 : calendarContentHeight;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
child: Container(
|
child: SizedBox(
|
||||||
height: calendarHeight,
|
height: calendarHeight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -261,11 +271,11 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||||||
children: [
|
children: [
|
||||||
// Calendar header (weekdays)
|
// Calendar header (weekdays)
|
||||||
Row(
|
Row(
|
||||||
children: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
children: [
|
||||||
.map((day) => Expanded(
|
Expanded(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
day,
|
'Mon',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
@@ -273,8 +283,86 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
))
|
),
|
||||||
.toList(),
|
SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'Tue',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
fontSize: isWideScreen ? 14 : 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'Wed',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
fontSize: isWideScreen ? 14 : 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'Thu',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
fontSize: isWideScreen ? 14 : 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'Fri',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
fontSize: isWideScreen ? 14 : 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'Sat',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
fontSize: isWideScreen ? 14 : 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
'Sun',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
fontSize: isWideScreen ? 14 : 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
// Calendar grid
|
// Calendar grid
|
||||||
@@ -293,7 +381,7 @@ class _HistoryScreenState extends State<HistoryScreen> {
|
|||||||
final dayNumber = index - firstWeekday + 2;
|
final dayNumber = index - firstWeekday + 2;
|
||||||
|
|
||||||
if (dayNumber < 1 || dayNumber > daysInMonth) {
|
if (dayNumber < 1 || dayNumber > daysInMonth) {
|
||||||
return const SizedBox(); // Empty cell
|
return const SizedBox.shrink(); // Empty cell
|
||||||
}
|
}
|
||||||
|
|
||||||
final date = DateTime(_selectedYear, _selectedMonth, dayNumber);
|
final date = DateTime(_selectedYear, _selectedMonth, dayNumber);
|
||||||
|
@@ -67,8 +67,6 @@ class SettingsScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
RadioListTile<ThemeOption>(
|
RadioListTile<ThemeOption>(
|
||||||
title: const Text('Follow System'),
|
|
||||||
subtitle: const Text('Use system theme setting'),
|
|
||||||
value: ThemeOption.system,
|
value: ThemeOption.system,
|
||||||
groupValue: settingsProvider.themeOption,
|
groupValue: settingsProvider.themeOption,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -76,10 +74,10 @@ class SettingsScreen extends StatelessWidget {
|
|||||||
settingsProvider.setThemeOption(value);
|
settingsProvider.setThemeOption(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
title: const Text('Follow System'),
|
||||||
|
subtitle: const Text('Use system theme setting'),
|
||||||
),
|
),
|
||||||
RadioListTile<ThemeOption>(
|
RadioListTile<ThemeOption>(
|
||||||
title: const Text('Light Theme'),
|
|
||||||
subtitle: const Text('Always use light theme'),
|
|
||||||
value: ThemeOption.light,
|
value: ThemeOption.light,
|
||||||
groupValue: settingsProvider.themeOption,
|
groupValue: settingsProvider.themeOption,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -87,10 +85,10 @@ class SettingsScreen extends StatelessWidget {
|
|||||||
settingsProvider.setThemeOption(value);
|
settingsProvider.setThemeOption(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
title: const Text('Light Theme'),
|
||||||
|
subtitle: const Text('Always use light theme'),
|
||||||
),
|
),
|
||||||
RadioListTile<ThemeOption>(
|
RadioListTile<ThemeOption>(
|
||||||
title: const Text('Dark Theme'),
|
|
||||||
subtitle: const Text('Always use dark theme'),
|
|
||||||
value: ThemeOption.dark,
|
value: ThemeOption.dark,
|
||||||
groupValue: settingsProvider.themeOption,
|
groupValue: settingsProvider.themeOption,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
@@ -98,6 +96,8 @@ class SettingsScreen extends StatelessWidget {
|
|||||||
settingsProvider.setThemeOption(value);
|
settingsProvider.setThemeOption(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
title: const Text('Dark Theme'),
|
||||||
|
subtitle: const Text('Always use dark theme'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@@ -232,7 +232,7 @@ class AutoSyncService {
|
|||||||
if (_consecutiveFailures >= _autoDisableThreshold) {
|
if (_consecutiveFailures >= _autoDisableThreshold) {
|
||||||
_autoDisabledDueToErrors = true;
|
_autoDisabledDueToErrors = true;
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
printLog('AutoSyncService: Auto-sync disabled due to ${_consecutiveFailures} consecutive failures');
|
printLog('AutoSyncService: Auto-sync disabled due to $_consecutiveFailures consecutive failures');
|
||||||
}
|
}
|
||||||
|
|
||||||
// For configuration errors, disable immediately
|
// For configuration errors, disable immediately
|
||||||
|
@@ -93,6 +93,7 @@ class NotificationRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final context = _navigatorKey!.currentContext!;
|
final context = _navigatorKey!.currentContext!;
|
||||||
|
if (!context.mounted) return;
|
||||||
final provider = context.read<SupplementProvider>();
|
final provider = context.read<SupplementProvider>();
|
||||||
|
|
||||||
if (payload == null) {
|
if (payload == null) {
|
||||||
@@ -113,6 +114,7 @@ class NotificationRouter {
|
|||||||
if (s == null) {
|
if (s == null) {
|
||||||
// Attempt reload once
|
// Attempt reload once
|
||||||
await provider.loadSupplements();
|
await provider.loadSupplements();
|
||||||
|
if (!context.mounted) return;
|
||||||
try {
|
try {
|
||||||
s = provider.supplements.firstWhere((el) => el.id == id);
|
s = provider.supplements.firstWhere((el) => el.id == id);
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
@@ -124,6 +126,7 @@ class NotificationRouter {
|
|||||||
// Ensure we close any existing dialog first
|
// Ensure we close any existing dialog first
|
||||||
_popAnyDialog(context);
|
_popAnyDialog(context);
|
||||||
await showTakeSupplementDialog(context, s, hideTime: false);
|
await showTakeSupplementDialog(context, s, hideTime: false);
|
||||||
|
if (!context.mounted) return;
|
||||||
} else {
|
} else {
|
||||||
printLog('⚠️ Supplement id=$id not found for single-take routing');
|
printLog('⚠️ Supplement id=$id not found for single-take routing');
|
||||||
_showSnack(context, 'Supplement not found');
|
_showSnack(context, 'Supplement not found');
|
||||||
@@ -145,6 +148,7 @@ class NotificationRouter {
|
|||||||
|
|
||||||
_popAnyDialog(context);
|
_popAnyDialog(context);
|
||||||
await showBulkTakeDialog(context, list);
|
await showBulkTakeDialog(context, list);
|
||||||
|
if (!context.mounted) return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
printLog('⚠️ Unknown payload type: $type');
|
printLog('⚠️ Unknown payload type: $type');
|
||||||
@@ -158,8 +162,9 @@ class NotificationRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try to wait for providers to be ready to build rich content.
|
// Try to wait for providers to be ready to build rich content.
|
||||||
final ready = await _waitUntilReady(timeout: const Duration(seconds: 5));
|
|
||||||
BuildContext? ctx = _navigatorKey?.currentContext;
|
BuildContext? ctx = _navigatorKey?.currentContext;
|
||||||
|
final ready = await _waitUntilReady(timeout: const Duration(seconds: 5));
|
||||||
|
if (ctx != null && !ctx.mounted) ctx = null;
|
||||||
|
|
||||||
SupplementProvider? provider;
|
SupplementProvider? provider;
|
||||||
if (ready && ctx != null) {
|
if (ready && ctx != null) {
|
||||||
@@ -262,6 +267,7 @@ class NotificationRouter {
|
|||||||
final key = _navigatorKey;
|
final key = _navigatorKey;
|
||||||
final ctx = key?.currentContext;
|
final ctx = key?.currentContext;
|
||||||
if (ctx != null) {
|
if (ctx != null) {
|
||||||
|
if (!ctx.mounted) continue;
|
||||||
try {
|
try {
|
||||||
final provider = Provider.of<SupplementProvider>(ctx, listen: false);
|
final provider = Provider.of<SupplementProvider>(ctx, listen: false);
|
||||||
if (!provider.isLoading) {
|
if (!provider.isLoading) {
|
||||||
@@ -283,8 +289,10 @@ class NotificationRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showSnack(BuildContext context, String message) {
|
void _showSnack(BuildContext context, String message) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(message)),
|
SnackBar(content: Text(message)),
|
||||||
);
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -51,7 +51,7 @@ Future<void> showBulkTakeDialog(
|
|||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withValues(alpha: 0.5),
|
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.3),
|
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.3),
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||||
|
|
||||||
import '../../models/supplement.dart';
|
import '../../models/supplement.dart';
|
||||||
import '../../providers/supplement_provider.dart';
|
import '../../providers/supplement_provider.dart';
|
||||||
@@ -16,38 +17,32 @@ Future<void> showTakeSupplementDialog(
|
|||||||
DateTime selectedDateTime = DateTime.now();
|
DateTime selectedDateTime = DateTime.now();
|
||||||
bool useCustomTime = false;
|
bool useCustomTime = false;
|
||||||
|
|
||||||
await showDialog(
|
await showShadDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => StatefulBuilder(
|
builder: (context) => StatefulBuilder(
|
||||||
builder: (context, setState) {
|
builder: (context, setState) {
|
||||||
return AlertDialog(
|
return ShadDialog(
|
||||||
title: Text('Take ${supplement.name}'),
|
title: Text('Take ${supplement.name}'),
|
||||||
content: Column(
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: ShadInput(
|
||||||
controller: unitsController,
|
controller: unitsController,
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
decoration: InputDecoration(
|
placeholder: Text('Number of ${supplement.unitType}'),
|
||||||
labelText: 'Number of ${supplement.unitType}',
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
suffixText: supplement.unitType,
|
|
||||||
),
|
|
||||||
onChanged: (value) => setState(() {}),
|
onChanged: (value) => setState(() {}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Container(
|
ShadCard(
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
@@ -55,7 +50,7 @@ Future<void> showTakeSupplementDialog(
|
|||||||
'Total dosage:',
|
'Total dosage:',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
@@ -63,25 +58,18 @@ Future<void> showTakeSupplementDialog(
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).colorScheme.onSurface,
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
if (!hideTime) ...[
|
if (!hideTime) ...[
|
||||||
// Time selection section
|
ShadCard(
|
||||||
Container(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.primaryContainer.withValues(alpha: 0.3),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -90,7 +78,7 @@ Future<void> showTakeSupplementDialog(
|
|||||||
Icon(
|
Icon(
|
||||||
Icons.access_time,
|
Icons.access_time,
|
||||||
size: 16,
|
size: 16,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: ShadTheme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
@@ -98,57 +86,39 @@ Future<void> showTakeSupplementDialog(
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: ShadTheme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
ShadRadioGroupFormField<bool>(
|
||||||
children: [
|
initialValue: useCustomTime,
|
||||||
Expanded(
|
onChanged: (value) => setState(() => useCustomTime = value!),
|
||||||
child: RadioListTile<bool>(
|
items: [
|
||||||
dense: true,
|
ShadRadio(
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
title: const Text('Just now', style: TextStyle(fontSize: 12)),
|
|
||||||
value: false,
|
value: false,
|
||||||
groupValue: useCustomTime,
|
label: const Text('Just now', style: TextStyle(fontSize: 12)),
|
||||||
onChanged: (value) => setState(() => useCustomTime = value!),
|
|
||||||
),
|
),
|
||||||
),
|
ShadRadio(
|
||||||
Expanded(
|
|
||||||
child: RadioListTile<bool>(
|
|
||||||
dense: true,
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
title: const Text('Custom time', style: TextStyle(fontSize: 12)),
|
|
||||||
value: true,
|
value: true,
|
||||||
groupValue: useCustomTime,
|
label: const Text('Custom time', style: TextStyle(fontSize: 12)),
|
||||||
onChanged: (value) => setState(() => useCustomTime = value!),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (useCustomTime) ...[
|
if (useCustomTime) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Container(
|
ShadCard(
|
||||||
width: double.infinity,
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).colorScheme.outline.withValues(alpha: 0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Date picker
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.calendar_today,
|
Icons.calendar_today,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -157,7 +127,7 @@ Future<void> showTakeSupplementDialog(
|
|||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
ShadButton.outline(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final date = await showDatePicker(
|
final date = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -182,13 +152,12 @@ Future<void> showTakeSupplementDialog(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
// Time picker
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.access_time,
|
Icons.access_time,
|
||||||
size: 14,
|
size: 14,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -197,7 +166,7 @@ Future<void> showTakeSupplementDialog(
|
|||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
ShadButton.outline(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final time = await showTimePicker(
|
final time = await showTimePicker(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -222,54 +191,53 @@ Future<void> showTakeSupplementDialog(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
],
|
],
|
||||||
|
ShadInput(
|
||||||
TextField(
|
|
||||||
controller: notesController,
|
controller: notesController,
|
||||||
decoration: const InputDecoration(
|
placeholder: const Text('Notes (optional)'),
|
||||||
labelText: 'Notes (optional)',
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(height: 16),
|
||||||
),
|
Row(
|
||||||
actions: [
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
TextButton(
|
children: [
|
||||||
|
ShadButton.outline(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: const Text('Cancel'),
|
child: const Text('Cancel'),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
const SizedBox(width: 8),
|
||||||
|
ShadButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final unitsTaken = double.tryParse(unitsController.text) ?? supplement.numberOfUnits.toDouble();
|
final unitsTaken = double.tryParse(unitsController.text) ?? supplement.numberOfUnits.toDouble();
|
||||||
// For now, we'll record 0 as total dosage since we're transitioning to ingredients
|
|
||||||
// This will be properly implemented when we add the full ingredient tracking
|
|
||||||
final totalDosageTaken = 0.0;
|
final totalDosageTaken = 0.0;
|
||||||
context.read<SupplementProvider>().recordIntake(
|
context.read<SupplementProvider>().recordIntake(
|
||||||
supplement.id!,
|
supplement.id!,
|
||||||
totalDosageTaken,
|
totalDosageTaken,
|
||||||
unitsTaken: unitsTaken,
|
unitsTaken: unitsTaken,
|
||||||
notes: notesController.text.isNotEmpty ? notesController.text : null,
|
notes: notesController.text.isNotEmpty ? notesController.text : null,
|
||||||
takenAt: hideTime
|
takenAt: hideTime ? null : (useCustomTime ? selectedDateTime : null),
|
||||||
? null
|
|
||||||
: (useCustomTime ? selectedDateTime : null),
|
|
||||||
);
|
);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ShadToaster.of(context).show(
|
||||||
SnackBar(
|
ShadToast(
|
||||||
content: Text('${supplement.name} recorded!'),
|
title: Text('${supplement.name} recorded!'),
|
||||||
backgroundColor: Colors.green,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: const Text('Record'),
|
child: const Text('Record'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@@ -7,6 +7,7 @@ class InfoChip extends StatelessWidget {
|
|||||||
final bool fullWidth;
|
final bool fullWidth;
|
||||||
|
|
||||||
const InfoChip({
|
const InfoChip({
|
||||||
|
super.key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.context,
|
required this.context,
|
||||||
@@ -19,7 +20,7 @@ class InfoChip extends StatelessWidget {
|
|||||||
width: fullWidth ? double.infinity : null,
|
width: fullWidth ? double.infinity : null,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withValues(alpha: 0.4),
|
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.4),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||||
import '../models/supplement.dart';
|
import '../models/supplement.dart';
|
||||||
import '../providers/supplement_provider.dart';
|
import '../providers/supplement_provider.dart';
|
||||||
|
|
||||||
@@ -176,24 +177,6 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
|
||||||
onPressed: widget.onTake,
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: isCompletelyTaken
|
|
||||||
? Colors.green.shade500
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.inverseSurface,
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
minimumSize: const Size(60, 32),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
isCompletelyTaken ? '✓' : 'Take',
|
|
||||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -223,15 +206,14 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
if (isTakenToday)
|
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 'take',
|
value: 'take',
|
||||||
onTap: widget.onTake,
|
onTap: widget.onTake,
|
||||||
child: const Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.add_circle_outline),
|
Icon(isTakenToday ? Icons.add_circle_outline : Icons.medication),
|
||||||
SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text('Take Again'),
|
Text(isTakenToday ? 'Take Again' : 'Take'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -325,7 +307,7 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
children: todayIntakes.map((intake) {
|
children: todayIntakes.map((intake) {
|
||||||
final units = intake['units'] as double;
|
final units = intake['units'] as double;
|
||||||
final unitsText = units == 1.0
|
final unitsText = units == 1.0
|
||||||
? '${widget.supplement.unitType}'
|
? widget.supplement.unitType
|
||||||
: '${units.toStringAsFixed(units % 1 == 0 ? 0 : 1)} ${widget.supplement.unitType}';
|
: '${units.toStringAsFixed(units % 1 == 0 ? 0 : 1)} ${widget.supplement.unitType}';
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
@@ -356,12 +338,9 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
],
|
],
|
||||||
|
|
||||||
// Ingredients section
|
// Ingredients section
|
||||||
Container(
|
ShadCard(
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withValues(alpha: 0.3),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -370,7 +349,7 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@@ -378,21 +357,12 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
children: widget.supplement.ingredients.map((ingredient) {
|
children: widget.supplement.ingredients.map((ingredient) {
|
||||||
return Container(
|
return ShadBadge(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
border: Border.all(
|
|
||||||
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
'${ingredient.name} ${ingredient.amount}${ingredient.unit}',
|
'${ingredient.name} ${ingredient.amount}${ingredient.unit}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -401,6 +371,7 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
|
|
||||||
@@ -408,18 +379,34 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _InfoChip(
|
child: ShadBadge(
|
||||||
icon: Icons.schedule,
|
child: Row(
|
||||||
label: '${widget.supplement.frequencyPerDay}x daily',
|
children: [
|
||||||
context: context,
|
Icon(
|
||||||
|
Icons.schedule,
|
||||||
|
size: 14,
|
||||||
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text('${widget.supplement.frequencyPerDay}x daily'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _InfoChip(
|
child: ShadBadge(
|
||||||
icon: Icons.medication,
|
child: Row(
|
||||||
label: '${widget.supplement.numberOfUnits} ${widget.supplement.unitType}',
|
children: [
|
||||||
context: context,
|
Icon(
|
||||||
|
Icons.medication,
|
||||||
|
size: 14,
|
||||||
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Text('${widget.supplement.numberOfUnits} ${widget.supplement.unitType}'),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -427,66 +414,43 @@ class _SupplementCardState extends State<SupplementCard> {
|
|||||||
|
|
||||||
if (widget.supplement.reminderTimes.isNotEmpty) ...[
|
if (widget.supplement.reminderTimes.isNotEmpty) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_InfoChip(
|
ShadBadge(
|
||||||
icon: Icons.notifications,
|
child: Row(
|
||||||
label: 'Reminders: ${widget.supplement.reminderTimes.join(', ')}',
|
children: [
|
||||||
context: context,
|
Icon(
|
||||||
fullWidth: true,
|
Icons.notifications,
|
||||||
|
size: 14,
|
||||||
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Reminders: ${widget.supplement.reminderTimes.join(', ')}',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
if (widget.supplement.notes != null && widget.supplement.notes!.isNotEmpty) ...[
|
if (widget.supplement.notes != null && widget.supplement.notes!.isNotEmpty) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Container(
|
ShadCard(
|
||||||
width: double.infinity,
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withValues(alpha: 0.2),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.supplement.notes!,
|
widget.supplement.notes!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: ShadTheme.of(context).colorScheme.foreground,
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Take button
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
child: ElevatedButton.icon(
|
|
||||||
onPressed: widget.onTake,
|
|
||||||
icon: Icon(
|
|
||||||
isCompletelyTaken ? Icons.check_circle : Icons.medication,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
label: Text(
|
|
||||||
isCompletelyTaken
|
|
||||||
? 'All doses taken today'
|
|
||||||
: isTakenToday
|
|
||||||
? 'Take next dose'
|
|
||||||
: 'Take supplement',
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: isCompletelyTaken
|
|
||||||
? Colors.green.shade500
|
|
||||||
: Theme.of(context).colorScheme.primary,
|
|
||||||
foregroundColor: Colors.white,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
elevation: isCompletelyTaken ? 0 : 2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -516,7 +480,7 @@ class _InfoChip extends StatelessWidget {
|
|||||||
width: fullWidth ? double.infinity : null,
|
width: fullWidth ? double.infinity : null,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant.withValues(alpha: 0.4),
|
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.4),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
135
pubspec.lock
135
pubspec.lock
@@ -33,6 +33,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
boxy:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boxy
|
||||||
|
sha256: "71af0cd1bf7889c09787f26219a345aa4f38ccb98384c8ec24189e4d8e746005"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -129,6 +137,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.7"
|
||||||
|
extended_image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: extended_image
|
||||||
|
sha256: f6cbb1d798f51262ed1a3d93b4f1f2aa0d76128df39af18ecb77fa740f88b2e0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.1"
|
||||||
|
extended_image_library:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: extended_image_library
|
||||||
|
sha256: "1f9a24d3a00c2633891c6a7b5cab2807999eb2d5b597e5133b63f49d113811fe"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.1"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -166,6 +190,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_animate:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_animate
|
||||||
|
sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.5.2"
|
||||||
flutter_fgbg:
|
flutter_fgbg:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -214,6 +246,11 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.2"
|
version: "1.0.2"
|
||||||
|
flutter_localizations:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -263,6 +300,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "4.0.0"
|
||||||
|
flutter_shaders:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_shaders
|
||||||
|
sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3"
|
||||||
|
flutter_svg:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_svg
|
||||||
|
sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -281,6 +334,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.5.0"
|
||||||
|
http_client_helper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_client_helper
|
||||||
|
sha256: "8a9127650734da86b5c73760de2b404494c968a3fd55602045ffec789dac3cb1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -297,6 +358,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.2"
|
version: "0.20.2"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.2"
|
||||||
json_annotation:
|
json_annotation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -345,6 +414,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
lucide_icons_flutter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lucide_icons_flutter
|
||||||
|
sha256: c88e3611c0aa272ca2f2aa263662174ae4996f5e3ee1c300021514df230b6588
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.9"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -401,6 +478,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_parsing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_parsing
|
||||||
|
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -489,6 +574,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.28.0"
|
version: "0.28.0"
|
||||||
|
shadcn_ui:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shadcn_ui
|
||||||
|
sha256: "628d1a7f36e4c764dae3b86b38abb086adf39ccea0073960f481777d1880f90d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.29.2"
|
||||||
shared_preferences:
|
shared_preferences:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -678,6 +771,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.10.1"
|
version: "0.10.1"
|
||||||
|
two_dimensional_scrollables:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: two_dimensional_scrollables
|
||||||
|
sha256: "0f77ecb96596f2f82eec2b0a8e60d9305c58315557da9fa3b610c7dbf5ded621"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.7"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -686,6 +787,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
universal_image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_image
|
||||||
|
sha256: ef47a4a002158cf0b36ed3b7605af132d2476cc42703e41b8067d3603705c40d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.11"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -758,6 +867,30 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.1"
|
version: "4.5.1"
|
||||||
|
vector_graphics:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics
|
||||||
|
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.19"
|
||||||
|
vector_graphics_codec:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics_codec
|
||||||
|
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.13"
|
||||||
|
vector_graphics_compiler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics_compiler
|
||||||
|
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.19"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -816,4 +949,4 @@ packages:
|
|||||||
version: "6.6.1"
|
version: "6.6.1"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.9.0 <4.0.0"
|
dart: ">=3.9.0 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.32.0"
|
||||||
|
@@ -29,6 +29,9 @@ dependencies:
|
|||||||
# Date time handling
|
# Date time handling
|
||||||
intl: ^0.20.2
|
intl: ^0.20.2
|
||||||
|
|
||||||
|
# UI components
|
||||||
|
shadcn_ui: ^0.29.2
|
||||||
|
|
||||||
# WebDAV sync functionality
|
# WebDAV sync functionality
|
||||||
webdav_client: ^1.2.2
|
webdav_client: ^1.2.2
|
||||||
connectivity_plus: ^6.1.5
|
connectivity_plus: ^6.1.5
|
||||||
|
Reference in New Issue
Block a user