Added Graph
parent
333046951c
commit
d4dd475e0b
@ -1,191 +0,0 @@
|
|||||||
import 'dart:math';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
|
|
||||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
|
||||||
|
|
||||||
class LocalNotification {
|
|
||||||
Function(String payload)? _onNotificationClick;
|
|
||||||
static LocalNotification? _instance;
|
|
||||||
|
|
||||||
static LocalNotification? getInstance() {
|
|
||||||
return _instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static init({required Function(String payload) onNotificationClick}) {
|
|
||||||
if (_instance == null) {
|
|
||||||
_instance = LocalNotification();
|
|
||||||
_instance?._onNotificationClick = onNotificationClick;
|
|
||||||
_instance?._initialize();
|
|
||||||
} else {
|
|
||||||
// assert(false,(){
|
|
||||||
// //TODO fix it
|
|
||||||
// "LocalNotification Already Initialized";
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_initialize() async {
|
|
||||||
try {
|
|
||||||
var initializationSettingsAndroid = new AndroidInitializationSettings('app_icon');
|
|
||||||
var initializationSettingsIOS = DarwinInitializationSettings();
|
|
||||||
var initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
|
|
||||||
await flutterLocalNotificationsPlugin.initialize(
|
|
||||||
initializationSettings,
|
|
||||||
onDidReceiveNotificationResponse: (NotificationResponse notificationResponse) {
|
|
||||||
switch (notificationResponse.notificationResponseType) {
|
|
||||||
case NotificationResponseType.selectedNotification:
|
|
||||||
// selectNotificationStream.add(notificationResponse.payload);
|
|
||||||
break;
|
|
||||||
case NotificationResponseType.selectedNotificationAction:
|
|
||||||
// if (notificationResponse.actionId == navigationActionId) {
|
|
||||||
// selectNotificationStream.add(notificationResponse.payload);
|
|
||||||
// }
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// onDidReceiveBackgroundNotificationResponse: notificationTapBackground,
|
|
||||||
);
|
|
||||||
} catch (ex) {
|
|
||||||
print(ex.toString());
|
|
||||||
}
|
|
||||||
// flutterLocalNotificationsPlugin.initialize(initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse notificationResponse)
|
|
||||||
// {
|
|
||||||
// switch (notificationResponse.notificationResponseType) {
|
|
||||||
// case NotificationResponseType.selectedNotification:
|
|
||||||
// // selectNotificationStream.add(notificationResponse.payload);
|
|
||||||
// break;
|
|
||||||
// case NotificationResponseType.selectedNotificationAction:
|
|
||||||
// // if (notificationResponse.actionId == navigationActionId) {
|
|
||||||
// // selectNotificationStream.add(notificationResponse.payload);
|
|
||||||
// }
|
|
||||||
// // break;
|
|
||||||
// },}
|
|
||||||
//
|
|
||||||
// ,
|
|
||||||
//
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
|
|
||||||
// void notificationTapBackground(NotificationResponse notificationResponse) {
|
|
||||||
// // ignore: avoid_print
|
|
||||||
// print('notification(${notificationResponse.id}) action tapped: '
|
|
||||||
// '${notificationResponse.actionId} with'
|
|
||||||
// ' payload: ${notificationResponse.payload}');
|
|
||||||
// if (notificationResponse.input?.isNotEmpty ?? false) {
|
|
||||||
// // ignore: avoid_print
|
|
||||||
// print('notification action tapped with input: ${notificationResponse.input}');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
var _random = new Random();
|
|
||||||
|
|
||||||
_randomNumber({int from = 100000}) {
|
|
||||||
return _random.nextInt(from);
|
|
||||||
}
|
|
||||||
|
|
||||||
_vibrationPattern() {
|
|
||||||
var vibrationPattern = Int64List(4);
|
|
||||||
vibrationPattern[0] = 0;
|
|
||||||
vibrationPattern[1] = 1000;
|
|
||||||
vibrationPattern[2] = 5000;
|
|
||||||
vibrationPattern[3] = 2000;
|
|
||||||
|
|
||||||
return vibrationPattern;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future? showNow({required String title, required String subtitle, required String payload}) {
|
|
||||||
Future.delayed(Duration(seconds: 1)).then((result) async {
|
|
||||||
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
|
|
||||||
'com.hmg.local_notification',
|
|
||||||
'HMG',
|
|
||||||
channelDescription: 'HMG',
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.high,
|
|
||||||
ticker: 'ticker',
|
|
||||||
vibrationPattern: _vibrationPattern(),
|
|
||||||
ongoing: true,
|
|
||||||
autoCancel: false,
|
|
||||||
usesChronometer: true,
|
|
||||||
when: DateTime.now().millisecondsSinceEpoch - 120 * 1000,
|
|
||||||
);
|
|
||||||
var iOSPlatformChannelSpecifics = DarwinNotificationDetails();
|
|
||||||
var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics);
|
|
||||||
await flutterLocalNotificationsPlugin.show(25613, title, subtitle, platformChannelSpecifics, payload: payload).catchError((err) {
|
|
||||||
print(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future scheduleNotification({required DateTime scheduledNotificationDateTime, required String title, required String description}) async {
|
|
||||||
///vibrationPattern
|
|
||||||
var vibrationPattern = Int64List(4);
|
|
||||||
vibrationPattern[0] = 0;
|
|
||||||
vibrationPattern[1] = 1000;
|
|
||||||
vibrationPattern[2] = 5000;
|
|
||||||
vibrationPattern[3] = 2000;
|
|
||||||
|
|
||||||
// var androidPlatformChannelSpecifics = AndroidNotificationDetails('active-prescriptions', 'ActivePrescriptions',
|
|
||||||
// channelDescription: 'ActivePrescriptionsDescription',
|
|
||||||
// // icon: 'secondary_icon',
|
|
||||||
// sound: RawResourceAndroidNotificationSound('slow_spring_board'),
|
|
||||||
//
|
|
||||||
// ///change it to be as ionic
|
|
||||||
// // largeIcon: DrawableResourceAndroidBitmap('sample_large_icon'),///change it to be as ionic
|
|
||||||
// vibrationPattern: vibrationPattern,
|
|
||||||
// enableLights: true,
|
|
||||||
// color: const Color.fromARGB(255, 255, 0, 0),
|
|
||||||
// ledColor: const Color.fromARGB(255, 255, 0, 0),
|
|
||||||
// ledOnMs: 1000,
|
|
||||||
// ledOffMs: 500);
|
|
||||||
// var iOSPlatformChannelSpecifics = DarwinNotificationDetails(sound: 'slow_spring_board.aiff');
|
|
||||||
|
|
||||||
// /change it to be as ionic
|
|
||||||
// var platformChannelSpecifics = NotificationDetails(android: androidPlatformChannelSpecifics, iOS: iOSPlatformChannelSpecifics);
|
|
||||||
// await flutterLocalNotificationsPlugin.schedule(0, title, description, scheduledNotificationDateTime, platformChannelSpecifics);
|
|
||||||
}
|
|
||||||
|
|
||||||
///Repeat notification every day at approximately 10:00:00 am
|
|
||||||
Future showDailyAtTime() async {
|
|
||||||
// var time = Time(10, 0, 0);
|
|
||||||
// var androidPlatformChannelSpecifics = AndroidNotificationDetails('repeatDailyAtTime channel id', 'repeatDailyAtTime channel name', channelDescription: 'repeatDailyAtTime description');
|
|
||||||
// var iOSPlatformChannelSpecifics = DarwinNotificationDetails();
|
|
||||||
// var platformChannelSpecifics = NotificationDetails(
|
|
||||||
// androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
|
|
||||||
// await flutterLocalNotificationsPlugin.showDailyAtTime(
|
|
||||||
// 0,
|
|
||||||
// 'show daily title',
|
|
||||||
// 'Daily notification shown at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
|
|
||||||
// time,
|
|
||||||
// platformChannelSpecifics);
|
|
||||||
}
|
|
||||||
|
|
||||||
///Repeat notification weekly on Monday at approximately 10:00:00 am
|
|
||||||
Future showWeeklyAtDayAndTime() async {
|
|
||||||
// var time = Time(10, 0, 0);
|
|
||||||
// var androidPlatformChannelSpecifics = AndroidNotificationDetails('show weekly channel id', 'show weekly channel name', channelDescription: 'show weekly description');
|
|
||||||
// var iOSPlatformChannelSpecifics = DarwinNotificationDetails();
|
|
||||||
// var platformChannelSpecifics = NotificationDetails(
|
|
||||||
// androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
|
|
||||||
// await flutterLocalNotificationsPlugin.showWeeklyAtDayAndTime(
|
|
||||||
// 0,
|
|
||||||
// 'show weekly title',
|
|
||||||
// 'Weekly notification shown on Monday at approximately ${_toTwoDigitString(time.hour)}:${_toTwoDigitString(time.minute)}:${_toTwoDigitString(time.second)}',
|
|
||||||
// Day.Monday,
|
|
||||||
// time,
|
|
||||||
// platformChannelSpecifics);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _toTwoDigitString(int value) {
|
|
||||||
return value.toString().padLeft(2, '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future cancelNotification() async {
|
|
||||||
await flutterLocalNotificationsPlugin.cancel(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future cancelAllNotifications() async {
|
|
||||||
await flutterLocalNotificationsPlugin.cancelAll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||||
|
|
||||||
|
class HydrationTipsWidget extends StatelessWidget {
|
||||||
|
const HydrationTipsWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 24.w),
|
||||||
|
padding: EdgeInsets.all(16.h),
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
borderRadius: 24.r,
|
||||||
|
hasShadow: true,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Utils.buildSvgWithAssets(
|
||||||
|
icon: AppAssets.bulb_icon,
|
||||||
|
width: 24.w,
|
||||||
|
height: 24.h,
|
||||||
|
),
|
||||||
|
SizedBox(width: 8.w),
|
||||||
|
"Tips to stay hydrated".needTranslation.toText16(isBold: true),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
" • ${"Drink before you feel thirsty"}".needTranslation.toText12(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textColorLight,
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.h),
|
||||||
|
" • ${"Keep a refillable bottle next to you"}".needTranslation.toText12(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textColorLight,
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.h),
|
||||||
|
" • ${"Track your daily intake to stay motivated"}".needTranslation.toText12(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textColorLight,
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.h),
|
||||||
|
" • ${"Choose sparkling water instead of soda"}".needTranslation.toText12(
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: AppColors.textColorLight,
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -0,0 +1,176 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart';
|
||||||
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class WaterActionButtonsWidget extends StatelessWidget {
|
||||||
|
const WaterActionButtonsWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<WaterMonitorViewModel>(builder: (context, vm, _) {
|
||||||
|
final cupAmount = vm.selectedCupCapacityMl;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
if (cupAmount > 0) {
|
||||||
|
await vm.undoUserActivity();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Utils.buildSvgWithAssets(
|
||||||
|
icon: AppAssets.minimizeIcon,
|
||||||
|
height: 20.w,
|
||||||
|
width: 20.w,
|
||||||
|
iconColor: AppColors.textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 4.w),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||||
|
color: AppColors.blueColor,
|
||||||
|
borderRadius: 99.r,
|
||||||
|
hasShadow: true,
|
||||||
|
),
|
||||||
|
child: (cupAmount > 0 ? "+ $cupAmount ml" : "+ 0ml").toText12(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
if (cupAmount > 0) {
|
||||||
|
await vm.insertUserActivity(quantityIntake: cupAmount);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Utils.buildSvgWithAssets(
|
||||||
|
icon: AppAssets.addIconDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
_buildActionButton(
|
||||||
|
context: context,
|
||||||
|
onTap: () => showSwitchCupBottomSheet(context),
|
||||||
|
overlayWidget: AppAssets.refreshIcon,
|
||||||
|
title: "Switch Cup".needTranslation,
|
||||||
|
icon: Utils.buildSvgWithAssets(icon: AppAssets.glassIcon, height: 24.w, width: 24.w),
|
||||||
|
),
|
||||||
|
_buildActionButton(
|
||||||
|
context: context,
|
||||||
|
onTap: () async {
|
||||||
|
final success = await vm.scheduleTestNotification();
|
||||||
|
if (success) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Test notification will appear in 5 seconds!'.needTranslation),
|
||||||
|
backgroundColor: AppColors.blueColor,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
margin: EdgeInsets.all(16.w),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Failed to schedule test notification'.needTranslation),
|
||||||
|
backgroundColor: AppColors.errorColor,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
margin: EdgeInsets.all(16.w),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: "Test Alert".needTranslation,
|
||||||
|
icon: Icon(
|
||||||
|
Icons.notifications_outlined,
|
||||||
|
color: AppColors.blueColor,
|
||||||
|
size: 24.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildActionButton(
|
||||||
|
context: context,
|
||||||
|
onTap: () => context.navigateWithName(AppRoutes.waterMonitorSettingsScreen),
|
||||||
|
title: "Settings".needTranslation,
|
||||||
|
icon: Icon(
|
||||||
|
Icons.settings,
|
||||||
|
color: AppColors.blueColor,
|
||||||
|
size: 24.w,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildActionButton({
|
||||||
|
required BuildContext context,
|
||||||
|
String? overlayWidget,
|
||||||
|
required String title,
|
||||||
|
required Widget icon,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 46.w,
|
||||||
|
width: 46.w,
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||||
|
color: AppColors.blueColor.withValues(alpha: 0.14),
|
||||||
|
borderRadius: 12.r,
|
||||||
|
hasShadow: true,
|
||||||
|
),
|
||||||
|
child: Center(child: icon),
|
||||||
|
),
|
||||||
|
if (overlayWidget != null) ...[
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(2.w),
|
||||||
|
height: 16.w,
|
||||||
|
width: 16.w,
|
||||||
|
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
|
||||||
|
color: AppColors.blueColor,
|
||||||
|
borderRadius: 100.r,
|
||||||
|
hasShadow: true,
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Utils.buildSvgWithAssets(
|
||||||
|
icon: AppAssets.refreshIcon,
|
||||||
|
iconColor: AppColors.whiteColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 4.h),
|
||||||
|
title.toText10(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_assets.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/utils/utils.dart';
|
||||||
|
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/bottle_shape_clipper.dart';
|
||||||
|
import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_consumption_progress_widget.dart';
|
||||||
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class WaterBottleWidget extends StatelessWidget {
|
||||||
|
const WaterBottleWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<WaterMonitorViewModel>(
|
||||||
|
builder: (context, vm, _) {
|
||||||
|
final progressPercent = (vm.progress * 100).clamp(0.0, 100.0);
|
||||||
|
|
||||||
|
// SVG aspect ratio
|
||||||
|
const svgAspectRatio = 315.0 / 143.0; // ~2.2
|
||||||
|
|
||||||
|
// Responsive bottle sizing with device-specific constraints
|
||||||
|
double bottleWidth;
|
||||||
|
if (isTablet) {
|
||||||
|
bottleWidth = math.min(SizeUtils.width * 0.15, 180.0);
|
||||||
|
} else if (isFoldable) {
|
||||||
|
bottleWidth = math.min(100.w, 160.0);
|
||||||
|
} else {
|
||||||
|
bottleWidth = math.min(120.w, 140.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
final bottleHeight = bottleWidth * svgAspectRatio;
|
||||||
|
|
||||||
|
// Fillable area percentages
|
||||||
|
final fillableHeightPercent = 0.7;
|
||||||
|
const fillableWidthPercent = 0.8;
|
||||||
|
|
||||||
|
final fillableHeight = bottleHeight * fillableHeightPercent;
|
||||||
|
final fillableWidth = bottleWidth * fillableWidthPercent;
|
||||||
|
|
||||||
|
// Device-specific positioning offsets
|
||||||
|
final double leftOffset = isTablet ? 4.w : 8.w;
|
||||||
|
final double bottomOffset = isTablet ? -65.h : -78.h;
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: bottleHeight,
|
||||||
|
width: bottleWidth,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// Bottle SVG outline
|
||||||
|
Center(
|
||||||
|
child: Utils.buildSvgWithAssets(
|
||||||
|
icon: AppAssets.waterBottle,
|
||||||
|
height: bottleHeight,
|
||||||
|
width: bottleWidth,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Wave and bubbles clipped to bottle shape
|
||||||
|
Positioned.fill(
|
||||||
|
left: leftOffset,
|
||||||
|
bottom: bottomOffset,
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: fillableWidth,
|
||||||
|
height: fillableHeight,
|
||||||
|
child: ClipPath(
|
||||||
|
clipper: BottleShapeClipper(),
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
children: [
|
||||||
|
// Animated wave
|
||||||
|
Positioned(
|
||||||
|
child: WaterConsumptionProgressWidget(
|
||||||
|
progress: progressPercent,
|
||||||
|
size: math.min(fillableWidth, fillableHeight),
|
||||||
|
containerWidth: fillableWidth,
|
||||||
|
containerHeight: fillableHeight,
|
||||||
|
waveDuration: const Duration(milliseconds: 3000),
|
||||||
|
waveColor: AppColors.blueColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Bubbles (only show if progress > 10%)
|
||||||
|
if (progressPercent > 10)
|
||||||
|
Positioned(
|
||||||
|
bottom: fillableHeight * 0.12,
|
||||||
|
child: Utils.buildSvgWithAssets(
|
||||||
|
icon: AppAssets.waterBottleOuterBubbles,
|
||||||
|
height: isTablet ? math.min(45.0, fillableHeight * 0.2) : math.min(55.0, fillableHeight * 0.22),
|
||||||
|
width: fillableWidth * 0.65,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,116 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hmg_patient_app_new/core/app_export.dart';
|
||||||
|
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
|
||||||
|
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
|
||||||
|
import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_action_buttons_widget.dart';
|
||||||
|
import 'package:hmg_patient_app_new/presentation/water_monitor/widgets/water_bottle_widget.dart';
|
||||||
|
import 'package:hmg_patient_app_new/theme/colors.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:shimmer/shimmer.dart';
|
||||||
|
|
||||||
|
class WaterIntakeSummaryWidget extends StatelessWidget {
|
||||||
|
const WaterIntakeSummaryWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: EdgeInsets.all(24.w),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [
|
||||||
|
AppColors.blueGradientColorOne,
|
||||||
|
AppColors.blueGradientColorTwo,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: isTablet ? 2 : 3,
|
||||||
|
child: Consumer<WaterMonitorViewModel>(builder: (context, vm, _) {
|
||||||
|
if (vm.isLoading) {
|
||||||
|
return _buildLoadingShimmer();
|
||||||
|
}
|
||||||
|
|
||||||
|
final goalMl = vm.dailyGoalMl;
|
||||||
|
final consumed = vm.totalConsumedMl;
|
||||||
|
final remaining = (goalMl - consumed) > 0 ? (goalMl - consumed) : 0;
|
||||||
|
final completedPercent = "${(vm.progress * 100).clamp(0.0, 100.0).toStringAsFixed(0)}%";
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
"Next Drink Time".needTranslation.toText18(weight: FontWeight.w600, color: AppColors.textColor),
|
||||||
|
vm.nextDrinkTime.toText32(weight: FontWeight.w600, color: AppColors.blueColor),
|
||||||
|
SizedBox(height: 12.h),
|
||||||
|
_buildStatusColumn(title: "Your Goal".needTranslation, subTitle: "${goalMl}ml"),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
_buildStatusColumn(title: "Remaining".needTranslation, subTitle: "${remaining}ml"),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
_buildStatusColumn(title: "Completed".needTranslation, subTitle: completedPercent, subTitleColor: AppColors.successColor),
|
||||||
|
SizedBox(height: 8.h),
|
||||||
|
_buildStatusColumn(
|
||||||
|
title: "Hydration Status".needTranslation,
|
||||||
|
subTitle: vm.hydrationStatus,
|
||||||
|
subTitleColor: vm.hydrationStatusColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
SizedBox(width: isTablet ? 32 : 16.w),
|
||||||
|
Expanded(
|
||||||
|
flex: isTablet ? 1 : 2,
|
||||||
|
child: const WaterBottleWidget(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const WaterActionButtonsWidget(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatusColumn({required String title, required String subTitle, Color? subTitleColor}) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
"$title: ".toText16(weight: FontWeight.w500, color: AppColors.textColor),
|
||||||
|
subTitle.toText12(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: subTitleColor ?? AppColors.greyTextColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildLoadingShimmer() {
|
||||||
|
return ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
padding: EdgeInsets.all(0.w),
|
||||||
|
itemCount: 4,
|
||||||
|
separatorBuilder: (_, __) => SizedBox(height: 12.h),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return Shimmer.fromColors(
|
||||||
|
baseColor: AppColors.shimmerBaseColor,
|
||||||
|
highlightColor: AppColors.shimmerHighlightColor,
|
||||||
|
child: Container(
|
||||||
|
height: 40.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.whiteColor,
|
||||||
|
borderRadius: BorderRadius.circular(10.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,373 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
import 'package:hmg_patient_app_new/services/logger_service.dart';
|
||||||
|
import 'package:timezone/data/latest_all.dart' as tz;
|
||||||
|
import 'package:timezone/timezone.dart' as tz show TZDateTime, local, setLocalLocation, getLocation;
|
||||||
|
|
||||||
|
/// Abstract class defining the notification service interface
|
||||||
|
abstract class NotificationService {
|
||||||
|
/// Initialize the notification service
|
||||||
|
Future<void> initialize({Function(String payload)? onNotificationClick});
|
||||||
|
|
||||||
|
/// Request notification permissions (mainly for iOS)
|
||||||
|
Future<bool> requestPermissions();
|
||||||
|
|
||||||
|
/// Show an immediate notification
|
||||||
|
Future<void> showNotification({
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Schedule a notification at a specific date and time
|
||||||
|
Future<void> scheduleNotification({
|
||||||
|
required int id,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
required DateTime scheduledDate,
|
||||||
|
String? payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Schedule daily notifications at specific times
|
||||||
|
Future<void> scheduleDailyNotifications({
|
||||||
|
required List<DateTime> times,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Schedule water reminder notifications
|
||||||
|
Future<void> scheduleWaterReminders({
|
||||||
|
required List<DateTime> reminderTimes,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Cancel a specific notification by id
|
||||||
|
Future<void> cancelNotification(int id);
|
||||||
|
|
||||||
|
/// Cancel all scheduled notifications
|
||||||
|
Future<void> cancelAllNotifications();
|
||||||
|
|
||||||
|
/// Get list of pending notifications
|
||||||
|
Future<List<PendingNotificationRequest>> getPendingNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of NotificationService following the project architecture
|
||||||
|
class NotificationServiceImp implements NotificationService {
|
||||||
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
|
||||||
|
final LoggerService loggerService;
|
||||||
|
|
||||||
|
NotificationServiceImp({required this.flutterLocalNotificationsPlugin, required this.loggerService});
|
||||||
|
|
||||||
|
// Channel IDs for different notification types
|
||||||
|
static const String _waterReminderChannelId = 'water_reminder_channel';
|
||||||
|
static const String _waterReminderChannelName = 'Water Reminders';
|
||||||
|
static const String _waterReminderChannelDescription = 'Daily water intake reminders';
|
||||||
|
|
||||||
|
static const String _generalChannelId = 'hmg_general_channel';
|
||||||
|
static const String _generalChannelName = 'HMG Notifications';
|
||||||
|
static const String _generalChannelDescription = 'General notifications from HMG';
|
||||||
|
|
||||||
|
Function(String payload)? _onNotificationClick;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> initialize({Function(String payload)? onNotificationClick}) async {
|
||||||
|
try {
|
||||||
|
// Initialize timezone database
|
||||||
|
tz.initializeTimeZones();
|
||||||
|
|
||||||
|
// Set local timezone (you can also use a specific timezone if needed)
|
||||||
|
// For example: tz.setLocalLocation(tz.getLocation('Asia/Riyadh'));
|
||||||
|
final locationName = DateTime.now().timeZoneName;
|
||||||
|
try {
|
||||||
|
tz.setLocalLocation(tz.getLocation(locationName));
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to UTC if specific timezone not found
|
||||||
|
loggerService.logInfo('Could not set timezone $locationName, using UTC');
|
||||||
|
tz.setLocalLocation(tz.getLocation('UTC'));
|
||||||
|
}
|
||||||
|
|
||||||
|
_onNotificationClick = onNotificationClick;
|
||||||
|
|
||||||
|
const androidSettings = AndroidInitializationSettings('app_icon');
|
||||||
|
const iosSettings = DarwinInitializationSettings(
|
||||||
|
requestAlertPermission: true,
|
||||||
|
requestBadgePermission: true,
|
||||||
|
requestSoundPermission: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
const initializationSettings = InitializationSettings(
|
||||||
|
android: androidSettings,
|
||||||
|
iOS: iosSettings,
|
||||||
|
);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.initialize(
|
||||||
|
initializationSettings,
|
||||||
|
onDidReceiveNotificationResponse: _handleNotificationResponse,
|
||||||
|
);
|
||||||
|
|
||||||
|
loggerService.logInfo('NotificationService initialized successfully');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Failed to initialize NotificationService: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handle notification tap
|
||||||
|
void _handleNotificationResponse(NotificationResponse response) {
|
||||||
|
try {
|
||||||
|
if (response.payload != null && _onNotificationClick != null) {
|
||||||
|
_onNotificationClick!(response.payload!);
|
||||||
|
}
|
||||||
|
loggerService.logInfo('Notification tapped: ${response.payload}');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error handling notification response: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> requestPermissions() async {
|
||||||
|
try {
|
||||||
|
// Request permissions for iOS
|
||||||
|
final result =
|
||||||
|
await flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()?.requestPermissions(
|
||||||
|
alert: true,
|
||||||
|
badge: true,
|
||||||
|
sound: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// For Android 13+, permissions are requested at runtime
|
||||||
|
final androidResult = await flutterLocalNotificationsPlugin
|
||||||
|
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
|
||||||
|
?.requestNotificationsPermission();
|
||||||
|
|
||||||
|
loggerService.logInfo('Notification permissions: iOS=${result ?? true}, Android=${androidResult ?? true}');
|
||||||
|
return result ?? androidResult ?? true;
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error requesting notification permissions: $ex');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> showNotification({
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final androidDetails = AndroidNotificationDetails(
|
||||||
|
_generalChannelId,
|
||||||
|
_generalChannelName,
|
||||||
|
channelDescription: _generalChannelDescription,
|
||||||
|
importance: Importance.high,
|
||||||
|
priority: Priority.high,
|
||||||
|
vibrationPattern: _getVibrationPattern(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const iosDetails = DarwinNotificationDetails();
|
||||||
|
|
||||||
|
final notificationDetails = NotificationDetails(
|
||||||
|
android: androidDetails,
|
||||||
|
iOS: iosDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.show(
|
||||||
|
DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
notificationDetails,
|
||||||
|
payload: payload,
|
||||||
|
);
|
||||||
|
|
||||||
|
loggerService.logInfo('Notification shown: $title');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error showing notification: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> scheduleNotification({
|
||||||
|
required int id,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
required DateTime scheduledDate,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final androidDetails = AndroidNotificationDetails(
|
||||||
|
_generalChannelId,
|
||||||
|
_generalChannelName,
|
||||||
|
channelDescription: _generalChannelDescription,
|
||||||
|
importance: Importance.high,
|
||||||
|
priority: Priority.high,
|
||||||
|
vibrationPattern: _getVibrationPattern(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const iosDetails = DarwinNotificationDetails();
|
||||||
|
|
||||||
|
final notificationDetails = NotificationDetails(
|
||||||
|
android: androidDetails,
|
||||||
|
iOS: iosDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
tz.TZDateTime.from(scheduledDate, tz.local),
|
||||||
|
notificationDetails,
|
||||||
|
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||||
|
payload: payload,
|
||||||
|
);
|
||||||
|
|
||||||
|
loggerService.logInfo('Notification scheduled for: $scheduledDate');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error scheduling notification: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> scheduleDailyNotifications({
|
||||||
|
required List<DateTime> times,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < times.length; i++) {
|
||||||
|
final time = times[i];
|
||||||
|
await scheduleNotification(
|
||||||
|
id: i + 1000,
|
||||||
|
// Offset ID to avoid conflicts
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
scheduledDate: time,
|
||||||
|
payload: payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loggerService.logInfo('Scheduled ${times.length} daily notifications');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error scheduling daily notifications: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> scheduleWaterReminders({
|
||||||
|
required List<DateTime> reminderTimes,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
// Cancel existing water reminders first
|
||||||
|
await _cancelWaterReminders();
|
||||||
|
|
||||||
|
final androidDetails = AndroidNotificationDetails(
|
||||||
|
_waterReminderChannelId,
|
||||||
|
_waterReminderChannelName,
|
||||||
|
channelDescription: _waterReminderChannelDescription,
|
||||||
|
importance: Importance.high,
|
||||||
|
priority: Priority.high,
|
||||||
|
vibrationPattern: _getVibrationPattern(),
|
||||||
|
icon: 'app_icon',
|
||||||
|
styleInformation: const BigTextStyleInformation(''),
|
||||||
|
);
|
||||||
|
|
||||||
|
const iosDetails = DarwinNotificationDetails(
|
||||||
|
presentAlert: true,
|
||||||
|
presentBadge: true,
|
||||||
|
presentSound: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final notificationDetails = NotificationDetails(
|
||||||
|
android: androidDetails,
|
||||||
|
iOS: iosDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int i = 0; i < reminderTimes.length; i++) {
|
||||||
|
final reminderTime = reminderTimes[i];
|
||||||
|
final notificationId = 5000 + i; // Use 5000+ range for water reminders
|
||||||
|
|
||||||
|
// Schedule for today if time hasn't passed, otherwise schedule for tomorrow
|
||||||
|
DateTime scheduledDate = reminderTime;
|
||||||
|
if (scheduledDate.isBefore(DateTime.now())) {
|
||||||
|
scheduledDate = scheduledDate.add(const Duration(days: 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||||
|
notificationId,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
tz.TZDateTime.from(scheduledDate, tz.local),
|
||||||
|
notificationDetails,
|
||||||
|
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||||
|
matchDateTimeComponents: DateTimeComponents.time, // Repeat daily at the same time
|
||||||
|
payload: 'water_reminder_$i',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loggerService.logInfo('Scheduled ${reminderTimes.length} water reminders');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error scheduling water reminders: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cancel all water reminders (IDs 5000-5999)
|
||||||
|
Future<void> _cancelWaterReminders() async {
|
||||||
|
try {
|
||||||
|
final pendingNotifications = await getPendingNotifications();
|
||||||
|
for (final notification in pendingNotifications) {
|
||||||
|
if (notification.id >= 5000 && notification.id < 6000) {
|
||||||
|
await cancelNotification(notification.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loggerService.logInfo('Cancelled all water reminders');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error cancelling water reminders: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> cancelNotification(int id) async {
|
||||||
|
try {
|
||||||
|
await flutterLocalNotificationsPlugin.cancel(id);
|
||||||
|
loggerService.logInfo('Cancelled notification with ID: $id');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error cancelling notification: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> cancelAllNotifications() async {
|
||||||
|
try {
|
||||||
|
await flutterLocalNotificationsPlugin.cancelAll();
|
||||||
|
loggerService.logInfo('Cancelled all notifications');
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error cancelling all notifications: $ex');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<PendingNotificationRequest>> getPendingNotifications() async {
|
||||||
|
try {
|
||||||
|
final pending = await flutterLocalNotificationsPlugin.pendingNotificationRequests();
|
||||||
|
loggerService.logInfo('Found ${pending.length} pending notifications');
|
||||||
|
return pending;
|
||||||
|
} catch (ex) {
|
||||||
|
loggerService.logError('Error getting pending notifications: $ex');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get vibration pattern for notifications
|
||||||
|
Int64List _getVibrationPattern() {
|
||||||
|
final vibrationPattern = Int64List(4);
|
||||||
|
vibrationPattern[0] = 0;
|
||||||
|
vibrationPattern[1] = 500;
|
||||||
|
vibrationPattern[2] = 500;
|
||||||
|
vibrationPattern[3] = 500;
|
||||||
|
return vibrationPattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue