From 38a849d8a1d05eebe5cd1cb94e3e18d800d499ca Mon Sep 17 00:00:00 2001 From: Adrien MOREL <adrien.morel2.etu@univ-lille.fr> Date: Fri, 23 Dec 2022 15:10:13 +0100 Subject: [PATCH] Notifications + Background refresh for notifications done --- .gitlab-ci.yml | 1 + README.md | 13 +- android/app/build.gradle | 8 +- android/app/src/main/AndroidManifest.xml | 2 + android/build.gradle | 10 +- ios/Podfile.lock | 18 ++ ios/Runner/AppDelegate.swift | 3 + ios/Runner/Base.lproj/Main.storyboard | 13 +- ios/Runner/Info.plist | 22 +- .../background_tasks_utils.dart | 81 ++++++ lib/Notifications/custom_notifications.dart | 30 +++ lib/bloc/watchlist_bloc.dart | 2 - lib/main.dart | 254 ++++++++++-------- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 91 +++++++ pubspec.yaml | 3 + 16 files changed, 417 insertions(+), 136 deletions(-) create mode 100644 lib/BackgroundTasks/background_tasks_utils.dart create mode 100644 lib/Notifications/custom_notifications.dart diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c7a2981..5a60922 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,4 @@ +# Docker container with flutter installed to run on Univ-Lille's servers image: cirrusci/flutter stages: diff --git a/README.md b/README.md index 6265751..7810922 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,8 @@ A new Flutter project. ## Getting Started -This project is a starting point for a Flutter application. +## Testing -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +```bash +adb shell cmd jobscheduler run -f com.example.is_eat_safe 999 +``` diff --git a/android/app/build.gradle b/android/app/build.gradle index e1b778d..1bcf9ac 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,10 +26,11 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -48,6 +49,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 18 + multiDexEnabled true targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -67,5 +69,9 @@ flutter { } dependencies { + implementation 'androidx.window:window:1.0.0' + implementation 'androidx.window:window-java:1.0.0' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' + implementation 'androidx.multidex:multidex:2.0.1' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fed62f6..0578b5b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,9 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" package="com.example.is_eat_safe"> <uses-permission android:name="android.permission.INTERNET"/> <application + tools:replace="android:label" android:label="is_eat_safe" android:name="${applicationName}" android:icon="@mipmap/ic_launcher"> diff --git a/android/build.gradle b/android/build.gradle index 83ae220..2b14f9e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,12 +1,17 @@ buildscript { ext.kotlin_version = '1.6.10' + ext { + compileSdkVersion = 33 // or latest + targetSdkVersion = 33 // or latest + appCompatVersion = "1.4.2" // or latest + } repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:7.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -15,6 +20,9 @@ allprojects { repositories { google() mavenCentral() + maven{ + url "${project(':background_fetch').projectDir}/libs" + } } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0fe9ad2..9d9e36d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,20 +1,29 @@ PODS: + - background_fetch (1.1.2): + - Flutter - barcode_scan2 (0.0.1): - Flutter - MTBBarcodeScanner - SwiftProtobuf - Flutter (1.0.0) + - flutter_background_service_ios (0.0.3): + - Flutter - flutter_barcode_scanner (2.0.0): - Flutter + - flutter_local_notifications (0.0.1): + - Flutter - MTBBarcodeScanner (5.0.11) - shared_preferences_ios (0.0.1): - Flutter - SwiftProtobuf (1.20.3) DEPENDENCIES: + - background_fetch (from `.symlinks/plugins/background_fetch/ios`) - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) - Flutter (from `Flutter`) + - flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`) - flutter_barcode_scanner (from `.symlinks/plugins/flutter_barcode_scanner/ios`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) SPEC REPOS: @@ -23,19 +32,28 @@ SPEC REPOS: - SwiftProtobuf EXTERNAL SOURCES: + background_fetch: + :path: ".symlinks/plugins/background_fetch/ios" barcode_scan2: :path: ".symlinks/plugins/barcode_scan2/ios" Flutter: :path: Flutter + flutter_background_service_ios: + :path: ".symlinks/plugins/flutter_background_service_ios/ios" flutter_barcode_scanner: :path: ".symlinks/plugins/flutter_barcode_scanner/ios" + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" shared_preferences_ios: :path: ".symlinks/plugins/shared_preferences_ios/ios" SPEC CHECKSUMS: + background_fetch: aaf8fc9d1da7f04e5373aabd4bb239704a5be6eb barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac flutter_barcode_scanner: 7a1144744c28dc0c57a8de7218ffe5ec59a9e4bf + flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad SwiftProtobuf: b02b5075dcf60c9f5f403000b3b0c202a11b6ae1 diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..c17fa4f 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -8,6 +8,9 @@ import Flutter didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard index f3c2851..08dbc27 100644 --- a/ios/Runner/Base.lproj/Main.storyboard +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -1,8 +1,10 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r"> +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r"> + <device id="retina6_12" orientation="portrait" appearance="light"/> <dependencies> <deployment identifier="iOS"/> - <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> <!--Flutter View Controller--> @@ -14,13 +16,14 @@ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> </layoutGuides> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> - <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> + <rect key="frame" x="0.0" y="0.0" width="393" height="852"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> - <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> + <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> </view> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> </objects> + <point key="canvasLocation" x="-26" y="-76"/> </scene> </scenes> </document> diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 78a3e86..4a99181 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,8 +2,13 @@ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> - <key>NSCameraUsageDescription</key> - <string>Autorisez l'accès à la caméra pour pouvoir scanner un produit.</string> + <key>BGTaskSchedulerPermittedIdentifiers</key> + <array> + <string>com.transistorsoft.fetch</string> + <string>com.transistorsoft.customtask</string> + </array> + <key>CADisableMinimumFrameDurationOnPhone</key> + <true/> <key>CFBundleDevelopmentRegion</key> <string>$(DEVELOPMENT_LANGUAGE)</string> <key>CFBundleDisplayName</key> @@ -26,6 +31,15 @@ <string>$(FLUTTER_BUILD_NUMBER)</string> <key>LSRequiresIPhoneOS</key> <true/> + <key>NSCameraUsageDescription</key> + <string>Autorisez l'accès à la caméra pour pouvoir scanner un produit.</string> + <key>UIApplicationSupportsIndirectInputEvents</key> + <true/> + <key>UIBackgroundModes</key> + <array> + <string>fetch</string> + <string>processing</string> + </array> <key>UILaunchStoryboardName</key> <string>LaunchScreen</string> <key>UIMainStoryboardFile</key> @@ -45,9 +59,5 @@ </array> <key>UIViewControllerBasedStatusBarAppearance</key> <false/> - <key>CADisableMinimumFrameDurationOnPhone</key> - <true/> - <key>UIApplicationSupportsIndirectInputEvents</key> - <true/> </dict> </plist> diff --git a/lib/BackgroundTasks/background_tasks_utils.dart b/lib/BackgroundTasks/background_tasks_utils.dart new file mode 100644 index 0000000..6926744 --- /dev/null +++ b/lib/BackgroundTasks/background_tasks_utils.dart @@ -0,0 +1,81 @@ +import 'dart:ffi'; + +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:background_fetch/background_fetch.dart'; +import 'package:is_eat_safe/Notifications/custom_notifications.dart'; +import 'package:is_eat_safe/models/watchlist_item.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../api_utils.dart'; + +class BackgroundUtils{ + + @pragma('vm:entry-point') + static void backgroundFetchHeadlessTask(HeadlessTask task) async { + String taskId = task.taskId; + bool isTimeout = task.timeout; + if (isTimeout) { + // This task has exceeded its allowed running-time. + // You must stop what you're doing and immediately .finish(taskId) + print("[BackgroundFetch] Headless task timed-out: $taskId"); + BackgroundFetch.finish(taskId); + return; + } + print('[BackgroundFetch] Headless event received.'); + onHeadlessBackgroundFetch(); + BackgroundFetch.finish(taskId); + } + + static Future<void> configureBackgroundFetch(SharedPreferences prefs, FlutterLocalNotificationsPlugin fln) async { + int status = await BackgroundFetch.configure(BackgroundFetchConfig( + minimumFetchInterval: 15, + requiredNetworkType: NetworkType.ANY + ), (String taskId) async { // <-- Event callback. + // This is the fetch-event callback. + print("[BackgroundFetch] taskId: $taskId"); + + // Use a switch statement to route task-handling. + switch (taskId) { + case 'com.transistorsoft.customtask': + print("Received custom task"); + break; + default: + print("Default fetch task"); + } + onBackgroundFetch(prefs,fln); + // Finish, providing received taskId. + BackgroundFetch.finish(taskId); + }, (String taskId) async { // <-- Event timeout callback + // This task has exceeded its allowed running-time. You must stop what you're doing and immediately .finish(taskId) + print("[BackgroundFetch] TIMEOUT taskId: $taskId"); + BackgroundFetch.finish(taskId); + }); + +// Step 2: Schedule a custom "oneshot" task "com.transistorsoft.customtask" to execute 5000ms from now. + BackgroundFetch.scheduleTask(TaskConfig( + taskId: "com.transistorsoft.customtask", + delay: 5000 // <-- milliseconds + )); + } + + + static Future<void> onBackgroundFetch(SharedPreferences prefs, FlutterLocalNotificationsPlugin fln) async{ + final List<String> items = prefs.getStringList('watchlist_items') ?? []; + //CustomNotifications.showBigTextNotification(title: "Danger", body: "Un article de votre liste a été rapplé", fln: fln); + for (var e in items) { + WatchlistItem item = await ApiUtils.fetchWLItem(e); + if(item.isRecalled){ + CustomNotifications.showBigTextNotification(title: "Danger", body: "Un article de votre liste a été rappelé", fln: fln); + return; + } + } + } + + static Future<void> onHeadlessBackgroundFetch() async { + final prefs = await SharedPreferences.getInstance(); + FlutterLocalNotificationsPlugin fln = FlutterLocalNotificationsPlugin(); + CustomNotifications.initialize(fln); + onBackgroundFetch(prefs,fln); + } + +} \ No newline at end of file diff --git a/lib/Notifications/custom_notifications.dart b/lib/Notifications/custom_notifications.dart new file mode 100644 index 0000000..3e4525e --- /dev/null +++ b/lib/Notifications/custom_notifications.dart @@ -0,0 +1,30 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +class CustomNotifications{ + + static Future initialize(FlutterLocalNotificationsPlugin fln) async { + var androidInit = const AndroidInitializationSettings('mipmap/ic_launcher'); + var iosInit = const DarwinInitializationSettings(); + var initSettings = InitializationSettings( + android: androidInit, + iOS: iosInit); + await fln.initialize(initSettings); + } + + static Future showBigTextNotification({required String title, required String body, required FlutterLocalNotificationsPlugin fln + } ) async { + AndroidNotificationDetails androidPlatformChannelSpecifics = + const AndroidNotificationDetails( + 'watchlist_warning', + 'iseatsafe_channel', + importance: Importance.max, + priority: Priority.high, + ); + + var not = NotificationDetails(android: androidPlatformChannelSpecifics, + iOS: const DarwinNotificationDetails() + ); + await fln.show(0, title, body, not); + } + +} \ No newline at end of file diff --git a/lib/bloc/watchlist_bloc.dart b/lib/bloc/watchlist_bloc.dart index 3447426..af705e9 100644 --- a/lib/bloc/watchlist_bloc.dart +++ b/lib/bloc/watchlist_bloc.dart @@ -40,8 +40,6 @@ class WatchlistBloc extends Bloc<WatchlistEvent, WatchlistState> { try { var it = await ApiUtils.fetchWLItem(toBeAddedItemId); - //TODO Api call to get real data - if (state is WatchlistInitial) { var tmp = <WatchlistItem>[]; tmp.add(it); diff --git a/lib/main.dart b/lib/main.dart index 9b65659..41e92db 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,11 @@ +import 'dart:async'; +import 'dart:io' show Platform; +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:is_eat_safe/BackgroundTasks/background_tasks_utils.dart'; +import 'package:is_eat_safe/Notifications/custom_notifications.dart'; import 'package:is_eat_safe/api_utils.dart'; import 'package:is_eat_safe/bloc/watchlist_bloc.dart'; import 'package:is_eat_safe/models/watchlist_item.dart'; @@ -8,6 +14,8 @@ import 'package:is_eat_safe/views/rappel_listview.dart'; import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:is_eat_safe/views/watchlist_view.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:background_fetch/background_fetch.dart'; import 'bloc/produit_bloc.dart'; @@ -15,6 +23,11 @@ late WatchlistBloc _watchlistBloc; late ProduitBloc _produitBloc; +final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + + + void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -35,6 +48,15 @@ void main() async { theme: ThemeData(primarySwatch: Colors.orange), debugShowCheckedModeBanner: false, home: const MyApp(),)); + + + //Fetch data from api and compares it to local data, if data matches emit local notification + //See https://pub.dev/packages/background_fetch for more info + if(Platform.isAndroid) { + BackgroundFetch.registerHeadlessTask(BackgroundUtils.backgroundFetchHeadlessTask); + } + CustomNotifications.initialize(flutterLocalNotificationsPlugin); + BackgroundUtils.configureBackgroundFetch(prefs, flutterLocalNotificationsPlugin); } class MyApp extends StatefulWidget { @@ -56,120 +78,126 @@ class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return Scaffold( - floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, - appBar: AppBar( - title: _searchBoolean && _index == 0 ? _searchTextField() : const Text('IsEatSafe'), - actions: [ - //add - if (_index == 0) - !_searchBoolean - ? IconButton( - icon: const Icon(Icons.search), - onPressed: () { - setState(() { - _searchBoolean = true; - }); - }) - : IconButton( - icon: const Icon(Icons.clear), - onPressed: () { - setState(() { - _produitBloc.add(ProduitRefreshed()); - _searchBoolean = false; - }); - }), - ], - toolbarHeight: 40, - ), - body: Stack( - children: <Widget>[ - Offstage( - offstage: _index != 0, - child: TickerMode( - enabled: _index == 0, - child: BlocProvider<ProduitBloc>( - create: (context) => _produitBloc..add(ProduitFetched()), - child: const RappelListView()), - ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, + appBar: AppBar( + title: _searchBoolean && _index == 0 ? _searchTextField() : const Text( + 'IsEatSafe'), + actions: [ + //add + if (_index == 0) + !_searchBoolean + ? IconButton( + icon: const Icon(Icons.search), + onPressed: () { + setState(() { + _searchBoolean = true; + }); + }) + : IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + setState(() { + _produitBloc.add(ProduitRefreshed()); + _searchBoolean = false; + }); + }), + ], + toolbarHeight: 40, + ), + body: Stack( + children: <Widget>[ + Offstage( + offstage: _index != 0, + child: TickerMode( + enabled: _index == 0, + child: BlocProvider<ProduitBloc>( + create: (context) => _produitBloc..add(ProduitFetched()), + child: const RappelListView()), ), - Offstage( - offstage: _index != 1, - child: TickerMode( - enabled: _index == 1, - child: BlocProvider<WatchlistBloc>( - create: (context) => _watchlistBloc, - child: const WatchListView()), - ), + ), + Offstage( + offstage: _index != 1, + child: TickerMode( + enabled: _index == 1, + child: BlocProvider<WatchlistBloc>( + create: (context) => _watchlistBloc, + child: const WatchListView()), ), - ], + ), + ], + ), + floatingActionButton: _index == 0 + ? FloatingActionButton( + backgroundColor: Colors.black, + onPressed: () async { + var result = await BarcodeScanner.scan(); + if (await ApiUtils.isItemRecalled(result.rawContent)) { + ApiUtils.getOneRecalledProduct(result.rawContent).then((value) => + Navigator.push(context, MaterialPageRoute( + builder: (context) => DetailledProductView(value)))); + } else { + showDialog( + context: context, + builder: (_) => _productIsOkDialog(), + barrierDismissible: true, + ); + } + }, + child: const Icon( + Icons.qr_code_scanner, + color: Colors.white, ), - floatingActionButton: _index == 0 - ? FloatingActionButton( - backgroundColor: Colors.black, - onPressed: () async { - var result = await BarcodeScanner.scan(); - if(await ApiUtils.isItemRecalled(result.rawContent)){ - ApiUtils.getOneRecalledProduct(result.rawContent).then((value) => Navigator.push(context, MaterialPageRoute(builder: (context) => DetailledProductView(value)))); - }else { - showDialog( - context: context, - builder: (_) => _productIsOkDialog(), - barrierDismissible: true, - ); - } - }, - child: const Icon( - Icons.qr_code_scanner, - color: Colors.white, - ), - ) - : FloatingActionButton( - backgroundColor: Colors.black, - onPressed: () async { - var result = await BarcodeScanner.scan(); - _watchlistBloc.add(ElementToBeAdded(result.rawContent)); - }, - child: const Icon( - Icons.add, - color: Colors.white, - ), + ) + : FloatingActionButton( + backgroundColor: Colors.black, + onPressed: () async { + var result = await BarcodeScanner.scan(); + _watchlistBloc.add(ElementToBeAdded(result.rawContent)); + }, + child: const Icon( + Icons.add, + color: Colors.white, + ), + ), + bottomNavigationBar: BottomAppBar( + shape: const CircularNotchedRectangle(), + elevation: 1, + notchMargin: 5, + color: Colors.orange, + child: BottomNavigationBar( + elevation: 0, + showSelectedLabels: false, + showUnselectedLabels: false, + backgroundColor: Theme + .of(context) + .primaryColor + .withAlpha(0), + fixedColor: Colors.white, + currentIndex: _index, + onTap: (int index) { + setState(() { + _index = index; + }); + }, + items: const [ + BottomNavigationBarItem( + icon: Padding( + padding: EdgeInsets.fromLTRB(0, 0, 25, 0), + child: Icon(Icons.list), ), - bottomNavigationBar: BottomAppBar( - shape: const CircularNotchedRectangle(), - elevation: 1, - notchMargin: 5, - color: Colors.orange, - child: BottomNavigationBar( - elevation: 0, - showSelectedLabels: false, - showUnselectedLabels: false, - backgroundColor: Theme.of(context).primaryColor.withAlpha(0), - fixedColor: Colors.white, - currentIndex: _index, - onTap: (int index) { - setState(() { - _index = index; - }); - }, - items: const [ - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.fromLTRB(0, 0, 25, 0), - child: Icon(Icons.list), - ), - label: "", + label: "", + ), + BottomNavigationBarItem( + icon: Padding( + padding: EdgeInsets.fromLTRB(25, 0, 0, 0), + child: Icon(Icons.add_alarm_outlined), ), - BottomNavigationBarItem( - icon: Padding( - padding: EdgeInsets.fromLTRB(25, 0, 0, 0), - child: Icon(Icons.add_alarm_outlined), - ), - label: "", - ) - ], - ), + label: "", + ) + ], ), - ); + ), + ); } @@ -192,10 +220,10 @@ class _MyAppState extends State<MyApp> { decoration: const InputDecoration( //Style of TextField enabledBorder: UnderlineInputBorder( - //Default TextField border + //Default TextField border borderSide: BorderSide(color: Colors.white)), focusedBorder: UnderlineInputBorder( - //Borders when a TextField is in focus + //Borders when a TextField is in focus borderSide: BorderSide(color: Colors.white)), hintText: 'Search', //Text that is displayed when nothing is entered. hintStyle: TextStyle( @@ -208,10 +236,11 @@ class _MyAppState extends State<MyApp> { } - Widget _productIsOkDialog(){ + Widget _productIsOkDialog() { return AlertDialog( title: const Text("Produit OK"), - content: const Text("Selon nos informations ce produit ne fait pas l'objet d'un rappel"), + content: const Text( + "Selon nos informations ce produit ne fait pas l'objet d'un rappel"), actions: <Widget>[ TextButton( child: const Text('Bien reçu!'), @@ -223,3 +252,4 @@ class _MyAppState extends State<MyApp> { ); } } + diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 287b6a9..57a36ee 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import flutter_local_notifications import shared_preferences_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 4cea9d2..1ec893a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.1" async: dependency: transitive description: @@ -8,6 +15,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.9.0" + background_fetch: + dependency: "direct main" + description: + name: background_fetch + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" barcode_scan2: dependency: "direct main" description: @@ -57,6 +71,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + dbus: + dependency: transitive + description: + name: dbus + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.8" equatable: dependency: "direct main" description: @@ -97,6 +118,34 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_background_service: + dependency: "direct main" + description: + name: flutter_background_service + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.6" + flutter_background_service_android: + dependency: transitive + description: + name: flutter_background_service_android + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.3" + flutter_background_service_ios: + dependency: transitive + description: + name: flutter_background_service_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + flutter_background_service_platform_interface: + dependency: transitive + description: + name: flutter_background_service_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.0" flutter_barcode_scanner: dependency: "direct main" description: @@ -118,6 +167,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + url: "https://pub.dartlang.org" + source: hosted + version: "13.0.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -219,6 +289,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" platform: dependency: transitive description: @@ -357,6 +434,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.12" + timezone: + dependency: transitive + description: + name: timezone + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.0" typed_data: dependency: transitive description: @@ -385,6 +469,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0+2" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" sdks: dart: ">=2.18.1 <3.0.0" flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index d510f6b..778f8f8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,9 @@ dependencies: cupertino_icons: ^1.0.2 barcode_scan2: ^4.2.1 shared_preferences: ^2.0.15 + flutter_background_service: ^2.4.6 + flutter_local_notifications: ^13.0.0 + background_fetch: ^1.1.2 dev_dependencies: flutter_test: -- GitLab