From f63c3141b475a0bdffd210f58f9f4a3142ec4bf8 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Wed, 11 Feb 2026 22:02:15 +0530 Subject: [PATCH 01/15] Add dependencies --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + app/pubspec.lock | 130 +++++++++++++++++- app/pubspec.yaml | 3 + app/windows/flutter/generated_plugins.cmake | 1 + 4 files changed, 135 insertions(+), 1 deletion(-) diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index 0c2a31a..fc70c6a 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,9 +6,11 @@ import FlutterMacOS import Foundation import dynamic_color +import flutter_local_notifications import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/app/pubspec.lock b/app/pubspec.lock index 3e85987..d0ce277 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + android_alarm_manager_plus: + dependency: "direct main" + description: + name: android_alarm_manager_plus + sha256: "591b97ceaaa8db69776a32bc1ea2fed8f83c3dadfa7b7e95351abfc0894361e7" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -49,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.dev" + source: hosted + version: "0.7.12" dynamic_color: dependency: "direct main" description: @@ -65,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter: dependency: "direct main" description: flutter @@ -78,11 +110,59 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "76cd20bcfa72fabe50ea27eeaf165527f446f55d3033021462084b87805b4cac" + url: "https://pub.dev" + source: hosted + version: "20.0.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: dce0116868cedd2cdf768af0365fc37ff1cbef7c02c4f51d0587482e625868d0 + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "23de31678a48c084169d7ae95866df9de5c9d2a44be3e5915a2ff067aeeba899" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: e97a1a3016512437d9c0b12fae7d1491c3c7b9aa7f03a69b974308840656b02a + url: "https://pub.dev" + source: hosted + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" leak_tracker: dependency: transitive description: @@ -147,6 +227,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" platform: dependency: transitive description: @@ -264,6 +352,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.7" + timezone: + dependency: "direct main" + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" vector_math: dependency: transitive description: @@ -280,6 +384,30 @@ packages: url: "https://pub.dev" source: hosted version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" sdks: dart: ">=3.10.1 <4.0.0" - flutter: ">=3.24.0" + flutter: ">=3.32.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 29055bf..595c80d 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -37,6 +37,9 @@ dependencies: sqflite: ^2.4.2 path: ^1.9.1 dynamic_color: ^1.8.1 + flutter_local_notifications: ^20.0.0 + android_alarm_manager_plus: ^5.0.0 + timezone: ^0.10.1 dev_dependencies: flutter_test: diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake index 841e8c4..7df96d4 100644 --- a/app/windows/flutter/generated_plugins.cmake +++ b/app/windows/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + flutter_local_notifications_windows ) set(PLUGIN_BUNDLED_LIBRARIES) From 7e770208197e6bf7fbc76e4eea031ce18fd738af Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Wed, 11 Feb 2026 22:08:27 +0530 Subject: [PATCH 02/15] Update permissions in manifest --- app/android/app/src/main/AndroidManifest.xml | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index 6ee3df0..661de68 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -1,21 +1,24 @@ + + + + + + + + - - + - - - - - - - - - + + + + + + + + + \ No newline at end of file From a8fc251763948cd5d36720dcd93ad1c84f443a76 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 17 Feb 2026 09:07:28 +0530 Subject: [PATCH 03/15] Update notification service --- app/android/app/build.gradle.kts | 5 +++++ app/lib/main.dart | 24 ++++++++++++++++++++---- app/pubspec.lock | 8 -------- app/pubspec.yaml | 1 - 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/android/app/build.gradle.kts b/app/android/app/build.gradle.kts index e272c33..b661ee0 100644 --- a/app/android/app/build.gradle.kts +++ b/app/android/app/build.gradle.kts @@ -11,6 +11,7 @@ android { ndkVersion = flutter.ndkVersion compileOptions { + isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } @@ -42,3 +43,7 @@ android { flutter { source = "../.." } + +dependencies { + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") +} \ No newline at end of file diff --git a/app/lib/main.dart b/app/lib/main.dart index 0bd93c7..4f3c3ef 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -1,14 +1,31 @@ import 'package:flutter/material.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:app/dose.dart'; +import 'package:app/services/notification_service.dart'; + +void main() async { + // Ensure widgets are bound before async calls + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize Notifications + await NotificationHelper().init(); -void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); + static final _defaultLightColorScheme = ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.light, + ); + + static final _defaultDarkColorScheme = ColorScheme.fromSeed( + seedColor: Colors.deepPurple, + brightness: Brightness.dark, + ); + @override Widget build(BuildContext context) { return DynamicColorBuilder( @@ -16,15 +33,14 @@ class MyApp extends StatelessWidget { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData( - colorScheme: lightDynamic, + colorScheme: lightDynamic ?? _defaultLightColorScheme, useMaterial3: true, ), darkTheme: ThemeData( - colorScheme: darkDynamic, + colorScheme: darkDynamic ?? _defaultDarkColorScheme, useMaterial3: true, ), themeMode: ThemeMode.system, - home: const Dose(), ); }, diff --git a/app/pubspec.lock b/app/pubspec.lock index d0ce277..38c8dad 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1,14 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - android_alarm_manager_plus: - dependency: "direct main" - description: - name: android_alarm_manager_plus - sha256: "591b97ceaaa8db69776a32bc1ea2fed8f83c3dadfa7b7e95351abfc0894361e7" - url: "https://pub.dev" - source: hosted - version: "5.0.0" args: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 595c80d..260eba1 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -38,7 +38,6 @@ dependencies: path: ^1.9.1 dynamic_color: ^1.8.1 flutter_local_notifications: ^20.0.0 - android_alarm_manager_plus: ^5.0.0 timezone: ^0.10.1 dev_dependencies: From 5c8a20c5e8002421127707cda1815e7de03969fb Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 17 Feb 2026 09:07:41 +0530 Subject: [PATCH 04/15] Update notification service --- .vscode/settings.json | 3 + app/lib/services/notification_service.dart | 97 ++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 app/lib/services/notification_service.dart diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart new file mode 100644 index 0000000..6a25ff0 --- /dev/null +++ b/app/lib/services/notification_service.dart @@ -0,0 +1,97 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest.dart' as tz; + +class NotificationHelper { + static final NotificationHelper _instance = NotificationHelper._internal(); + factory NotificationHelper() => _instance; + NotificationHelper._internal(); + + final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + + Future init() async { + tz.initializeTimeZones(); + + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); + + const InitializationSettings initializationSettings = + InitializationSettings(android: initializationSettingsAndroid); + + await flutterLocalNotificationsPlugin.initialize( + settings: initializationSettings, + onDidReceiveNotificationResponse: (NotificationResponse response) { + if (response.actionId == 'mark_done') { + print("Mark Done clicked for payload: ${response.payload}"); + } + }, + ); + + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestNotificationsPermission(); + } + + Future scheduleDailyNotification({ + required int id, + required String medicineName, + required String timeString, + }) async { + final parts = timeString.split(':'); + final int hour = int.parse(parts[0]); + final int minute = int.parse(parts[1]); + + const AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails( + 'medicine_channel_id', + 'Medicine Reminders', + channelDescription: 'Notifications for medicine intake', + importance: Importance.max, + priority: Priority.high, + fullScreenIntent: true, + ticker: 'Time to take your medicine', + actions: [ + AndroidNotificationAction( + 'mark_done', + 'Done', + showsUserInterface: true, + cancelNotification: true, + ), + ], + ); + + const NotificationDetails platformChannelSpecifics = + NotificationDetails(android: androidPlatformChannelSpecifics); + + final tz.TZDateTime nextInstance = _nextInstanceOfTime(hour, minute); + + await flutterLocalNotificationsPlugin.zonedSchedule( + id: id, + title: 'Time for taking $medicineName', + body: 'It is $timeString. Have you taken your dose?', + scheduledDate: nextInstance, + notificationDetails: platformChannelSpecifics, + androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, + matchDateTimeComponents: DateTimeComponents.time, + ); + + print("Scheduled notification for $medicineName at $nextInstance (ID: $id)"); + } + + Future cancelNotification(int id) async { + await flutterLocalNotificationsPlugin.cancel(id: id); + } + + tz.TZDateTime _nextInstanceOfTime(int hour, int minute) { + final tz.TZDateTime now = tz.TZDateTime.now(tz.local); + tz.TZDateTime scheduledDate = + tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minute); + + if (scheduledDate.isBefore(now)) { + scheduledDate = scheduledDate.add(const Duration(days: 1)); + } + return scheduledDate; + } +} \ No newline at end of file From 15804742efc60d4c3c13df378e6b314d27534784 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 3 Mar 2026 08:55:15 +0530 Subject: [PATCH 05/15] Create test notif button --- app/android/app/src/main/AndroidManifest.xml | 1 + app/lib/home/addMenu.dart | 5 +- app/lib/home/home.dart | 210 +++++++++--------- app/lib/main.dart | 6 +- app/lib/services/notification_service.dart | 98 ++------ .../flutter/generated_plugin_registrant.cc | 4 + app/linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + app/pubspec.lock | 21 ++ app/pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + app/windows/flutter/generated_plugins.cmake | 1 + 12 files changed, 167 insertions(+), 186 deletions(-) diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index 661de68..c926621 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -41,5 +41,6 @@ + \ No newline at end of file diff --git a/app/lib/home/addMenu.dart b/app/lib/home/addMenu.dart index b110793..310e4cc 100644 --- a/app/lib/home/addMenu.dart +++ b/app/lib/home/addMenu.dart @@ -1,5 +1,5 @@ -import 'package:app/models/cabinet.dart'; import 'package:flutter/material.dart'; +import 'package:app/models/cabinet.dart'; import 'package:app/db/cabinetdb.dart'; class AddMedicineMenu extends StatefulWidget { @@ -49,7 +49,8 @@ class _AddMedicineMenuState extends State { priority: _priority, ); - await DatabaseHelper.instance.create(medicine); + int newId = await DatabaseHelper.instance.create(medicine); + widget.onSave(); if (mounted) { diff --git a/app/lib/home/home.dart b/app/lib/home/home.dart index 38d76a0..d430c44 100644 --- a/app/lib/home/home.dart +++ b/app/lib/home/home.dart @@ -1,6 +1,7 @@ import 'package:app/models/cabinet.dart'; import 'package:flutter/material.dart'; import 'package:app/db/cabinetdb.dart'; +import 'package:app/services/notification_service.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -54,121 +55,132 @@ class _HomePageState extends State { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - return FutureBuilder>( - future: _medicinesFuture, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - "Error loading data: ${snapshot.error}", - textAlign: TextAlign.center, - style: TextStyle(color: colorScheme.error), - ), - ), - ); - } - - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: ElevatedButton( + onPressed: () async { + await NotificationHelper().showTestNotification(); + }, + child: const Text('Trigger Test Notification'), + ), + ), + Expanded( + child: FutureBuilder>( + future: _medicinesFuture, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + "Error loading data: ${snapshot.error}", + textAlign: TextAlign.center, + style: TextStyle(color: colorScheme.error), + ), + ), + ); + } - final medicines = snapshot.data ?? []; + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } - if (medicines.isEmpty) { - return Center( - child: Text( - "No reminders added yet.", - style: TextStyle(color: colorScheme.onSurfaceVariant), - ), - ); - } + final medicines = snapshot.data ?? []; - return ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: medicines.length, - itemBuilder: (context, index) { - final med = medicines[index]; - return GestureDetector( - onLongPressStart: (details) { - _deleteMedicine(med.id!, details.globalPosition); - }, - child: Card( - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide(color: colorScheme.outlineVariant), - ), - color: colorScheme.surfaceContainer, - margin: const EdgeInsets.only(bottom: 12), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - // Icon - Container( - width: 48, - height: 48, - decoration: BoxDecoration( - color: colorScheme.tertiaryContainer, - shape: BoxShape.circle, - ), - child: Icon(Icons.medication, color: colorScheme.onTertiaryContainer), + if (medicines.isEmpty) { + return Center( + child: Text( + "No reminders added yet.", + style: TextStyle(color: colorScheme.onSurfaceVariant), + ), + ); + } + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: medicines.length, + itemBuilder: (context, index) { + final med = medicines[index]; + return GestureDetector( + onLongPressStart: (details) { + _deleteMedicine(med.id!, details.globalPosition); + }, + child: Card( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: colorScheme.outlineVariant), ), - const SizedBox(width: 16), - // Med name - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + color: colorScheme.surfaceContainer, + margin: const EdgeInsets.only(bottom: 12), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: colorScheme.tertiaryContainer, + shape: BoxShape.circle, + ), + child: Icon(Icons.medication, color: colorScheme.onTertiaryContainer), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + med.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + const SizedBox(height: 4), + Text( + med.dosage, + style: TextStyle( + fontSize: 14, + color: colorScheme.onSurfaceVariant, + ), + ), + ], + ), + ), Text( - med.name, + med.time, style: TextStyle( fontSize: 16, - fontWeight: FontWeight.bold, + fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), - const SizedBox(height: 4), - Text( - med.dosage, - style: TextStyle( - fontSize: 14, - color: colorScheme.onSurfaceVariant, + const SizedBox(width: 16), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(12), ), + child: Icon(Icons.check, color: colorScheme.onPrimaryContainer), ), ], ), ), - // Time - Text( - med.time, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - ), - ), - const SizedBox(width: 16), - // Checkbox - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: colorScheme.primaryContainer, - borderRadius: BorderRadius.circular(12), - ), - child: Icon(Icons.check, color: colorScheme.onPrimaryContainer), - ), - ], - ), - ), - ), - ); - }, - ); - }, + ), + ); + }, + ); + }, + ), + ), + ], ); } } \ No newline at end of file diff --git a/app/lib/main.dart b/app/lib/main.dart index 4f3c3ef..9bcf67a 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -4,13 +4,9 @@ import 'package:app/dose.dart'; import 'package:app/services/notification_service.dart'; void main() async { - // Ensure widgets are bound before async calls WidgetsFlutterBinding.ensureInitialized(); - - // Initialize Notifications await NotificationHelper().init(); - - runApp(const MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart index 6a25ff0..7be4af3 100644 --- a/app/lib/services/notification_service.dart +++ b/app/lib/services/notification_service.dart @@ -1,97 +1,35 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:timezone/timezone.dart' as tz; -import 'package:timezone/data/latest.dart' as tz; class NotificationHelper { static final NotificationHelper _instance = NotificationHelper._internal(); factory NotificationHelper() => _instance; NotificationHelper._internal(); - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = - FlutterLocalNotificationsPlugin(); + final FlutterLocalNotificationsPlugin plugin = FlutterLocalNotificationsPlugin(); Future init() async { - tz.initializeTimeZones(); - - const AndroidInitializationSettings initializationSettingsAndroid = - AndroidInitializationSettings('@mipmap/ic_launcher'); - - const InitializationSettings initializationSettings = - InitializationSettings(android: initializationSettingsAndroid); - - await flutterLocalNotificationsPlugin.initialize( - settings: initializationSettings, - onDidReceiveNotificationResponse: (NotificationResponse response) { - if (response.actionId == 'mark_done') { - print("Mark Done clicked for payload: ${response.payload}"); - } - }, - ); - - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.requestNotificationsPermission(); + const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); + const InitializationSettings settings = InitializationSettings(android: androidSettings); + + await plugin.initialize(settings: settings); + + await plugin.resolvePlatformSpecificImplementation()?.requestNotificationsPermission(); } - Future scheduleDailyNotification({ - required int id, - required String medicineName, - required String timeString, - }) async { - final parts = timeString.split(':'); - final int hour = int.parse(parts[0]); - final int minute = int.parse(parts[1]); - - const AndroidNotificationDetails androidPlatformChannelSpecifics = - AndroidNotificationDetails( - 'medicine_channel_id', - 'Medicine Reminders', - channelDescription: 'Notifications for medicine intake', + Future showTestNotification() async { + const AndroidNotificationDetails androidDetails = AndroidNotificationDetails( + 'test_channel', + 'Test Channel', importance: Importance.max, priority: Priority.high, - fullScreenIntent: true, - ticker: 'Time to take your medicine', - actions: [ - AndroidNotificationAction( - 'mark_done', - 'Done', - showsUserInterface: true, - cancelNotification: true, - ), - ], ); - - const NotificationDetails platformChannelSpecifics = - NotificationDetails(android: androidPlatformChannelSpecifics); - - final tz.TZDateTime nextInstance = _nextInstanceOfTime(hour, minute); - - await flutterLocalNotificationsPlugin.zonedSchedule( - id: id, - title: 'Time for taking $medicineName', - body: 'It is $timeString. Have you taken your dose?', - scheduledDate: nextInstance, - notificationDetails: platformChannelSpecifics, - androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, - matchDateTimeComponents: DateTimeComponents.time, + const NotificationDetails details = NotificationDetails(android: androidDetails); + + await plugin.show( + id: 0, + title: 'Test Title', + body: 'Test Notification Works', + notificationDetails: details, ); - - print("Scheduled notification for $medicineName at $nextInstance (ID: $id)"); - } - - Future cancelNotification(int id) async { - await flutterLocalNotificationsPlugin.cancel(id: id); - } - - tz.TZDateTime _nextInstanceOfTime(int hour, int minute) { - final tz.TZDateTime now = tz.TZDateTime.now(tz.local); - tz.TZDateTime scheduledDate = - tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minute); - - if (scheduledDate.isBefore(now)) { - scheduledDate = scheduledDate.add(const Duration(days: 1)); - } - return scheduledDate; } } \ No newline at end of file diff --git a/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc index 675b719..8a93927 100644 --- a/app/linux/flutter/generated_plugin_registrant.cc +++ b/app/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_color_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); + g_autoptr(FlPluginRegistrar) flutter_timezone_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterTimezonePlugin"); + flutter_timezone_plugin_register_with_registrar(flutter_timezone_registrar); } diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake index 3e303c1..35c8ab1 100644 --- a/app/linux/flutter/generated_plugins.cmake +++ b/app/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color + flutter_timezone ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index fc70c6a..ce40423 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,10 +7,12 @@ import Foundation import dynamic_color import flutter_local_notifications +import flutter_timezone import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/app/pubspec.lock b/app/pubspec.lock index 38c8dad..f2a4a9e 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -73,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.8.1" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" fake_async: dependency: transitive description: @@ -139,6 +147,19 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_timezone: + dependency: "direct main" + description: + name: flutter_timezone + sha256: "978192f2f9ea6d019a4de4f0211d76a9af955ca24865828fa98ca4e20cf0cb3c" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" http: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 260eba1..ef0be0a 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: dynamic_color: ^1.8.1 flutter_local_notifications: ^20.0.0 timezone: ^0.10.1 + flutter_timezone: ^5.0.1 dev_dependencies: flutter_test: diff --git a/app/windows/flutter/generated_plugin_registrant.cc b/app/windows/flutter/generated_plugin_registrant.cc index e4899a6..1c0de6b 100644 --- a/app/windows/flutter/generated_plugin_registrant.cc +++ b/app/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); + FlutterTimezonePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi")); } diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake index 7df96d4..be08263 100644 --- a/app/windows/flutter/generated_plugins.cmake +++ b/app/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color + flutter_timezone ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From cd4624f31b4158a26f926a05ce5e37110cbf8ffa Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 3 Mar 2026 08:58:08 +0530 Subject: [PATCH 06/15] Update add_menu name --- app/lib/home/{addMenu.dart => add_menu.dart} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/lib/home/{addMenu.dart => add_menu.dart} (100%) diff --git a/app/lib/home/addMenu.dart b/app/lib/home/add_menu.dart similarity index 100% rename from app/lib/home/addMenu.dart rename to app/lib/home/add_menu.dart From 12a4e044c24ac05d9c4693745305903ad96207c3 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 3 Mar 2026 08:59:22 +0530 Subject: [PATCH 07/15] Update add_menu name --- app/lib/home/add_menu.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/lib/home/add_menu.dart b/app/lib/home/add_menu.dart index 310e4cc..89f4749 100644 --- a/app/lib/home/add_menu.dart +++ b/app/lib/home/add_menu.dart @@ -130,7 +130,7 @@ class _AddMedicineMenuState extends State { const SizedBox(width: 12), Expanded( child: DropdownButtonFormField( - value: _cycle, + initialValue: _cycle, decoration: const InputDecoration( labelText: "Cycle", border: OutlineInputBorder(), @@ -178,7 +178,7 @@ class _AddMedicineMenuState extends State { const SizedBox(height: 16), DropdownButtonFormField( - value: _priority, + initialValue: _priority, decoration: const InputDecoration( labelText: "Priority", border: OutlineInputBorder(), From d846b8f4281d826091ddc0f8f311f86b484edd32 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 3 Mar 2026 09:36:07 +0530 Subject: [PATCH 08/15] Update dependencies --- app/lib/dose.dart | 2 +- app/lib/home/home.dart | 2 +- app/pubspec.lock | 72 +++++++++++++++---- app/pubspec.yaml | 7 +- .../flutter/generated_plugin_registrant.cc | 3 + app/windows/flutter/generated_plugins.cmake | 1 + 6 files changed, 69 insertions(+), 18 deletions(-) diff --git a/app/lib/dose.dart b/app/lib/dose.dart index bf5da0f..2199e25 100644 --- a/app/lib/dose.dart +++ b/app/lib/dose.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:app/home/home.dart'; import 'package:app/analytics/analytics.dart'; import 'package:app/profile/profile.dart'; -import 'package:app/home/addMenu.dart'; +import 'package:app/home/add_menu.dart'; class Dose extends StatefulWidget { const Dose({super.key}); diff --git a/app/lib/home/home.dart b/app/lib/home/home.dart index d430c44..0d1c0e9 100644 --- a/app/lib/home/home.dart +++ b/app/lib/home/home.dart @@ -63,7 +63,7 @@ class _HomePageState extends State { onPressed: () async { await NotificationHelper().showTestNotification(); }, - child: const Text('Trigger Test Notification'), + child: const Text('Test'), ), ), Expanded( diff --git a/app/pubspec.lock b/app/pubspec.lock index f2a4a9e..853022c 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + android_alarm_manager_plus: + dependency: "direct main" + description: + name: android_alarm_manager_plus + sha256: "591b97ceaaa8db69776a32bc1ea2fed8f83c3dadfa7b7e95351abfc0894361e7" + url: "https://pub.dev" + source: hosted + version: "5.0.0" args: dependency: transitive description: @@ -49,14 +57,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" - source: hosted - version: "1.0.8" dbus: dependency: transitive description: @@ -114,10 +114,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "76cd20bcfa72fabe50ea27eeaf165527f446f55d3033021462084b87805b4cac" + sha256: "2b50e938a275e1ad77352d6a25e25770f4130baa61eaf02de7a9a884680954ad" url: "https://pub.dev" source: hosted - version: "20.0.0" + version: "20.1.0" flutter_local_notifications_linux: dependency: transitive description: @@ -240,14 +240,62 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.dev" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.dev" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" petitparser: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" platform: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index ef0be0a..db811b9 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -30,16 +30,15 @@ environment: dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 + sqflite: ^2.4.2 path: ^1.9.1 dynamic_color: ^1.8.1 flutter_local_notifications: ^20.0.0 timezone: ^0.10.1 flutter_timezone: ^5.0.1 + permission_handler: ^12.0.1 + android_alarm_manager_plus: ^5.0.0 dev_dependencies: flutter_test: diff --git a/app/windows/flutter/generated_plugin_registrant.cc b/app/windows/flutter/generated_plugin_registrant.cc index 1c0de6b..03882ab 100644 --- a/app/windows/flutter/generated_plugin_registrant.cc +++ b/app/windows/flutter/generated_plugin_registrant.cc @@ -8,10 +8,13 @@ #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); FlutterTimezonePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); } diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake index be08263..65dc5f8 100644 --- a/app/windows/flutter/generated_plugins.cmake +++ b/app/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color flutter_timezone + permission_handler_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From 0fdd3b3dfee48e5bba230f9dcedfe8e9d182411e Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 3 Mar 2026 09:36:49 +0530 Subject: [PATCH 09/15] Add alarm service helper --- app/lib/services/alarm_service.dart | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/lib/services/alarm_service.dart diff --git a/app/lib/services/alarm_service.dart b/app/lib/services/alarm_service.dart new file mode 100644 index 0000000..e69de29 From 2699efa32cbdf0bb23234fc73c0dd9ba57799c17 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 3 Mar 2026 22:59:26 +0530 Subject: [PATCH 10/15] Testing notifications: success --- app/lib/home/add_menu.dart | 9 ++++ app/lib/home/home.dart | 2 +- app/lib/services/alarm_service.dart | 1 + app/lib/services/notification_service.dart | 56 +++++++++++++++++----- app/pubspec.lock | 16 +++---- 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/app/lib/home/add_menu.dart b/app/lib/home/add_menu.dart index 89f4749..8e94dad 100644 --- a/app/lib/home/add_menu.dart +++ b/app/lib/home/add_menu.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:app/models/cabinet.dart'; import 'package:app/db/cabinetdb.dart'; +import 'package:app/services/notification_service.dart'; class AddMedicineMenu extends StatefulWidget { final VoidCallback onSave; @@ -49,8 +50,16 @@ class _AddMedicineMenuState extends State { priority: _priority, ); + // 1. Save to DB and get the generated ID int newId = await DatabaseHelper.instance.create(medicine); + // 2. Schedule the notification using the real ID and Data + await NotificationHelper().scheduleMedicineNotification( + newId, + medicine.name, + timeString + ); + widget.onSave(); if (mounted) { diff --git a/app/lib/home/home.dart b/app/lib/home/home.dart index 0d1c0e9..150d399 100644 --- a/app/lib/home/home.dart +++ b/app/lib/home/home.dart @@ -61,7 +61,7 @@ class _HomePageState extends State { padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: () async { - await NotificationHelper().showTestNotification(); + await NotificationHelper().scheduleMedicineNotification(999, 'Test Medicine', '23:00'); }, child: const Text('Test'), ), diff --git a/app/lib/services/alarm_service.dart b/app/lib/services/alarm_service.dart index e69de29..7a5bb7b 100644 --- a/app/lib/services/alarm_service.dart +++ b/app/lib/services/alarm_service.dart @@ -0,0 +1 @@ +class AlarmService {} \ No newline at end of file diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart index 7be4af3..e878634 100644 --- a/app/lib/services/notification_service.dart +++ b/app/lib/services/notification_service.dart @@ -1,4 +1,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:timezone/data/latest_all.dart' as tz; +import 'package:timezone/timezone.dart' as tz; +import 'package:flutter_timezone/flutter_timezone.dart'; class NotificationHelper { static final NotificationHelper _instance = NotificationHelper._internal(); @@ -8,28 +11,59 @@ class NotificationHelper { final FlutterLocalNotificationsPlugin plugin = FlutterLocalNotificationsPlugin(); Future init() async { + tz.initializeTimeZones(); + final TimezoneInfo timeZoneInfo = await FlutterTimezone.getLocalTimezone(); + tz.setLocalLocation(tz.getLocation(timeZoneInfo.identifier)); + const AndroidInitializationSettings androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); const InitializationSettings settings = InitializationSettings(android: androidSettings); - await plugin.initialize(settings: settings); - - await plugin.resolvePlatformSpecificImplementation()?.requestNotificationsPermission(); + await plugin.initialize( + settings: settings, + onDidReceiveNotificationResponse: (NotificationResponse response) { + print("Action triggered: ${response.actionId} for payload: ${response.payload}"); + }, + ); + + final androidImplementation = plugin.resolvePlatformSpecificImplementation(); + await androidImplementation?.requestNotificationsPermission(); + await androidImplementation?.requestExactAlarmsPermission(); } - Future showTestNotification() async { + Future scheduleMedicineNotification(int id, String name, String timeString) async { + final parts = timeString.split(':'); + final int hour = int.parse(parts[0]); + final int minute = int.parse(parts[1]); + + final tz.TZDateTime now = tz.TZDateTime.now(tz.local); + tz.TZDateTime scheduledDate = tz.TZDateTime(tz.local, now.year, now.month, now.day, hour, minute); + + if (scheduledDate.isBefore(now)) { + scheduledDate = scheduledDate.add(const Duration(days: 1)); + } + const AndroidNotificationDetails androidDetails = AndroidNotificationDetails( - 'test_channel', - 'Test Channel', + 'priority_channel', + 'Priority Reminders', importance: Importance.max, priority: Priority.high, + actions: [ + AndroidNotificationAction('action_done', 'Done'), + AndroidNotificationAction('action_not_taken', 'Not taken'), + ], ); + const NotificationDetails details = NotificationDetails(android: androidDetails); - - await plugin.show( - id: 0, - title: 'Test Title', - body: 'Test Notification Works', + + await plugin.zonedSchedule( + id: id, + title: 'Time to take $name', + body: 'Did you take your dose?', + scheduledDate: scheduledDate, notificationDetails: details, + androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, + matchDateTimeComponents: DateTimeComponents.time, + payload: 'med_$id', ); } } \ No newline at end of file diff --git a/app/pubspec.lock b/app/pubspec.lock index 853022c..08d7741 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" clock: dependency: transitive description: @@ -212,18 +212,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: @@ -409,10 +409,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.9" timezone: dependency: "direct main" description: From 4894d37ba87814733729193daafc47b77b3ee8b9 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 3 Mar 2026 23:52:39 +0530 Subject: [PATCH 11/15] Implement notifications --- app/README.md | 5 +++- app/lib/db/cabinetdb.dart | 24 +++++++++++++++++++ app/lib/home/add_menu.dart | 2 -- app/lib/home/home.dart | 3 ++- app/lib/services/notification_service.dart | 28 ++++++++++++++++++++-- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/app/README.md b/app/README.md index 2db82b7..3ef1baf 100644 --- a/app/README.md +++ b/app/README.md @@ -9,12 +9,15 @@ lib/ │ ├── cabinetdb.dart │ └── profiledb.dart ├── home/ -│ ├── addMenu.dart +│ ├── add_menu.dart │ └── home.dart ├── models/ │ ├── cabinet.dart │ └── profile.dart ├── profile/ │ └── profile.dart +├── models/ +│ ├── alarm_service.dart +│ └── notification_service.dart ├── dose.dart └── main.dart \ No newline at end of file diff --git a/app/lib/db/cabinetdb.dart b/app/lib/db/cabinetdb.dart index 86c81d4..9a0bade 100644 --- a/app/lib/db/cabinetdb.dart +++ b/app/lib/db/cabinetdb.dart @@ -8,6 +8,30 @@ class DatabaseHelper { DatabaseHelper._init(); + Future update(Cabinet cabinet) async { + final db = await instance.database; + return db.update( + 'cabinet', + cabinet.toMap(), + where: 'id = ?', + whereArgs: [cabinet.id], + ); + } + + Future readMedicine(int id) async { + final db = await instance.database; + final maps = await db.query( + 'cabinet', + where: 'id = ?', + whereArgs: [id], + ); + + if (maps.isNotEmpty) { + return Cabinet.fromMap(maps.first); + } + return null; + } + Future get database async { if (_database != null) return _database!; _database = await _initDB('dose.db'); diff --git a/app/lib/home/add_menu.dart b/app/lib/home/add_menu.dart index 8e94dad..d99cefb 100644 --- a/app/lib/home/add_menu.dart +++ b/app/lib/home/add_menu.dart @@ -50,10 +50,8 @@ class _AddMedicineMenuState extends State { priority: _priority, ); - // 1. Save to DB and get the generated ID int newId = await DatabaseHelper.instance.create(medicine); - // 2. Schedule the notification using the real ID and Data await NotificationHelper().scheduleMedicineNotification( newId, medicine.name, diff --git a/app/lib/home/home.dart b/app/lib/home/home.dart index 150d399..56b2612 100644 --- a/app/lib/home/home.dart +++ b/app/lib/home/home.dart @@ -45,6 +45,7 @@ class _HomePageState extends State { if (result == 'delete') { await DatabaseHelper.instance.delete(id); + await NotificationHelper().cancelNotification(id); setState(() { _refreshList(); }); @@ -61,7 +62,7 @@ class _HomePageState extends State { padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: () async { - await NotificationHelper().scheduleMedicineNotification(999, 'Test Medicine', '23:00'); + await NotificationHelper().scheduleMedicineNotification(999, 'Test Medicine', '23:18'); }, child: const Text('Test'), ), diff --git a/app/lib/services/notification_service.dart b/app/lib/services/notification_service.dart index e878634..f981b9f 100644 --- a/app/lib/services/notification_service.dart +++ b/app/lib/services/notification_service.dart @@ -2,6 +2,8 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:timezone/data/latest_all.dart' as tz; import 'package:timezone/timezone.dart' as tz; import 'package:flutter_timezone/flutter_timezone.dart'; +import 'package:app/db/cabinetdb.dart'; +import 'package:app/models/cabinet.dart'; class NotificationHelper { static final NotificationHelper _instance = NotificationHelper._internal(); @@ -20,8 +22,26 @@ class NotificationHelper { await plugin.initialize( settings: settings, - onDidReceiveNotificationResponse: (NotificationResponse response) { - print("Action triggered: ${response.actionId} for payload: ${response.payload}"); + onDidReceiveNotificationResponse: (NotificationResponse response) async { + if (response.payload != null && response.payload!.startsWith('med_')) { + final int id = int.parse(response.payload!.split('_')[1]); + + if (response.actionId == 'action_done') { + final med = await DatabaseHelper.instance.readMedicine(id); + if (med != null && med.currstock > 0) { + final updatedMed = Cabinet( + id: med.id, + name: med.name, + dosage: med.dosage, + time: med.time, + initstock: med.initstock, + currstock: med.currstock - 1, + priority: med.priority, + ); + await DatabaseHelper.instance.update(updatedMed); + } + } + } }, ); @@ -66,4 +86,8 @@ class NotificationHelper { payload: 'med_$id', ); } + + Future cancelNotification(int id) async { + await plugin.cancel(id: id); + } } \ No newline at end of file From 0b5a222f82955d62be7b663a0b536479aa78e330 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Tue, 3 Mar 2026 23:54:33 +0530 Subject: [PATCH 12/15] Fix int-readme --- app/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/README.md b/app/README.md index 3ef1baf..4ab2ffc 100644 --- a/app/README.md +++ b/app/README.md @@ -16,7 +16,7 @@ lib/ │ └── profile.dart ├── profile/ │ └── profile.dart -├── models/ +├── services/ │ ├── alarm_service.dart │ └── notification_service.dart ├── dose.dart From b07789a8654753e0acafb852b63d46e854730744 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Wed, 4 Mar 2026 17:47:11 +0530 Subject: [PATCH 13/15] Update dependencies --- .../Flutter/GeneratedPluginRegistrant.swift | 2 + app/pubspec.lock | 130 +++++++++++++++++- app/pubspec.yaml | 2 +- 3 files changed, 128 insertions(+), 6 deletions(-) diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index ce40423..8d243d8 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,11 +8,13 @@ import Foundation import dynamic_color import flutter_local_notifications import flutter_timezone +import shared_preferences_foundation import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/app/pubspec.lock b/app/pubspec.lock index 08d7741..21af346 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -1,14 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - android_alarm_manager_plus: + alarm: dependency: "direct main" description: - name: android_alarm_manager_plus - sha256: "591b97ceaaa8db69776a32bc1ea2fed8f83c3dadfa7b7e95351abfc0894361e7" + name: alarm + sha256: f50869fb28d46ce44922340a44a0ad9ee4be298349efeb20bbfd44aeba0c257d url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.2.1" args: dependency: transitive description: @@ -97,11 +97,27 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_fgbg: + dependency: transitive + description: + name: flutter_fgbg + sha256: eb6da9b2047372566a6e17b505975fe5bace94af01f6fc825c4b6f81baa6c447 + url: "https://pub.dev" + source: hosted + version: "0.7.1" flutter_lints: dependency: "direct dev" description: @@ -176,6 +192,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" leak_tracker: dependency: transitive description: @@ -208,6 +232,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" matcher: dependency: transitive description: @@ -240,6 +272,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" permission_handler: dependency: "direct main" description: @@ -312,6 +368,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8374d6200ab33ac99031a852eba4c8eb2170c4bf20778b3e2c9eccb45384fb41" + url: "https://pub.dev" + source: hosted + version: "2.4.21" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -471,4 +591,4 @@ packages: version: "6.6.1" sdks: dart: ">=3.10.1 <4.0.0" - flutter: ">=3.32.0" + flutter: ">=3.35.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml index db811b9..8c5bff2 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -38,7 +38,7 @@ dependencies: timezone: ^0.10.1 flutter_timezone: ^5.0.1 permission_handler: ^12.0.1 - android_alarm_manager_plus: ^5.0.0 + alarm: ^5.2.1 dev_dependencies: flutter_test: From 886068cd5383f680e13739899bbb081008aa5734 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Wed, 4 Mar 2026 18:10:53 +0530 Subject: [PATCH 14/15] Testing alarms: ongoing --- app/android/app/src/main/AndroidManifest.xml | 2 + app/lib/home/add_menu.dart | 1 + app/lib/main.dart | 4 +- app/lib/services/alarm_service.dart | 60 +++++++++++++++++++- 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml index c926621..af36229 100644 --- a/app/android/app/src/main/AndroidManifest.xml +++ b/app/android/app/src/main/AndroidManifest.xml @@ -5,6 +5,8 @@ + + _instance; + AlarmService._internal(); + + Future init() async { + await Alarm.init(); + } + + Future scheduleMedicineAlarm(Cabinet medicine) async { + if (medicine.priority == 0) return; + + final parts = medicine.time.split(':'); + final int hour = int.parse(parts[0]); + final int minute = int.parse(parts[1]); + + DateTime now = DateTime.now(); + DateTime scheduledDate = DateTime( + now.year, + now.month, + now.day, + hour, + minute, + ); + + if (scheduledDate.isBefore(now)) { + scheduledDate = scheduledDate.add(const Duration(days: 1)); + } + + final alarmSettings = AlarmSettings( + id: medicine.id!, + dateTime: scheduledDate, + assetAudioPath: 'assets/alarm.mp3', + loopAudio: true, + vibrate: true, + warningNotificationOnKill: true, + androidFullScreenIntent: true, + volumeSettings: VolumeSettings.fade( + volume: medicine.priority == 2 ? 1.0 : 0.8, + fadeDuration: const Duration(seconds: 3), + volumeEnforced: true, + ), + notificationSettings: NotificationSettings( + title: 'Time to take ${medicine.name}', + body: 'Please take your scheduled dose.', + stopButton: 'Stop', + ), + ); + + await Alarm.set(alarmSettings: alarmSettings); + } + + Future cancelAlarm(int id) async { + await Alarm.stop(id); + } +} \ No newline at end of file From 29f3d06325fb4f7726bc29a0648bc14b9d8eb605 Mon Sep 17 00:00:00 2001 From: Jonathan Jimson Date: Thu, 5 Mar 2026 21:34:32 +0530 Subject: [PATCH 15/15] Testing alarms: success --- app/lib/dose.dart | 24 +++++++++ app/lib/home/add_menu.dart | 4 +- app/lib/home/home.dart | 4 +- app/lib/services/alarm_ring.dart | 35 ++++++++++++ app/lib/services/alarm_service.dart | 84 ++++++++++++++++++----------- 5 files changed, 118 insertions(+), 33 deletions(-) create mode 100644 app/lib/services/alarm_ring.dart diff --git a/app/lib/dose.dart b/app/lib/dose.dart index 2199e25..5801601 100644 --- a/app/lib/dose.dart +++ b/app/lib/dose.dart @@ -3,6 +3,9 @@ import 'package:app/home/home.dart'; import 'package:app/analytics/analytics.dart'; import 'package:app/profile/profile.dart'; import 'package:app/home/add_menu.dart'; +import 'dart:async'; +import 'package:alarm/alarm.dart'; +import 'package:app/services/alarm_ring.dart'; class Dose extends StatefulWidget { const Dose({super.key}); @@ -43,6 +46,27 @@ class _DoseState extends State { } } + StreamSubscription? ringSubscription; + + @override + void initState() { + super.initState(); + ringSubscription = Alarm.ringStream.stream.listen((alarmSettings) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AlarmRingScreen(alarmSettings: alarmSettings), + ), + ); + }); + } + + @override + void dispose() { + ringSubscription?.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( diff --git a/app/lib/home/add_menu.dart b/app/lib/home/add_menu.dart index 7d81216..91231a9 100644 --- a/app/lib/home/add_menu.dart +++ b/app/lib/home/add_menu.dart @@ -52,7 +52,8 @@ class _AddMedicineMenuState extends State { ); int newId = await DatabaseHelper.instance.create(medicine); - + + await AlarmService().scheduleMedicineAlarm(newId, medicine); await NotificationHelper().scheduleMedicineNotification( newId, medicine.name, @@ -64,6 +65,7 @@ class _AddMedicineMenuState extends State { if (mounted) { Navigator.pop(context); } + } } diff --git a/app/lib/home/home.dart b/app/lib/home/home.dart index 56b2612..1f9de2c 100644 --- a/app/lib/home/home.dart +++ b/app/lib/home/home.dart @@ -2,6 +2,7 @@ import 'package:app/models/cabinet.dart'; import 'package:flutter/material.dart'; import 'package:app/db/cabinetdb.dart'; import 'package:app/services/notification_service.dart'; +import 'package:app/services/alarm_service.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @@ -46,6 +47,7 @@ class _HomePageState extends State { if (result == 'delete') { await DatabaseHelper.instance.delete(id); await NotificationHelper().cancelNotification(id); + await AlarmService().cancelAlarm(id); setState(() { _refreshList(); }); @@ -62,7 +64,7 @@ class _HomePageState extends State { padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: () async { - await NotificationHelper().scheduleMedicineNotification(999, 'Test Medicine', '23:18'); + await AlarmService().triggerTestAlarm(); }, child: const Text('Test'), ), diff --git a/app/lib/services/alarm_ring.dart b/app/lib/services/alarm_ring.dart new file mode 100644 index 0000000..81774a1 --- /dev/null +++ b/app/lib/services/alarm_ring.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:alarm/alarm.dart'; + +class AlarmRingScreen extends StatelessWidget { + final AlarmSettings alarmSettings; + + const AlarmRingScreen({super.key, required this.alarmSettings}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.alarm, size: 100), + const SizedBox(height: 20), + Text( + alarmSettings.notificationSettings.title, + style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 40), + ElevatedButton( + onPressed: () async { + await Alarm.stop(alarmSettings.id); + if (context.mounted) Navigator.pop(context); + }, + child: const Text('Stop Alarm'), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/app/lib/services/alarm_service.dart b/app/lib/services/alarm_service.dart index eaf03d2..aeedba6 100644 --- a/app/lib/services/alarm_service.dart +++ b/app/lib/services/alarm_service.dart @@ -6,46 +6,22 @@ class AlarmService { factory AlarmService() => _instance; AlarmService._internal(); - Future init() async { - await Alarm.init(); - } - - Future scheduleMedicineAlarm(Cabinet medicine) async { - if (medicine.priority == 0) return; - - final parts = medicine.time.split(':'); - final int hour = int.parse(parts[0]); - final int minute = int.parse(parts[1]); - - DateTime now = DateTime.now(); - DateTime scheduledDate = DateTime( - now.year, - now.month, - now.day, - hour, - minute, - ); - - if (scheduledDate.isBefore(now)) { - scheduledDate = scheduledDate.add(const Duration(days: 1)); - } - + Future triggerTestAlarm() async { final alarmSettings = AlarmSettings( - id: medicine.id!, - dateTime: scheduledDate, - assetAudioPath: 'assets/alarm.mp3', + id: 999, + dateTime: DateTime.now().add(const Duration(seconds: 20)), + assetAudioPath: null, loopAudio: true, vibrate: true, warningNotificationOnKill: true, androidFullScreenIntent: true, volumeSettings: VolumeSettings.fade( - volume: medicine.priority == 2 ? 1.0 : 0.8, + volume: 1.0, fadeDuration: const Duration(seconds: 3), - volumeEnforced: true, ), notificationSettings: NotificationSettings( - title: 'Time to take ${medicine.name}', - body: 'Please take your scheduled dose.', + title: 'Test Alarm', + body: 'Testing the alarm system.', stopButton: 'Stop', ), ); @@ -53,6 +29,52 @@ class AlarmService { await Alarm.set(alarmSettings: alarmSettings); } + Future init() async { + await Alarm.init(); + } + + Future scheduleMedicineAlarm(int id, Cabinet medicine) async { + if (medicine.priority != 2) return; + + final parts = medicine.time.split(':'); + final int hour = int.parse(parts[0]); + final int minute = int.parse(parts[1]); + + DateTime now = DateTime.now(); + DateTime scheduledDate = DateTime( + now.year, + now.month, + now.day, + hour, + minute, + ); + + if (scheduledDate.isBefore(now)) { + scheduledDate = scheduledDate.add(const Duration(days: 1)); + } + + final alarmSettings = AlarmSettings( + id: id, + dateTime: scheduledDate, + assetAudioPath: null, + loopAudio: true, + vibrate: true, + warningNotificationOnKill: true, + androidFullScreenIntent: true, + volumeSettings: VolumeSettings.fade( + volume: 1.0, + fadeDuration: const Duration(seconds: 3), + ), + notificationSettings: NotificationSettings( + title: 'Time to take ${medicine.name}', + body: 'Please take your scheduled dose.', + stopButton: 'Stop', + ), + ); + + await Alarm.set(alarmSettings: alarmSettings); +} + Future cancelAlarm(int id) async { await Alarm.stop(id); }