From cd3398df992040cbba0986f4ce63997d02cba5b3 Mon Sep 17 00:00:00 2001 From: bl3kunja Date: Sat, 30 May 2026 18:00:30 +0200 Subject: [PATCH] Notifications added to the app -> updated version to V1.1.0 --- android/app/build.gradle.kts | 9 +- android/app/proguard-rules.pro | 10 + android/app/src/main/AndroidManifest.xml | 10 + .../res/drawable-hdpi/ic_stat_icon_white.png | Bin 0 -> 1819 bytes .../res/drawable-mdpi/ic_stat_icon_white.png | Bin 0 -> 1130 bytes .../res/drawable-xhdpi/ic_stat_icon_white.png | Bin 0 -> 2794 bytes .../drawable-xxhdpi/ic_stat_icon_white.png | Bin 0 -> 3995 bytes .../drawable-xxxhdpi/ic_stat_icon_white.png | Bin 0 -> 5805 bytes .../main/res/drawable/ic_stat_icon_white.xml | 42 ++++ android/settings.gradle.kts | 2 +- lib/main.dart | 4 + lib/screens/calendar_screen.dart | 2 + lib/screens/home_screen.dart | 2 +- lib/screens/settings_screen.dart | 209 ++++++++++++++++++ lib/services/notification_service.dart | 169 ++++++++++++++ lib/utils/translations.dart | 50 +++++ pubspec.lock | 58 ++++- pubspec.yaml | 5 +- 18 files changed, 559 insertions(+), 13 deletions(-) create mode 100644 android/app/src/main/res/drawable-hdpi/ic_stat_icon_white.png create mode 100644 android/app/src/main/res/drawable-mdpi/ic_stat_icon_white.png create mode 100644 android/app/src/main/res/drawable-xhdpi/ic_stat_icon_white.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/ic_stat_icon_white.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/ic_stat_icon_white.png create mode 100644 android/app/src/main/res/drawable/ic_stat_icon_white.xml create mode 100644 lib/services/notification_service.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 1d878aa..9fed0ee 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -14,6 +14,7 @@ android { compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 + isCoreLibraryDesugaringEnabled = true } kotlinOptions { @@ -22,7 +23,7 @@ android { defaultConfig { applicationId = "com.example.shooter_app" - minSdk = 26 // ✅ From earlier fix + minSdk = 26 targetSdk = 33 versionCode = flutter.versionCode versionName = flutter.versionName @@ -31,12 +32,18 @@ android { buildTypes { release { signingConfig = signingConfigs.getByName("debug") + isMinifyEnabled = false + isShrinkResources = false } } } +dependencies { + coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") +} + flutter { source = "../.." } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 494833b..86e922f 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -16,3 +16,13 @@ # Keep your YOLO model loader class if needed -keep class your.package.name.YOLODetect { *; } + +# Flutter Local Notifications +-keep class com.dexterous.** { *; } +-dontwarn com.dexterous.** + +# Timezone +-keep class com.jakewharton.threetenabp.** { *; } +-dontwarn com.jakewharton.threetenabp.** +-keep class org.threeten.** { *; } +-dontwarn org.threeten.** diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ef27da9..c3fadf5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + + + + + + + + + + JZEN}nKNhZJ$EZiN6?T_w=?A` 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xhdpi/ic_stat_icon_white.png b/android/app/src/main/res/drawable-xhdpi/ic_stat_icon_white.png new file mode 100644 index 0000000000000000000000000000000000000000..aa9036ada163bfef9fe1b2692a1b4aa40a912ffb GIT binary patch literal 2794 zcmVwnempS}Y`@Mu|pks}VI43sS5J zf`&E<3PmUaR)aV27Ew^De$c2@tXfpCafg7p56?_4_6TBYm3u@X6<~Xo|h(5)jY=-{^?T!UA`@IEJ(HcV|0tiz! zV-(f#gHZYmP{&_QJ`cV0ylt-1o0w(oKi!vo_>vFGE=VsuPHk{ zx(kCCXEHQPXmSl&?;_WHCu^y+)JoBA3pK|yih48GBX|!%%Q1fGkm`$uW6fExX#Re3;qv=NpEiz!t)uR2?hvudQ=23szK@| z(`E)5rvfd|Co_*>ofP>!B%3Img8aY`!W-zsFDTb^cL>!v=*%aumms_XC{O}U_9;(* zW#B4∾mh)aW1y7D#KV%7P0g38FD7lD>ud&x1@786B|iAy`i7L^^(T2vr(kdW*cE zXjKR?RN*#+rvdF)myHIz9PK@YtVRV??MScox70B=5Yf>;;KT8p4%Up|p zZJVf?VM}jR^|xF1`5?CU>5AAU3c-i~+^(jNZj#3q`Vq$({h!n_96Sg9``~gqvI_hK zpoS!o^C46b`F5}l@d`ZX3|UTlzaJt0BXYe71q18!B+^A-BvlydsdS%`Ye!ICV;A~g z#F@WpoX=xh`~;P2k$sfBB<(JG6RPK}Wt$j4_gg6H%Ao@>eJdbGrn6!@c}d6PXI3jn zigmflX6tg!1O0OLC{Nqu&#wT{ky7HDcEjqH%@n+Sdk zw4M6AK=1FAuL4f!D1R2o0m&+yWvN&&VhUj|#=WW6okcC8MQ^zoCp)dRrZ=SB=)dS% zl1tq%0BF*xs@$RqLEG-lNL~&yx%QVR$Vj9VF`@Ohp9IJ!Q6~UiRRCPn29obY`x3Ac ztjbv9^@E5`l#UK1odsGi&1*xqp+3jha6c&h3|vat?ZxPRw;+?xgkTp^-WFr>8RLMs zkYrLPoQ;e)&aS0!XNE>r3vCJV#TINa@@qg{ul&Q5>o#{_+T|<*`dtsOMk!y6mtNC#NxakTmjYy)M{!RO|EM}jcuw+sI>EFByJC(@;1oJ8z@b2MOqR% zIwvk6S9=|PI_WQ9*TP06*;mkiGpK!@0DqhG6DstA4o1Y518{ayqWzc-hK0nZV_i72 z)rt(DX?tA4oYZ~EELi(EPs>J9@|G68nSQ$%9V;tefngn>^0yHF08HpiArqCVp$x!1 zTK%xi?SLOX+MiEnpMl(o7C(YH3>*x)gr67(2gexovCmx}?XJdb3w9DBeMgWrbT;y) z0I$*B+6qdpo~Fzl8Hw>wzZWP6KMMzRc5W1!M_HSJ(SJwA}dHeYl|s>wu~ngvPrRK8))K=Qqkrx zLLY$FfkbA;DE`OHbea1Yx&AU-N&ZSulcv{t?5sra9dH(FOw$k6^vTNTn|^%hd@yU> zFUR&So8n0UruDN${goVEdIn(0!fk9|~}G-WP>)2U20noeH96dwWFa0tv&k; zI-wCn#Z3BOn?=)GF@iUq2STW}GU{406xo8K?rNq^%f=B1ow5hL@r$veFFEAd3Y=NV zo239uPf*ee4E=)&rcL-AxzRb#NDc(L?_Wz0>I}t9qs_4%;sjcs2I~Av$%jezqhbrs zVYybxTMi(ormP4l&LDXL3r?Na58FGa)LTgvX8Qf{S~I(uAg%{T0i6}Ikkzs3@AN$6 zIu}?gQQsB%Xx9J~gJ{}Tk7K}Y0HqI^>P2eoeJ^^8TNinf+rfuW-Azfi!*7!#A#G_k ztmeNK=`Ha7DVnWnac)Gm1=HMML*J!O(`Fg^x?Z3Nn(Eb_fsS^Za$zHK zlFg^XmH$ljn`B>PnmS~qHy?*h1h$llO^mz?;HO1$R$3d~C3S6Jnmvi@d(u<+M2NDloVka`Fonu-a9hKPris37-$zCHpOoSlDovMsPqUE$wC&1x zOADjqNr!p<5ZDOT1O0K!6`d>W5x3my!>c@N4RVnTFRJY1y wW#)6{Iu8Q?0RR7YK>g|f000I_L_t&o05<<{9 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_stat_icon_white.png b/android/app/src/main/res/drawable-xxhdpi/ic_stat_icon_white.png new file mode 100644 index 0000000000000000000000000000000000000000..7e5acf62870a6b2ca3a9d68d0e6ae0cf8a37e9db GIT binary patch literal 3995 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_stat_icon_white.png b/android/app/src/main/res/drawable-xxxhdpi/ic_stat_icon_white.png new file mode 100644 index 0000000000000000000000000000000000000000..7b9169326dd73c2d6c8205ed7953f2680f222a72 GIT binary patch literal 5805 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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable/ic_stat_icon_white.xml b/android/app/src/main/res/drawable/ic_stat_icon_white.xml new file mode 100644 index 0000000..844f5dc --- /dev/null +++ b/android/app/src/main/res/drawable/ic_stat_icon_white.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index a439442..11662c3 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,7 +19,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.0" apply false - id("org.jetbrains.kotlin.android") version "1.8.22" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false } include(":app") diff --git a/lib/main.dart b/lib/main.dart index 6b7c050..2893833 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; // Import for SystemChrome import 'screens/home_screen.dart'; +import 'services/notification_service.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + try { + await NotificationService().initialize(); + } catch (_) {} // Lock orientation to portrait only await SystemChrome.setPreferredOrientations([ diff --git a/lib/screens/calendar_screen.dart b/lib/screens/calendar_screen.dart index f09af2c..e25db66 100644 --- a/lib/screens/calendar_screen.dart +++ b/lib/screens/calendar_screen.dart @@ -5,6 +5,7 @@ import 'package:intl/date_symbol_data_local.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:url_launcher/url_launcher.dart'; import '../services/google_calendar_service.dart'; +import '../services/notification_service.dart'; import '../utils/translations.dart'; import '../utils/target_colors.dart'; @@ -234,6 +235,7 @@ class _CalendarScreenState extends State { _events = filteredEvents; }); } + NotificationService().scheduleEventNotifications(filteredEvents); } catch (e) { if (mounted) { setState(() { diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index d512163..c6ba113 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -133,7 +133,7 @@ class _HomeScreenState extends State { padding: const EdgeInsets.only(right: 16.0), child: Center( child: Text( - 'V1.0.3', + 'V1.1', style: TextStyle( color: Colors.grey[400], fontSize: 12, diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index bf00e2b..e9995ff 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../utils/translations.dart'; import '../utils/target_colors.dart'; +import '../services/notification_service.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -43,6 +44,13 @@ class _SettingsScreenState extends State { 'Grey', ]; + int _notificationHours = 1; + final List _notificationHourOptions = [1, 2, 3, 6, 12, 24, 48]; + + bool _notifyDezuranje = true; + bool _notifySestanek = true; + bool _notifyTekma = true; + String _selectedLanguage = 'English'; final List> _languages = [ {'code': 'en', 'name': 'English', 'flag': '🇬🇧'}, @@ -96,6 +104,7 @@ class _SettingsScreenState extends State { _loadBulletColor(); _loadLanguage(); _loadSelectedTarget(); + _loadNotificationSettings(); } @@ -205,6 +214,28 @@ String _getTargetDisplayName(String targetPath) { return _t(targetName); } + Future _loadNotificationSettings() async { + final prefs = await SharedPreferences.getInstance(); + setState(() { + _notificationHours = prefs.getInt('notificationHours_single') ?? 1; + _notifyDezuranje = prefs.getBool('notifyDezuranje') ?? true; + _notifySestanek = prefs.getBool('notifySestanek') ?? true; + _notifyTekma = prefs.getBool('notifyTekma') ?? true; + }); + } + + Future _saveNotificationHours(int hours) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt('notificationHours_single', hours); + await prefs.setStringList('notificationHours', [hours.toString()]); + setState(() => _notificationHours = hours); + } + + Future _saveNotifyType(String key, bool value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(key, value); + } + Future _loadCameraIPs() async { final prefs = await SharedPreferences.getInstance(); @@ -388,6 +419,8 @@ String _getTargetDisplayName(String targetPath) { _buildAppearanceSelector(), const SizedBox(height: 16), + _buildNotificationSettings(), + const SizedBox(height: 16), _buildCameraList(), ], ), @@ -1096,4 +1129,180 @@ void _showLanguagePopup() { ); } + Widget _buildNotifyToggle(String label, bool value, ValueChanged onChanged) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(label, style: const TextStyle(color: Colors.black87, fontSize: 14)), + Switch( + value: value, + onChanged: onChanged, + activeThumbColor: _getColorFromName(_selectedBulletColor), + ), + ], + ), + ); + } + + Widget _buildNotificationSettings() { + return Container( + margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 0), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + decoration: BoxDecoration( + color: Colors.grey[900], + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + ), + child: Row( + children: [ + Container( + width: 4, + height: 16, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(2), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + _t('Notifications'), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + GestureDetector( + onTap: () async { + await NotificationService().scheduleTestNotification(); + }, + child: const Icon(Icons.notifications_active_outlined, color: Colors.white, size: 20), + ), + ], + ), + ), + // Hours before picker + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(12), + bottomRight: Radius.circular(12), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text( + _t('Event types'), + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + _buildNotifyToggle(_t('Dežuranje'), _notifyDezuranje, (v) { + setState(() => _notifyDezuranje = v); + _saveNotifyType('notifyDezuranje', v); + }), + _buildNotifyToggle(_t('Sestanek'), _notifySestanek, (v) { + setState(() => _notifySestanek = v); + _saveNotifyType('notifySestanek', v); + }), + _buildNotifyToggle(_t('Tekma'), _notifyTekma, (v) { + setState(() => _notifyTekma = v); + _saveNotifyType('notifyTekma', v); + }), + const SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _t('Notify before event'), + style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.black87), + ), + Text( + _notificationHours == 1 + ? '1 ${_t('hour')}' + : '$_notificationHours ${_t('hours')}', + style: TextStyle( + color: _getColorFromName(_selectedBulletColor), + fontWeight: FontWeight.bold, + fontSize: 13, + ), + ), + ], + ), + const SizedBox(height: 12), + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Row( + children: _notificationHourOptions.asMap().entries.map((entry) { + final i = entry.key; + final hours = entry.value; + final isSelected = hours == _notificationHours; + final label = '${hours}h'; + final isFirst = i == 0; + final isLast = i == _notificationHourOptions.length - 1; + return Expanded( + child: GestureDetector( + onTap: () => _saveNotificationHours(hours), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 10), + decoration: BoxDecoration( + color: isSelected + ? _getColorFromName(_selectedBulletColor) + : Colors.grey[700], + border: Border( + right: isLast + ? BorderSide.none + : BorderSide(color: Colors.grey[600]!, width: 1), + ), + borderRadius: isFirst + ? const BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)) + : isLast + ? const BorderRadius.only(topRight: Radius.circular(10), bottomRight: Radius.circular(10)) + : BorderRadius.zero, + ), + child: Text( + label, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + ), + ), + ); + }).toList(), + ), + ), + ], + ), + ), + ], + ), + ); + } } diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart new file mode 100644 index 0000000..442ddc1 --- /dev/null +++ b/lib/services/notification_service.dart @@ -0,0 +1,169 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest.dart' as tz; +import 'package:flutter_timezone/flutter_timezone.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../services/google_calendar_service.dart'; + +class NotificationService { + static final NotificationService _instance = NotificationService._internal(); + factory NotificationService() => _instance; + NotificationService._internal(); + + final FlutterLocalNotificationsPlugin _plugin = FlutterLocalNotificationsPlugin(); + bool _initialized = false; + + static const _channel = AndroidNotificationChannel( + 'shooting_app_events', + 'Event Reminders', + description: 'Notifications for upcoming shooting events', + importance: Importance.max, + playSound: true, + enableVibration: true, + ); + + NotificationDetails get _notifDetails => const NotificationDetails( + android: AndroidNotificationDetails( + 'shooting_app_events', + 'Event Reminders', + channelDescription: 'Notifications for upcoming shooting events', + importance: Importance.max, + priority: Priority.high, + icon: '@drawable/ic_stat_icon_white', + ), + iOS: DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + ), + ); + + Future initialize() async { + if (_initialized) return; + _initialized = true; // set early to prevent re-entry + + try { + tz.initializeTimeZones(); + final String timezoneName = await FlutterTimezone.getLocalTimezone(); + tz.setLocalLocation(tz.getLocation(timezoneName)); + } catch (_) {} + + try { + const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher'); + const iosSettings = DarwinInitializationSettings( + requestAlertPermission: true, + requestBadgePermission: true, + requestSoundPermission: true, + ); + const initSettings = InitializationSettings(android: androidSettings, iOS: iosSettings); + await _plugin.initialize(initSettings); + + final androidPlugin = _plugin.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>(); + await androidPlugin?.createNotificationChannel(_channel); + await requestPermissions(); + } catch (_) {} + } + + Future requestPermissions() async { + final android = _plugin.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>(); + final ios = _plugin.resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>(); + + bool granted = false; + if (android != null) { + granted = await android.requestNotificationsPermission() ?? false; + } + if (ios != null) { + granted = await ios.requestPermissions(alert: true, badge: true, sound: true) ?? false; + } + return granted; + } + + Future cancelAll() async { + await _plugin.cancelAll(); + } + + Future scheduleEventNotifications(List events) async { + try { + await initialize(); + + final prefs = await SharedPreferences.getInstance(); + final hours = prefs.getInt('notificationHours_single') ?? 1; + final notifyDezuranje = prefs.getBool('notifyDezuranje') ?? true; + final notifySestanek = prefs.getBool('notifySestanek') ?? true; + final notifyTekma = prefs.getBool('notifyTekma') ?? true; + final isSlovenian = (prefs.getString('language') ?? 'English') == 'Slovenian'; + final now = DateTime.now(); + + await _plugin.cancelAll(); + + int notifId = 0; + for (final event in events) { + if (event.startTime == null) continue; + + final summary = (event.summary ?? '').toLowerCase(); + final isCompetition = summary.contains('liga') || summary.contains('tekmovanje'); + final isSestanek = summary.contains('sestanek'); + final isDezuranje = !isCompetition && !isSestanek; + + if (isCompetition && !notifyTekma) continue; + if (isSestanek && !notifySestanek) continue; + if (isDezuranje && !notifyDezuranje) continue; + + final notifyAt = event.startTime!.subtract(Duration(hours: hours)); + if (notifyAt.isAfter(now)) { + await _scheduleNotification( + id: notifId++, + title: event.summary ?? 'Event', + body: isSlovenian + ? (hours == 1 ? 'Začne se čez 1 uro' : 'Začne se čez $hours ur') + : (hours == 1 ? 'Starts in 1 hour' : 'Starts in $hours hours'), + scheduledDate: notifyAt, + ); + } + } + } catch (_) {} + } + + Future scheduleTestNotification() async { + try { + await initialize(); + final prefs = await SharedPreferences.getInstance(); + final isSlovenian = (prefs.getString('language') ?? 'English') == 'Slovenian'; + await _plugin.show( + 9999, + isSlovenian ? 'Testno obvestilo' : 'Test Notification', + isSlovenian ? 'Obvestila delujejo!' : 'Notifications are working!', + _notifDetails, + ); + final in10s = DateTime.now().add(const Duration(seconds: 10)); + await _scheduleNotification( + id: 9998, + title: isSlovenian ? 'Načrtovano testno obvestilo' : 'Scheduled Test', + body: isSlovenian ? 'Načrtovana obvestila delujejo!' : 'Scheduled notifications are working!', + scheduledDate: in10s, + ); + } catch (_) {} + } + + Future _scheduleNotification({ + required int id, + required String title, + required String body, + required DateTime scheduledDate, + }) async { + final tzDate = tz.TZDateTime.from(scheduledDate, tz.local); + await _plugin.zonedSchedule( + id, + title, + body, + tzDate, + _notifDetails, + androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + ); + } +} diff --git a/lib/utils/translations.dart b/lib/utils/translations.dart index 6664f9f..111aed2 100644 --- a/lib/utils/translations.dart +++ b/lib/utils/translations.dart @@ -846,6 +846,56 @@ class AppTranslations { 'English': 'Points', 'Slovenian': 'Točke', }, + + // Notifications + 'Notifications': { + 'English': 'Notifications', + 'Slovenian': 'Obvestila', + }, + 'Event Reminders': { + 'English': 'Event Reminders', + 'Slovenian': 'Opomniki za dogodke', + }, + 'Notify before event': { + 'English': 'Notify before event', + 'Slovenian': 'Obvesti pred dogodkom', + }, + 'Hours before': { + 'English': 'Hours before', + 'Slovenian': 'Ur pred dogodkom', + }, + 'hour': { + 'English': 'hour', + 'Slovenian': 'ura', + }, + 'hours': { + 'English': 'hours', + 'Slovenian': 'ur', + }, + 'Notifications enabled': { + 'English': 'Notifications enabled', + 'Slovenian': 'Obvestila vključena', + }, + 'Notifications disabled': { + 'English': 'Notifications disabled', + 'Slovenian': 'Obvestila izključena', + }, + 'Event types': { + 'English': 'Event types', + 'Slovenian': 'Vrste dogodkov', + }, + 'Dežuranje': { + 'English': 'Duty shifts', + 'Slovenian': 'Dežuranje', + }, + 'Sestanek': { + 'English': 'Meetings', + 'Slovenian': 'Sestanki', + }, + 'Tekma': { + 'English': 'Competitions', + 'Slovenian': 'Tekme', + }, }; static String translate(String key, String language) { diff --git a/pubspec.lock b/pubspec.lock index 2e884c5..892f3e9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -85,10 +85,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" checked_yaml: dependency: transitive description: @@ -246,6 +246,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 + url: "https://pub.dev" + source: hosted + version: "18.0.1" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" + url: "https://pub.dev" + source: hosted + version: "8.0.0" flutter_mjpeg: dependency: "direct main" description: @@ -275,6 +299,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_timezone: + dependency: "direct main" + description: + name: flutter_timezone + sha256: ea53c61c9152f271a5e30624a624184804947b6a733ff2b64186bb2579446892 + url: "https://pub.dev" + source: hosted + version: "3.0.1" flutter_web_plugins: dependency: transitive description: flutter @@ -484,18 +516,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" 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: @@ -777,10 +809,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.10" tflite_flutter: dependency: "direct main" description: @@ -789,6 +821,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.0" + timezone: + dependency: "direct main" + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" typed_data: dependency: transitive description: @@ -998,5 +1038,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0-0 <4.0.0" + dart: ">=3.9.0-0 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index c966c98..cca3bd4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: test1 description: A new Flutter project. publish_to: 'none' # This prevents publishing to pub.dev -version: 1.0.3+3 +version: 1.1.0+4 environment: sdk: '>=3.3.0 <4.0.0' @@ -35,6 +35,9 @@ dependencies: image_picker: ^1.0.4 wakelock_plus: ^1.2.11 url_launcher: ^6.1.0 + flutter_local_notifications: ^18.0.1 + timezone: ^0.9.4 + flutter_timezone: ^3.0.0 dependency_overrides: http: ^0.13.6