diff --git a/android/app/build.gradle b/android/app/build.gradle
index fdd0f6e..6faf358 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -47,12 +47,12 @@ android {
}
defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "cz.hernikplays.opencanteen"
minSdkVersion 18
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
+ multiDexEnabled true
}
signingConfigs {
@@ -76,4 +76,6 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'com.android.support:multidex:1.0.3'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
}
diff --git a/android/app/src/main/res/drawable/notif_icon.xml b/android/app/src/main/res/drawable/notif_icon.xml
new file mode 100644
index 0000000..ad2d7ef
--- /dev/null
+++ b/android/app/src/main/res/drawable/notif_icon.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/lib/main.dart b/lib/main.dart
index 6ac8052..eb561fb 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,11 +1,11 @@
import 'dart:convert';
import 'dart:io';
-import 'package:background_fetch/background_fetch.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:opencanteen/lang/lang_cz.dart';
import 'package:opencanteen/loginmanager.dart';
@@ -16,6 +16,8 @@ import 'package:opencanteen/util.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';
+import 'package:timezone/data/latest_all.dart' as tz;
+import 'package:timezone/timezone.dart' as tz;
import 'lang/lang.dart';
import 'lang/lang_en.dart';
@@ -41,20 +43,18 @@ Copyright (C) 2022 Matyáš Caras a přispěvatelé
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
-void oznamitPredem(SharedPreferences prefs, {Languages? l}) async {
+void oznamitPredem(SharedPreferences prefs, tz.Location l) async {
String title;
- if (l == null) {
- String locale = Intl.getCurrentLocale();
- switch (locale) {
- case "cs_CZ":
- title = LanguageCz().lunchNotif;
- break;
- default:
- title = LanguageEn().lunchNotif;
- }
- } else {
- title = l.lunchNotif;
+
+ String locale = Intl.getCurrentLocale();
+ switch (locale) {
+ case "cs_CZ":
+ title = LanguageCz().lunchNotif;
+ break;
+ default:
+ title = LanguageEn().lunchNotif;
}
+
if (prefs.getBool("offline") ?? false) {
// TODO možnost brát z offline dat
} else {
@@ -66,21 +66,28 @@ void oznamitPredem(SharedPreferences prefs, {Languages? l}) async {
var jidla = await c.jidelnicekDen();
try {
var jidlo = jidla.jidla.singleWhere((element) => element.objednano);
- var ted = TimeOfDay.now();
- var kdy = prefs.getString("oznameni_cas");
- var cas = TimeOfDay.fromDateTime(DateTime.parse(kdy!));
- if (ted.hour == cas.hour && ted.minute == cas.minute) {
- const AndroidNotificationDetails androidPlatformChannelSpecifics =
+ var kdy = DateTime.parse(prefs.getString("oznameni_cas")!);
+ const AndroidNotificationDetails androidSpec =
AndroidNotificationDetails('opencanteen', 'predobjedem',
channelDescription: 'Oznámení o dnešním jídle',
importance: Importance.max,
priority: Priority.high,
ticker: 'today meal');
- const NotificationDetails platformChannelSpecifics =
- NotificationDetails(android: androidPlatformChannelSpecifics);
- await flutterLocalNotificationsPlugin.show(
- 0, title, "${jidlo.nazev} - ${jidlo.varianta}", platformChannelSpecifics);
- }
+ const IOSNotificationDetails iOSpec =
+ IOSNotificationDetails(presentAlert: true, presentBadge: true);
+ await flutterLocalNotificationsPlugin.zonedSchedule(
+ 0,
+ title,
+ "${jidlo.nazev} - ${jidlo.varianta}",
+ tz.TZDateTime.from(
+ casNaDate(
+ TimeOfDay(hour: kdy.hour, minute: kdy.minute),
+ ),
+ l),
+ const NotificationDetails(android: androidSpec, iOS: iOSpec),
+ androidAllowWhileIdle: true,
+ uiLocalNotificationDateInterpretation:
+ UILocalNotificationDateInterpretation.absoluteTime);
} on StateError catch (_) {
// nenalezeno
}
@@ -89,46 +96,29 @@ void oznamitPredem(SharedPreferences prefs, {Languages? l}) async {
}
}
-// Pouze pro Android
-void backgroundFetchHeadlessTask(HeadlessTask task) async {
- String taskId = task.taskId;
- bool isTimeout = task.timeout;
- if (isTimeout) {
- // Timeout
- debugPrint("[BackgroundFetch] Headless task má time-out: $taskId");
- BackgroundFetch.finish(taskId);
- return;
- }
- var prefs = await SharedPreferences.getInstance();
- debugPrint('[BackgroundFetch] Přišel headless event.');
-
- if (prefs.getBool("oznamit") ?? false) {
- // Oznámení před obědem
- oznamitPredem(prefs);
- }
- BackgroundFetch.finish(taskId);
-}
-
void main() async {
WidgetsFlutterBinding.ensureInitialized();
+ tz.initializeTimeZones();
+ var l = tz.getLocation(await FlutterNativeTimezone.getLocalTimezone());
+ tz.setLocalLocation(l);
+
+ var prefs = await SharedPreferences.getInstance();
+ if (prefs.getBool("oznamit") ?? false) {
+ oznamitPredem(prefs, l);
+ }
+
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('notif_icon');
- /// Note: permissions aren't requested here just to demonstrate that can be
- /// done later
final IOSInitializationSettings initializationSettingsIOS =
- IOSInitializationSettings(
- requestAlertPermission: false,
- requestBadgePermission: false,
- requestSoundPermission: false,
- onDidReceiveLocalNotification: (
- int id,
- String? title,
- String? body,
- String? payload,
- ) async {
- debugPrint(body);
- });
+ IOSInitializationSettings(onDidReceiveLocalNotification: (
+ int id,
+ String? title,
+ String? body,
+ String? payload,
+ ) async {
+ debugPrint(body);
+ });
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
@@ -141,7 +131,6 @@ void main() async {
}
});
runApp(const MyApp());
- BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}
class MyApp extends StatelessWidget {
@@ -182,32 +171,6 @@ class _LoginPageState extends State {
TextEditingController canteenControl = TextEditingController();
bool rememberMe = false;
- void nastavitPozadi() async {
- // Configure BackgroundFetch.
- int status = await BackgroundFetch.configure(
- BackgroundFetchConfig(
- minimumFetchInterval: 15,
- stopOnTerminate: false,
- enableHeadless: true,
- requiresBatteryNotLow: false,
- requiresCharging: false,
- requiresStorageNotLow: false,
- requiresDeviceIdle: false,
- requiredNetworkType: NetworkType.ANY), (String taskId) async {
- // Callback
- debugPrint("[BackgroundFetch] Event získán $taskId");
- var d = await LoginManager.getDetails();
- if (d != null) {
- // TODO
- }
- BackgroundFetch.finish(taskId);
- }, (String taskId) async {
- debugPrint("[BackgroundFetch] TASK TIMEOUT taskId: $taskId");
- BackgroundFetch.finish(taskId);
- });
- debugPrint('[BackgroundFetch] úspěšně nakonfigurováno: $status');
- }
-
@override
void initState() {
super.initState();
@@ -254,19 +217,18 @@ class _LoginPageState extends State {
}
const storage = FlutterSecureStorage();
var odsouhlasil = await storage.read(key: "oc_souhlas");
- nastavitPozadi();
if (odsouhlasil == null || odsouhlasil != "ano") {
Navigator.pushReplacement(
context,
MaterialPageRoute(
- builder: (c) => WelcomeScreen(canteen: canteen)));
+ builder: (c) => WelcomeScreen(
+ canteen: canteen, n: flutterLocalNotificationsPlugin)));
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => JidelnicekPage(
- canteen: canteen,
- )),
+ canteen: canteen, n: flutterLocalNotificationsPlugin)),
);
}
} on PlatformException {
@@ -392,7 +354,6 @@ class _LoginPageState extends State {
LoginManager.setDetails(userControl.text,
passControl.text, canteenControl.text);
}
- nastavitPozadi();
// souhlas
const storage = FlutterSecureStorage();
var odsouhlasil =
@@ -401,15 +362,16 @@ class _LoginPageState extends State {
Navigator.pushReplacement(
context,
MaterialPageRoute(
- builder: (c) =>
- WelcomeScreen(canteen: canteen)));
+ builder: (c) => WelcomeScreen(
+ canteen: canteen,
+ n: flutterLocalNotificationsPlugin)));
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => JidelnicekPage(
- canteen: canteen,
- )),
+ canteen: canteen,
+ n: flutterLocalNotificationsPlugin)),
);
}
} on PlatformException {
diff --git a/lib/okna/burza.dart b/lib/okna/burza.dart
index f145532..fce72a3 100644
--- a/lib/okna/burza.dart
+++ b/lib/okna/burza.dart
@@ -1,13 +1,16 @@
import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:opencanteen/util.dart';
import '../lang/lang.dart';
import '../main.dart';
class BurzaPage extends StatefulWidget {
- const BurzaPage({Key? key, required this.canteen}) : super(key: key);
+ const BurzaPage({Key? key, required this.canteen, required this.n})
+ : super(key: key);
final Canteen canteen;
+ final FlutterLocalNotificationsPlugin n;
@override
State createState() => _BurzaPageState();
}
@@ -112,7 +115,7 @@ class _BurzaPageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
- drawer: drawerGenerator(context, widget.canteen, 3),
+ drawer: drawerGenerator(context, widget.canteen, 3, widget.n),
appBar: AppBar(
title: Text(Languages.of(context)!.exchange),
),
diff --git a/lib/okna/jidelnicek.dart b/lib/okna/jidelnicek.dart
index d9f7524..e1e74b9 100644
--- a/lib/okna/jidelnicek.dart
+++ b/lib/okna/jidelnicek.dart
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:opencanteen/okna/nastaveni.dart';
import 'package:opencanteen/util.dart';
@@ -15,8 +16,10 @@ import '../main.dart';
import 'about.dart';
class JidelnicekPage extends StatefulWidget {
- const JidelnicekPage({Key? key, required this.canteen}) : super(key: key);
+ const JidelnicekPage({Key? key, required this.canteen, required this.n})
+ : super(key: key);
final Canteen canteen;
+ final FlutterLocalNotificationsPlugin n;
@override
State createState() => _JidelnicekPageState();
}
@@ -301,7 +304,8 @@ class _JidelnicekPageState extends State {
});
}
- void kliknuti(String value, BuildContext context) {
+ void kliknuti(
+ String value, BuildContext context, FlutterLocalNotificationsPlugin n) {
if (value == Languages.of(context)!.signOut) {
const storage = FlutterSecureStorage();
storage.deleteAll();
@@ -314,7 +318,7 @@ class _JidelnicekPageState extends State {
context, MaterialPageRoute(builder: (c) => const AboutPage()));
} else if (value == Languages.of(context)!.settings) {
Navigator.push(
- context, MaterialPageRoute(builder: (c) => const Nastaveni()));
+ context, MaterialPageRoute(builder: (c) => Nastaveni(n: n)));
}
}
@@ -365,12 +369,12 @@ class _JidelnicekPageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
- drawer: drawerGenerator(context, widget.canteen, 1),
+ drawer: drawerGenerator(context, widget.canteen, 1, widget.n),
appBar: AppBar(
title: Text(Languages.of(context)!.menu),
actions: [
PopupMenuButton(
- onSelected: ((String value) => kliknuti(value, context)),
+ onSelected: ((String value) => kliknuti(value, context, widget.n)),
itemBuilder: (BuildContext context) {
return {
Languages.of(context)!.reportBugs,
diff --git a/lib/okna/nastaveni.dart b/lib/okna/nastaveni.dart
index d65ac6a..ea81d45 100644
--- a/lib/okna/nastaveni.dart
+++ b/lib/okna/nastaveni.dart
@@ -1,13 +1,22 @@
import 'dart:io';
+import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
+import 'package:timezone/data/latest_all.dart' as tz;
+import 'package:timezone/timezone.dart' as tz;
import '../lang/lang.dart';
+import '../loginmanager.dart';
+import '../util.dart';
class Nastaveni extends StatefulWidget {
- const Nastaveni({Key? key}) : super(key: key);
+ const Nastaveni({Key? key, required this.n}) : super(key: key);
+
+ final FlutterLocalNotificationsPlugin n;
@override
State createState() => _NastaveniState();
@@ -18,7 +27,7 @@ class _NastaveniState extends State {
bool _preskakovatVikend = false;
bool _kontrolovatTyden = false;
bool _oznameniObed = false;
- String? _oznameniCas;
+ TimeOfDay _oznameniCas = TimeOfDay.now();
void najitNastaveni() async {
var preferences = await SharedPreferences.getInstance();
@@ -27,7 +36,14 @@ class _NastaveniState extends State {
_preskakovatVikend = preferences.getBool("skip") ?? false;
_kontrolovatTyden = preferences.getBool("tyden") ?? false;
_oznameniObed = preferences.getBool("oznamit") ?? false;
- _oznameniCas = preferences.getString("oznameni_cas");
+ var _casStr = preferences.getString("oznameni_cas");
+ if (_casStr == null) {
+ var now = DateTime.now();
+ _oznameniCas = TimeOfDay.fromDateTime(DateTime.now());
+ preferences.setString("oznameni_cas", now.toString());
+ } else {
+ _oznameniCas = TimeOfDay.fromDateTime(DateTime.parse(_casStr));
+ }
});
}
@@ -99,23 +115,82 @@ class _NastaveniState extends State {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- Text(Languages.of(context)!.skipWeekend),
+ Flexible(child: Text(Languages.of(context)!.notifyLunch)),
Switch(
value: _oznameniObed,
onChanged: (value) {
setState(() {
_oznameniObed = value;
- zmenitNastaveni("oznamit", value);
+ zmenitNastaveni("oznamit",
+ value); // TODO: změnit pouze s uloženými údaji
});
})
],
),
Text(Languages.of(context)!.notifyAt),
- TimePickerDialog(
- initialTime: (_oznameniCas == null)
- ? TimeOfDay.now()
- : TimeOfDay.fromDateTime(DateTime.parse(_oznameniCas!)),
- )
+ TextButton(
+ style: ButtonStyle(
+ textStyle: MaterialStateProperty.all(TextStyle(
+ color: (_oznameniObed) ? Colors.grey : Colors.purple))),
+ onPressed: () async {
+ if (_oznameniObed) {
+ var cas = await showTimePicker(
+ context: context, initialTime: _oznameniCas);
+ if (cas != null) {
+ var prefs = await SharedPreferences.getInstance();
+ prefs.setString(
+ "oznameni_cas",
+ casNaDate(cas)
+ .toString()); // aktualizovat vybraný čas
+ var d = await LoginManager.getDetails(); // získat údaje
+ if (d != null) {
+ // Nové oznámení
+ var c = Canteen(d["url"]!);
+ if (await c.login(d["user"]!, d["pass"]!)) {
+ var jidla = await c.jidelnicekDen();
+ try {
+ var jidlo = jidla.jidla
+ .singleWhere((element) => element.objednano);
+
+ const AndroidNotificationDetails androidSpec =
+ AndroidNotificationDetails(
+ 'opencanteen', 'predobjedem',
+ channelDescription:
+ 'Oznámení o dnešním jídle',
+ importance: Importance.max,
+ priority: Priority.high,
+ ticker: 'today meal');
+ const IOSNotificationDetails iOSpec =
+ IOSNotificationDetails(
+ presentAlert: true, presentBadge: true);
+ debugPrint(casNaDate(cas).toString());
+ var l = tz.getLocation(
+ await FlutterNativeTimezone.getLocalTimezone());
+ await widget.n.zonedSchedule(
+ // Vytvoří nové oznámení pro daný čas a datum
+ 0,
+ Languages.of(context)!.lunchNotif,
+ "${jidlo.nazev} - ${jidlo.varianta}",
+ tz.TZDateTime.from(casNaDate(cas), l),
+ const NotificationDetails(
+ android: androidSpec, iOS: iOSpec),
+ androidAllowWhileIdle: true,
+ uiLocalNotificationDateInterpretation:
+ UILocalNotificationDateInterpretation
+ .absoluteTime);
+ } on StateError catch (_) {
+ // nenalezeno
+ }
+ }
+ }
+ }
+ setState(() {
+ _oznameniCas = cas ?? _oznameniCas;
+ });
+ }
+ },
+ child: Text(
+ "${(_oznameniCas.hour < 10 ? "0" : "") + _oznameniCas.hour.toString()}:${(_oznameniCas.minute < 10 ? "0" : "") + _oznameniCas.minute.toString()}")),
],
),
)),
diff --git a/lib/okna/offline_jidelnicek.dart b/lib/okna/offline_jidelnicek.dart
index 69cceb6..973874e 100644
--- a/lib/okna/offline_jidelnicek.dart
+++ b/lib/okna/offline_jidelnicek.dart
@@ -87,9 +87,6 @@ class _OfflineJidelnicekState extends State {
} else if (value == Languages.of(context)!.about) {
Navigator.push(
context, MaterialPageRoute(builder: (c) => const AboutPage()));
- } else if (value == Languages.of(context)!.settings) {
- Navigator.push(
- context, MaterialPageRoute(builder: (c) => const Nastaveni()));
}
}
@@ -111,7 +108,6 @@ class _OfflineJidelnicekState extends State {
itemBuilder: (BuildContext context) {
return {
Languages.of(context)!.reportBugs,
- Languages.of(context)!.settings,
Languages.of(context)!.about,
Languages.of(context)!.signOut
}.map((String choice) {
diff --git a/lib/okna/welcome.dart b/lib/okna/welcome.dart
index 04639b5..36d555c 100644
--- a/lib/okna/welcome.dart
+++ b/lib/okna/welcome.dart
@@ -1,14 +1,17 @@
import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:introduction_screen/introduction_screen.dart';
import 'package:opencanteen/lang/lang.dart';
import 'package:opencanteen/okna/jidelnicek.dart';
class WelcomeScreen extends StatefulWidget {
- const WelcomeScreen({Key? key, required this.canteen}) : super(key: key);
+ const WelcomeScreen({Key? key, required this.canteen, required this.n})
+ : super(key: key);
final Canteen canteen;
+ final FlutterLocalNotificationsPlugin n;
@override
State createState() => _WelcomeScreenState();
@@ -67,7 +70,8 @@ class _WelcomeScreenState extends State {
const storage = FlutterSecureStorage();
await storage.write(key: "oc_souhlas", value: "ano");
Navigator.of(context).pushReplacement(MaterialPageRoute(
- builder: (c) => JidelnicekPage(canteen: widget.canteen)));
+ builder: (c) =>
+ JidelnicekPage(canteen: widget.canteen, n: widget.n)));
},
),
);
diff --git a/lib/util.dart b/lib/util.dart
index 8035ea7..1eccf9f 100644
--- a/lib/util.dart
+++ b/lib/util.dart
@@ -1,11 +1,13 @@
import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:opencanteen/okna/burza.dart';
import 'lang/lang.dart';
import 'okna/jidelnicek.dart';
-Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) {
+Drawer drawerGenerator(BuildContext context, Canteen canteen, int p,
+ FlutterLocalNotificationsPlugin n) {
Drawer drawer = const Drawer();
switch (p) {
case 1:
@@ -28,7 +30,7 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) {
onTap: () => Navigator.push(
context,
MaterialPageRoute(
- builder: (context) => BurzaPage(canteen: canteen),
+ builder: (context) => BurzaPage(canteen: canteen, n: n),
),
),
),
@@ -50,7 +52,7 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) {
onTap: () => Navigator.push(
context,
MaterialPageRoute(
- builder: (c) => JidelnicekPage(canteen: canteen))),
+ builder: (c) => JidelnicekPage(canteen: canteen, n: n))),
),
ListTile(
leading: const Icon(Icons.store),
@@ -81,3 +83,10 @@ class OfflineJidlo {
required this.naBurze,
required this.den});
}
+
+/// Vytvoří [DateTime] z [TimeOfDay]
+DateTime casNaDate(TimeOfDay c) {
+ var now = DateTime.now();
+ return DateTime.parse(
+ "${now.year}-${(now.month < 10 ? "0" : "") + now.month.toString()}-${(now.day < 10 ? "0" : "") + now.day.toString()} ${(c.hour < 10 ? "0" : "") + c.hour.toString()}:${(c.minute < 10 ? "0" : "") + c.minute.toString()}:00");
+}
diff --git a/pubspec.lock b/pubspec.lock
index 6d92ce2..b1df825 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -22,13 +22,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
- background_fetch:
- dependency: "direct main"
- description:
- name: background_fetch
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
canteenlib:
dependency: "direct main"
description:
@@ -144,6 +137,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_native_timezone:
+ dependency: "direct main"
+ description:
+ name: flutter_native_timezone
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
flutter_secure_storage:
dependency: "direct main"
description:
@@ -421,7 +421,7 @@ packages:
source: hosted
version: "1.2.0"
timezone:
- dependency: transitive
+ dependency: "direct main"
description:
name: timezone
url: "https://pub.dartlang.org"
diff --git a/pubspec.yaml b/pubspec.yaml
index a5fbbf7..816a923 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -23,7 +23,8 @@ dependencies:
shared_preferences: ^2.0.13
introduction_screen: ^3.0.1
flutter_local_notifications: ^9.5.3+1
- background_fetch: ^1.1.0
+ timezone: ^0.8.0
+ flutter_native_timezone: ^2.0.0
dev_dependencies:
flutter_lints: ^1.0.0