diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..b40cdf6 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,140 @@ +name: Draft Github Release +on: + workflow_dispatch: + # Enable manual run + pull_request: + branches: [ "master", "develop" ] + +jobs: + create-build: + name: Create ${{ matrix.target }} build + runs-on: ${{ matrix.os }} + strategy: + matrix: + target: [Android, Windows, Linux] + include: + - os: windows-2019 + target: Windows + build_target: windows + build_path: build\windows\runner\Release + asset_extension: .zip + asset_content_type: application/zip + - os: ubuntu-20.04 + target: Linux + build_target: linux + build_path: build/linux/x64/release/bundle + asset_extension: .tar.gz + asset_content_type: application/gzip + - os: ubuntu-20.04 + target: Android + build_target: apk + build_path: build/app/outputs/flutter-apk + asset_extension: .apk + asset_content_type: application/vnd.android.package-archive + # Disable fail-fast as we want results from all even if one fails. + fail-fast: false + steps: + # Set up Flutter. + - name: Clone Flutter repository with master channel + uses: subosito/flutter-action@4389e6cbc6cb8a4b18c628ff96ff90be0e926aa8 + with: + channel: master + + - name: Install Linux dependencies + if: matrix.target == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libx11-dev pkg-config cmake ninja-build libblkid-dev + - name: Install Android dependencies + if: matrix.target == 'Android' + uses: actions/setup-java@v1 + with: + java-version: '12.x' + - name: Enable desktop support + if: matrix.target != 'Android' + run: | + flutter config --enable-linux-desktop + flutter config --enable-macos-desktop + flutter config --enable-windows-desktop + - run: flutter doctor -v + + + # Checkout NSSL code, recreate missing files, and get packages. + - name: Checkout NSSL code + uses: actions/checkout@v2 + - run: flutter create . --project-name nssl --org de.susch19 + - run: flutter pub get + - name: Configure Keystore for Android + if: matrix.target == 'Android' + run: | + echo "$KEY_STORE_FILE" | base64 --decode > app/nssl-keystore.jks + echo "storeFile=nssl-keystore.jks" >> key.properties + echo "keyAlias=$KEYSTORE_KEY_ALIAS" >> key.properties + echo "storePassword=$KEYSTORE_STORE_PASSWORD" >> key.properties + echo "keyPassword=$KEYSTORE_KEY_PASSWORD" >> key.properties + env: + KEY_STORE_FILE: ${{ secrets.KEY_STORE_FILE }} + KEYSTORE_KEY_ALIAS: ${{ secrets.KEYSTORE_KEY_ALIAS }} + KEYSTORE_KEY_PASSWORD: ${{ secrets.KEYSTORE_KEY_PASSWORD }} + KEYSTORE_STORE_PASSWORD: ${{ secrets.KEYSTORE_STORE_PASSWORD }} + working-directory: android + + - name: Create License File Powershell + if: matrix.target == 'Windows' + run: | + [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("$Env:SCANDIT_LICENSE_FILE")) > .license.dart + working-directory: lib + env: + SCANDIT_LICENSE_FILE: ${{ secrets.SCANDIT_LICENSE_FILE }} + + - name: Create License File Bash + if: matrix.target != 'Windows' + run: | + echo "$SCANDIT_LICENSE_FILE" | base64 --decode > .license.dart + working-directory: lib + env: + SCANDIT_LICENSE_FILE: ${{ secrets.SCANDIT_LICENSE_FILE }} + + - name: Create Google Services Json File Bash + if: matrix.target != 'Windows' + run: | + echo "$GOOGLE_SERVICES_JSON" | base64 --decode > google-services.json + working-directory: android/app + env: + GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} + + + # Build the application + - name: Build Flutter Application + run: flutter build -v ${{ matrix.build_target }} --release + + - name: Copy SQLite3 Dependency + if: matrix.target == 'Windows' + run: cp .\windows\libs\* ${{ matrix.build_path }} + + # Package the build. + - name: Copy VC redistributables to release directory for Windows + if: matrix.target == 'Windows' + run: | + Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\msvcp140.dll') . + Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\vcruntime140.dll') . + Copy-Item (vswhere -latest -find 'VC\Redist\MSVC\*\x64\*\vcruntime140_1.dll') . + - name: Rename build for Android + if: matrix.target == 'Android' + run: mv app-release.apk $GITHUB_WORKSPACE/nssl_${{ matrix.target }}.apk + working-directory: ${{ matrix.build_path }} + - name: Compress build for Linux + if: matrix.target == 'Linux' + run: tar czf $GITHUB_WORKSPACE/nssl_${{ matrix.target }}.tar.gz * + working-directory: ${{ matrix.build_path }} + - name: Compress build for Windows + if: matrix.target == 'Windows' + run: compress-archive -Path * -DestinationPath ${env:GITHUB_WORKSPACE}\nssl_${{ matrix.target }}.zip + working-directory: ${{ matrix.build_path }} + + # Upload the build. + - name: Add build into artifacts + uses: actions/upload-artifact@v2 + with: + name: nssl_${{ matrix.target }}${{ matrix.asset_extension }} + path: ./nssl_${{ matrix.target }}${{ matrix.asset_extension }} diff --git a/.gitignore b/.gitignore index 0652200..7475bf4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ packages pubspec.lock *.apk *.keystore +*.properties .vscode/ .flutter-plugins .flutter-plugins-dependencies @@ -20,4 +21,11 @@ android/app/google-services.json ios/Flutter/flutter_export_environment.sh .dart_tool/* android/app/src/main/java/io/flutter/plugins/* -lib/.license.dart +linux/flutter/generated_plugin_registrant.cc +linux/flutter/generated_plugins.cmake +windows/flutter/generated_plugin_registrant.cc +windows/flutter/generated_plugins.cmake +.license.dart +windows/sqlite3.dll +android/java_pid62552.hprof +ios/Runner/GoogleService-Info.plist diff --git a/android/app/build.gradle b/android/app/build.gradle index c2b6020..4723113 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -15,8 +15,14 @@ apply plugin: 'com.android.application' apply plugin: 'com.google.gms.google-services' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + def keystoreProperties = new Properties() + def keystorePropertiesFile = rootProject.file('key.properties') + if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) + } + android { - compileSdkVersion 31 + compileSdkVersion 33 lintOptions { checkReleaseBuilds false // Add this @@ -29,14 +35,22 @@ android { resValue "string", "app_name", "NSSL" multiDexEnabled true minSdkVersion 21 - targetSdkVersion 31 + targetSdkVersion 33 } - buildTypes { + signingConfigs { release { - // Signing with the debug keys for now, so `flutter run --release` works. - signingConfig signingConfigs.debug + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] } + } + + buildTypes { + release { + signingConfig signingConfigs.release + } debug { applicationIdSuffix ".debug" versionNameSuffix "-debug" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d346c30..a357075 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,15 +1,34 @@ - + - + + + + + + + + + + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index 0ac86c6..aa52e33 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,8 +8,8 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' - classpath 'com.google.gms:google-services:4.3.8' + classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.google.gms:google-services:4.3.14' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/assets/images/scandit.png b/assets/images/scandit.png new file mode 100644 index 0000000..779216b Binary files /dev/null and b/assets/images/scandit.png differ diff --git a/assets/vectors/app_icon.svg b/assets/vectors/app_icon.svg new file mode 100644 index 0000000..cdb5f3d --- /dev/null +++ b/assets/vectors/app_icon.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/vectors/github.svg b/assets/vectors/github.svg new file mode 100644 index 0000000..a4c9776 --- /dev/null +++ b/assets/vectors/github.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + diff --git a/assets/vectors/google_play.svg b/assets/vectors/google_play.svg new file mode 100644 index 0000000..5c13b49 --- /dev/null +++ b/assets/vectors/google_play.svg @@ -0,0 +1,411 @@ + + + + diff --git a/assets/vectors/nssl_icon.svg b/assets/vectors/nssl_icon.svg new file mode 100644 index 0000000..cdb5f3d --- /dev/null +++ b/assets/vectors/nssl_icon.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icon - Kopie.png b/icon - Kopie.png deleted file mode 100644 index 27ef7df..0000000 Binary files a/icon - Kopie.png and /dev/null differ diff --git a/icon.png b/icon.png deleted file mode 100644 index b8575d1..0000000 Binary files a/icon.png and /dev/null differ diff --git a/ios/firebase_app_id_file.json b/ios/firebase_app_id_file.json new file mode 100644 index 0000000..d45add8 --- /dev/null +++ b/ios/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:714311873087:ios:4c24043cc62e99464dbe8a", + "FIREBASE_PROJECT_ID": "nonsuckingshoppinglist", + "GCM_SENDER_ID": "714311873087" +} \ No newline at end of file diff --git a/lib/firebase/cloud_messsaging.dart b/lib/firebase/cloud_messsaging.dart index e5734b4..d207c89 100644 --- a/lib/firebase/cloud_messsaging.dart +++ b/lib/firebase/cloud_messsaging.dart @@ -2,79 +2,110 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:nssl/helper/iterable_extensions.dart'; +import 'package:nssl/manager/startup_manager.dart'; import 'package:nssl/models/model_export.dart'; +import 'package:riverpod/riverpod.dart'; -FirebaseMessaging? get firebaseMessaging => Platform.isAndroid ? FirebaseMessaging.instance : null; +FirebaseMessaging? get firebaseMessaging => Startup.firebaseSupported() ? FirebaseMessaging.instance : null; + +final cloudMessagingProvider = Provider((ref) { + return CloudMessaging(ref); +}); class CloudMessaging { - static Future onMessage(RemoteMessage message, Function setState) async { - + static late Ref _ref; + + CloudMessaging(Ref ref) { + _ref = ref; + } + + static Future onMessage(RemoteMessage message) async { final dynamic data = message.data; int listId = int.parse(data["listId"]); - if (User.ownId == int.parse(data["userId"])) { + var ownId = _ref.read(userIdProvider); + if (ownId == int.parse(data["userId"])) { return null; } + var listController = _ref.read(shoppingListsProvider); - if (User.shoppingLists.firstWhere((x) => x.id == listId, orElse: () => ShoppingList.empty) == ShoppingList.empty) { + var list = listController.shoppingLists.firstOrNull((element) => element.id == listId); + + if (list == null) { var mapp = jsonDecode(data["items"]); //User was added to new list - User.shoppingLists.add(ShoppingList(listId, data["name"]) - ..shoppingItems = mapp - .map((x) => ShoppingItem(x["name"]) - ..id = x["id"] - ..amount = x["amount"] - ..sortOrder = x["sortOrder"]) - .toList()); - firebaseMessaging!.subscribeToTopic(listId.toString() + "shoppingListTopic"); + var items = _ref.watch(shoppingItemsProvider.notifier); + var newState = items.state.toList(); + newState + .addAll(mapp.map((x) => ShoppingItem(x["name"], listId, x["sortOrder"], id: x["id"], amount: x["amount"]))); + items.state = newState; + listController.addList(ShoppingList(listId, data["name"])); } else if (data.length == 1) { //List deleted - User.shoppingLists.removeWhere((x) => x.id == listId); - firebaseMessaging!.unsubscribeFromTopic(listId.toString() + "shoppingListTopic"); + listController.removeList(listId); } else { var action = data["action"]; - var list = User.shoppingLists.firstWhere((x) => x.id == listId); + var list = listController.shoppingLists.firstWhere((x) => x.id == listId); switch (action) { case "ItemChanged": //Id, Amount, action var id = int.parse(data["id"]); - list.shoppingItems!.firstWhere((x) => x!.id == id)!.amount = int.parse(data["amount"]); - list.save(); + var items = _ref.watch(shoppingItemsProvider.notifier); + var newState = items.state.toList(); + var item = newState.firstWhere((x) => x.id == id); + newState.remove(item); + newState.add(item.cloneWith(newAmount: int.parse(data["amount"]))); + items.state = newState; + listController.save(list); break; case "ItemDeleted": //Id, action var id = int.parse(data["id"]); - list.shoppingItems!.removeWhere((x) => x!.id == id); - list.save(); + listController.deleteSingleItemById(list, id); break; case "NewItemAdded": //Id, Name, Gtin, Amount, action - if (list.shoppingItems!.firstWhere((x) => x!.id == int.parse(data["id"]), orElse: () => null) != null) break; - list.shoppingItems!.add(ShoppingItem(data["name"]) - ..id = int.parse(data["id"]) - ..amount = int.parse(data["amount"]) - ..crossedOut = false - ..sortOrder = int.parse(data["sortOrder"])); - list.save(); + var newItemId = int.parse(data["id"]); + var existing = _ref.read(shoppingItemProvider.create(newItemId)); + if (existing != null) break; + + listController.addSingleItem( + list, + ShoppingItem(data["name"], list.id, int.parse(data["sortOrder"]), + id: int.parse(data["id"]), amount: int.parse(data["amount"]), crossedOut: false)); break; case "ListRename": //Name, action - list.name = data["name"]; - list.save(); + listController.rename(list.id, data["name"] as String); break; case "Refresh": //action - await list.refresh(); + listController.refresh(list); break; case "ItemRenamed": //product.Id, product.Name - list.shoppingItems!.firstWhere((x) => x!.id == int.parse(data["id"]))!.name = data["name"]; - list.save(); + var itemId = int.parse(data["id"]); + var items = _ref.watch(shoppingItemsProvider.notifier); + var newState = items.state.toList(); + var item = newState.firstWhere((x) => x.id == itemId); + newState.remove(item); + newState.add( + item.cloneWith(newName: data["name"]), + ); + items.state = newState; + listController.save(list); break; case "OrderChanged": - var id = int.parse(data["id"]); - list.shoppingItems!.firstWhere((x) => x!.id == id)!.sortOrder = int.parse(data["sortOrder"]); - list.save(); + var itemId = int.parse(data["id"]); + var items = _ref.watch(shoppingItemsProvider.notifier); + var newState = items.state.toList(); + var item = newState.firstWhere((x) => x.id == itemId); + newState.remove(item); + newState.add(item.cloneWith( + newAmount: int.parse(data["amount"]), + newSortOrder: int.parse(data["sortOrder"]), + )); + items.state = newState; + listController.save(list); break; } } - var args = []; - args.add(() {}); - Function.apply(setState, args); return null; } diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..3a4b2f3 --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,87 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + return macos; + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyD2AWdZaGet1YvVgck5gZ3mww9-6NDozek', + appId: '1:714311873087:web:7ff97d63debd5b364dbe8a', + messagingSenderId: '714311873087', + projectId: 'nonsuckingshoppinglist', + authDomain: 'nonsuckingshoppinglist.firebaseapp.com', + databaseURL: 'https://nonsuckingshoppinglist.firebaseio.com', + storageBucket: 'nonsuckingshoppinglist.appspot.com', + measurementId: 'G-55C6JSXQK6', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyBDY0v4Y8Ugdw0__eBIg0vA_oiVbIdPSWI', + appId: '1:714311873087:android:ca9c27dbc69e975a', + messagingSenderId: '714311873087', + projectId: 'nonsuckingshoppinglist', + databaseURL: 'https://nonsuckingshoppinglist.firebaseio.com', + storageBucket: 'nonsuckingshoppinglist.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyCaNflc7mTb67r81IZP4Xpn6vkU8kto9Fc', + appId: '1:714311873087:ios:4c24043cc62e99464dbe8a', + messagingSenderId: '714311873087', + projectId: 'nonsuckingshoppinglist', + databaseURL: 'https://nonsuckingshoppinglist.firebaseio.com', + storageBucket: 'nonsuckingshoppinglist.appspot.com', + iosClientId: '714311873087-6830bbhihm6v74r3jvp0jfh2si59g1r9.apps.googleusercontent.com', + iosBundleId: 'com.yourcompany.testProject', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyCaNflc7mTb67r81IZP4Xpn6vkU8kto9Fc', + appId: '1:714311873087:ios:293f27fa016b99dc4dbe8a', + messagingSenderId: '714311873087', + projectId: 'nonsuckingshoppinglist', + databaseURL: 'https://nonsuckingshoppinglist.firebaseio.com', + storageBucket: 'nonsuckingshoppinglist.appspot.com', + iosClientId: '714311873087-ch95nc16kbiuckgaogfg388vbhi3ka5n.apps.googleusercontent.com', + iosBundleId: 'de.susch19.nssl', + ); +} diff --git a/lib/generated_plugin_registrant.dart b/lib/generated_plugin_registrant.dart deleted file mode 100644 index 26631ee..0000000 --- a/lib/generated_plugin_registrant.dart +++ /dev/null @@ -1,20 +0,0 @@ -// -// Generated file. Do not edit. -// - -// ignore_for_file: directives_ordering -// ignore_for_file: lines_longer_than_80_chars - -import 'package:firebase_core_web/firebase_core_web.dart'; -import 'package:firebase_messaging_web/firebase_messaging_web.dart'; -import 'package:shared_preferences_web/shared_preferences_web.dart'; - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -// ignore: public_member_api_docs -void registerPlugins(Registrar registrar) { - FirebaseCoreWeb.registerWith(registrar); - FirebaseMessagingWeb.registerWith(registrar); - SharedPreferencesPlugin.registerWith(registrar); - registrar.registerMessageHandler(); -} diff --git a/lib/helper/choose_dialog.dart b/lib/helper/choose_dialog.dart new file mode 100644 index 0000000..5a4f446 --- /dev/null +++ b/lib/helper/choose_dialog.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:nssl/localization/nssl_strings.dart'; + +class ChooseDialog { + static AlertDialog create( + {String title = "", + String titleOption1 = "", + Function? onOption1, + String titleOption2 = "", + Function? onOption2, + required BuildContext context}) { + return AlertDialog( + title: Text(NSSLStrings.of(context).chooseListToAddTitle()), + content: Container( + width: 80, + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.vertical, + children: [ + ListTile( + title: Text(titleOption1), + onTap: () { + Navigator.pop(context, ""); + onOption1?.call(); + }, + ), + ListTile( + title: Text(titleOption2), + onTap: () { + Navigator.pop(context, ""); + onOption2?.call(); + }, + ), + ], + ), + ), + actions: [ + TextButton(child: Text(NSSLStrings.of(context).cancelButton()), onPressed: () => Navigator.pop(context, "")), + ], + ); + } +} diff --git a/lib/helper/password_service.dart b/lib/helper/password_service.dart new file mode 100644 index 0000000..ce715ff --- /dev/null +++ b/lib/helper/password_service.dart @@ -0,0 +1,15 @@ +enum PasswordErrorCode { none, empty, tooShort, missingCharacters } + +class PasswordService { + static PasswordErrorCode checkNewPassword(String newPassword) { + if (newPassword.isEmpty) return PasswordErrorCode.empty; + if (newPassword.length < 6) return PasswordErrorCode.tooShort; + var containsChar = newPassword.contains(RegExp(r'[a-zA-ZäöüÄÖÜ]')); + var containsDigit = newPassword.contains(RegExp(r'[0-9]')); + var containsSpecialChar = newPassword.contains(RegExp(r'[^0-9a-zA-ZäöüÄÖÜ]')); + if (!containsChar && !containsSpecialChar || !containsDigit && !containsSpecialChar) + return PasswordErrorCode.missingCharacters; + + return PasswordErrorCode.none; + } +} diff --git a/lib/helper/simple_dialog.dart b/lib/helper/simple_dialog.dart index 8b411d2..cd4ec6e 100644 --- a/lib/helper/simple_dialog.dart +++ b/lib/helper/simple_dialog.dart @@ -6,7 +6,7 @@ class SimpleDialogAcceptDeny { {String title = "", String text = "", ValueChanged? onSubmitted, - BuildContext? context}) { + required BuildContext context}) { return AlertDialog( title: title == "" ? null : Text(title), content: SingleChildScrollView( @@ -16,12 +16,12 @@ class SimpleDialogAcceptDeny { ), actions: [ TextButton( - child: Text(NSSLStrings.of(context)!.cancelButton()), - onPressed: () => Navigator.pop(context!, "")), + child: Text(NSSLStrings.of(context).cancelButton()), + onPressed: () => Navigator.pop(context, "")), TextButton( - child: Text(NSSLStrings.of(context)!.acceptButton()), + child: Text(NSSLStrings.of(context).acceptButton()), onPressed: () { - Navigator.pop(context!, ""); + Navigator.pop(context, ""); onSubmitted!(""); }) ]); diff --git a/lib/helper/simple_dialog_single_input.dart b/lib/helper/simple_dialog_single_input.dart index 9765be7..e1743db 100644 --- a/lib/helper/simple_dialog_single_input.dart +++ b/lib/helper/simple_dialog_single_input.dart @@ -9,7 +9,7 @@ class SimpleDialogSingleInput { String defaultText = "", int maxLines = 1, ValueChanged? onSubmitted, - BuildContext? context, + required BuildContext context, }) { var tec = TextEditingController(); tec.text = defaultText; @@ -25,18 +25,18 @@ class SimpleDialogSingleInput { maxLines: maxLines, autofocus: true, onSubmitted: (s) { - Navigator.pop(context!); + Navigator.pop(context); onSubmitted!(s); }), ], ), ), actions: [ - TextButton(child: Text(NSSLStrings.of(context)!.cancelButton()), onPressed: () => Navigator.pop(context!, "")), + TextButton(child: Text(NSSLStrings.of(context).cancelButton()), onPressed: () => Navigator.pop(context, "")), TextButton( - child: Text(NSSLStrings.of(context)!.acceptButton()), + child: Text(NSSLStrings.of(context).acceptButton()), onPressed: () { - Navigator.pop(context!, ""); + Navigator.pop(context, ""); onSubmitted!(tec.text); }) ]); diff --git a/lib/localization/nssl_messages_all.dart b/lib/localization/nssl_messages_all.dart index 2dc68fd..0faba72 100644 --- a/lib/localization/nssl_messages_all.dart +++ b/lib/localization/nssl_messages_all.dart @@ -14,34 +14,30 @@ Map _deferredLibraries = { MessageLookupByLibrary? _findExact(localeName) { switch (localeName) { - case 'en': - return m_en.messages; - case 'es': - return null; //messages_es.messages; case 'de': return m_de.messages; default: - return null; + return m_en.messages; } } /// User programs should call this before using [localeName] for messages. -Future initializeMessages(String localeName) { - var lib = _deferredLibraries[Intl.canonicalizedLocale(localeName)]; - var load = lib == null ? Future.value(false) : Future.value(null); - return load.then((_) { - initializeInternalMessageLookup(() => CompositeMessageLookup()); - messageLookup.addLocale(localeName, _findGeneratedMessagesFor); - }); -} + Future initializeMessages(String localeName) { + var lib = _deferredLibraries[Intl.canonicalizedLocale(localeName)]; + var load = lib == null ? Future.value(false) : Future.value(null); + return load.then((_) { + initializeInternalMessageLookup(() => CompositeMessageLookup()); + messageLookup.addLocale(localeName, _findGeneratedMessagesFor); + }); + } -bool _messagesExistFor(String locale) { - var messages; - try { - messages = _findExact(locale); - } catch (e) {} - return messages != null; -} + bool _messagesExistFor(String locale) { + var messages; + try { + messages = _findExact(locale); + } catch (e) {} + return messages != null; + } MessageLookupByLibrary? _findGeneratedMessagesFor(locale) { var actualLocale = diff --git a/lib/localization/nssl_messages_de.dart b/lib/localization/nssl_messages_de.dart index 8a70354..2c990f3 100644 --- a/lib/localization/nssl_messages_de.dart +++ b/lib/localization/nssl_messages_de.dart @@ -8,7 +8,7 @@ class MessageLookup extends MessageLookupByLibrary { final Map messages = _notInlinedMessages(_notInlinedMessages); static _notInlinedMessages(_) => { "options": MessageLookupByLibrary.simpleMessage("Optionen"), - "changeTheme": MessageLookupByLibrary.simpleMessage("Theme ändern"), + "changeTheme": MessageLookupByLibrary.simpleMessage("Design ändern"), "scanPB": MessageLookupByLibrary.simpleMessage("SCANNEN"), "addPB": MessageLookupByLibrary.simpleMessage("ADD"), //TODO find good german word "searchPB": MessageLookupByLibrary.simpleMessage("SUCHEN"), @@ -20,7 +20,8 @@ class MessageLookup extends MessageLookupByLibrary { "addProduct": MessageLookupByLibrary.simpleMessage("Artikel hinzufügen"), "addProductWithoutSearch": MessageLookupByLibrary.simpleMessage("Name des Artikels"), "productName": MessageLookupByLibrary.simpleMessage("Artikelname"), - "messageDeleteAllCrossedOut": MessageLookupByLibrary.simpleMessage("Alle durchgestrichenen Artikel wurden gelöscht"), + "messageDeleteAllCrossedOut": + MessageLookupByLibrary.simpleMessage("Alle durchgestrichenen Artikel wurden gelöscht"), "undo": MessageLookupByLibrary.simpleMessage("RÜCKG."), "removedShoppingListMessage": MessageLookupByLibrary.simpleMessage(" entfernt "), //\${User.shoppingLists} "noListsInDrawerMessage": MessageLookupByLibrary.simpleMessage(" Hier werden deine Listen angezeigt"), @@ -29,7 +30,18 @@ class MessageLookup extends MessageLookupByLibrary { "listName": MessageLookupByLibrary.simpleMessage("Listenname"), "renameListTitle": MessageLookupByLibrary.simpleMessage("Liste umbenennen"), "renameListHint": MessageLookupByLibrary.simpleMessage("Der neue Name der Liste"), + "chooseListToAddTitle": MessageLookupByLibrary.simpleMessage("Welche Liste hinzufügen?"), "addNewListTitle": MessageLookupByLibrary.simpleMessage("Füge eine neue Liste hinzu"), + "recipeCreateError": MessageLookupByLibrary.simpleMessage("Konnte Rezept nicht erstellen"), + "recipeFromShareTitle": MessageLookupByLibrary.simpleMessage("Zu welcher Liste hinzufügen?"), + "recipeFromShareNew": MessageLookupByLibrary.simpleMessage("NEU"), + "recipeName": MessageLookupByLibrary.simpleMessage("Rezept"), + "recipeNameHint": MessageLookupByLibrary.simpleMessage("Rezept ID oder URL"), + "addNewRecipeTitle": MessageLookupByLibrary.simpleMessage("Neues Rezept hinzufügen"), + "importNewRecipe": MessageLookupByLibrary.simpleMessage("Rezept importieren"), + "importNewRecipeTitle": MessageLookupByLibrary.simpleMessage("Neues Rezept importieren"), + "chooseAddListDialog": MessageLookupByLibrary.simpleMessage("Einkaufen"), + "chooseAddRecipeDialog": MessageLookupByLibrary.simpleMessage("Chefkoch"), "youHaveActionItemMessage": MessageLookupByLibrary.simpleMessage('Du hast '), //\$action \$item "archived": MessageLookupByLibrary.simpleMessage('archiviert'), "deleted": MessageLookupByLibrary.simpleMessage('gelöscht'), @@ -39,49 +51,64 @@ class MessageLookup extends MessageLookupByLibrary { "promoteMenu": MessageLookupByLibrary.simpleMessage("Befördern"), "contributorUser": MessageLookupByLibrary.simpleMessage(" - User"), "contributorAdmin": MessageLookupByLibrary.simpleMessage(" - Admin"), - "genericErrorMessageSnackbar": MessageLookupByLibrary.simpleMessage("Etwas Unerwartetes ist passiert!\n "), //\${z.error} + "genericErrorMessageSnackbar": + MessageLookupByLibrary.simpleMessage("Etwas Unerwartetes ist passiert!\n "), //\${z.error} "nameOfNewContributorHint": MessageLookupByLibrary.simpleMessage("Name des neuen Teilnehmers"), "wasRemovedSuccessfullyMessage": MessageLookupByLibrary.simpleMessage(" wurde erfolgreich gelöscht"), - "loginSuccessfulMessage": MessageLookupByLibrary.simpleMessage("Login erfolgreich"), + "loginSuccessfullMessage": MessageLookupByLibrary.simpleMessage("Login erfolgreich, die Listen werden geladen"), "nameEmailRequiredError": MessageLookupByLibrary.simpleMessage("Name oder EMail wird benötigt."), - "usernameToShortError": MessageLookupByLibrary.simpleMessage("Der Benutzername muss aus mindestens 4 Zeichen bestehen"), + "usernameToShortError": + MessageLookupByLibrary.simpleMessage("Der Benutzername muss aus mindestens 4 Zeichen bestehen"), "emailRequiredError": MessageLookupByLibrary.simpleMessage("EMail ist erforderlich"), - "emailIncorrectFormatError": MessageLookupByLibrary.simpleMessage("Die EMail-Adresse scheint ein falsches Format zu haben"), + "emailIncorrectFormatError": + MessageLookupByLibrary.simpleMessage("Die EMail-Adresse scheint ein falsches Format zu haben"), "chooseAPassword": MessageLookupByLibrary.simpleMessage("Bitte ein Passwort eingeben"), "login": MessageLookupByLibrary.simpleMessage("Login"), - "usernameOrEmailForLoginHint": MessageLookupByLibrary.simpleMessage("Benutzername oder EMail kann für's einloggen genutzt werden"), + "usernameOrEmailForLoginHint": + MessageLookupByLibrary.simpleMessage("Benutzername oder EMail kann für's einloggen genutzt werden"), "usernameOrEmailTitle": MessageLookupByLibrary.simpleMessage("Benutzername oder EMail"), "emailTitle": MessageLookupByLibrary.simpleMessage('EMail'), "choosenPasswordHint": MessageLookupByLibrary.simpleMessage("Dein gewähltes Passwort"), "password": MessageLookupByLibrary.simpleMessage("Passwort"), "loginButton": MessageLookupByLibrary.simpleMessage("LOGIN"), - "registerTextOnLogin": MessageLookupByLibrary.simpleMessage("Du hast noch keinen Account? Erstelle jetzt einen."), + "registerTextOnLogin": + MessageLookupByLibrary.simpleMessage("Du hast noch keinen Account? Erstelle jetzt einen."), "usernameEmptyError": MessageLookupByLibrary.simpleMessage("Benutzername muss ausgefüllt sein"), "passwordEmptyError": MessageLookupByLibrary.simpleMessage("Passwort muss ausgefüllt sein"), + "passwordTooShortError": MessageLookupByLibrary.simpleMessage("Passwort muss mindestens 6 Zeichen lang sein"), + "passwordMissingCharactersError": MessageLookupByLibrary.simpleMessage( + "Passwort muss mindestens ein spezielles Zeichen (Kann jedes Symbol/Emoji sein) und Buchstabe oder Zahl enthalten"), + "emailEmptyError": MessageLookupByLibrary.simpleMessage("EMail muss ausgefüllt sein"), - "reenterPasswordError": MessageLookupByLibrary.simpleMessage("Die Passwörter stimmen nicht überein oder sind leer"), + "reenterPasswordError": + MessageLookupByLibrary.simpleMessage("Die Passwörter stimmen nicht überein oder sind leer"), "unknownUsernameError": MessageLookupByLibrary.simpleMessage("Es stimmt etwas mit deinem Benutzername nicht"), "unknownEmailError": MessageLookupByLibrary.simpleMessage("Es stimmt etwas mit deiner EMail nicht"), "unknownPasswordError": MessageLookupByLibrary.simpleMessage("Es stimmt etwas mit deinem Passwort nicht"), - "unknownReenterPasswordError": MessageLookupByLibrary.simpleMessage("Es stimmt etwas mit dem wiederholten Passwort nicht"), + "unknownReenterPasswordError": + MessageLookupByLibrary.simpleMessage("Es stimmt etwas mit dem wiederholten Passwort nicht"), "registrationSuccessfulMessage": MessageLookupByLibrary.simpleMessage("Registrierung erfolgreich"), "registrationTitle": MessageLookupByLibrary.simpleMessage("Registrierung"), "nameEmptyError": MessageLookupByLibrary.simpleMessage("Name ist erforderlich"), "chooseAPasswordPrompt": MessageLookupByLibrary.simpleMessage("Bitte gib ein Passwort ein"), "reenterPasswordPrompt": MessageLookupByLibrary.simpleMessage("Bitte gib dein Passwort erneut ein"), "passwordsDontMatchError": MessageLookupByLibrary.simpleMessage("Die Passwörter stimmen nicht überein"), - "usernameRegisterHint": MessageLookupByLibrary.simpleMessage("Kann zum einloggen und zum gefunden werden genutzt werden"), + "usernameRegisterHint": + MessageLookupByLibrary.simpleMessage("Kann zum einloggen und zum gefunden werden genutzt werden"), "username": MessageLookupByLibrary.simpleMessage("Benutzername"), - "emailRegisterHint": MessageLookupByLibrary.simpleMessage("Kann zum einloggen und zum gefunden werden genutzt werden"), + "emailRegisterHint": + MessageLookupByLibrary.simpleMessage("Kann zum einloggen und zum gefunden werden genutzt werden"), "passwordRegisterHint": MessageLookupByLibrary.simpleMessage("Das Passwort schützt deinen Account"), - "retypePasswordHint": MessageLookupByLibrary.simpleMessage("Bitte wiederhole dein Passwort um Fehler zu vermeiden"), + "retypePasswordHint": + MessageLookupByLibrary.simpleMessage("Bitte wiederhole dein Passwort um Fehler zu vermeiden"), "retypePasswordTitle": MessageLookupByLibrary.simpleMessage("Passwortwiederholung"), "registerButton": MessageLookupByLibrary.simpleMessage("REGISTRIEREN"), "discardNewProduct": MessageLookupByLibrary.simpleMessage("Änderungen verwerfen?"), "cancelButton": MessageLookupByLibrary.simpleMessage("ABBRECHEN"), "acceptButton": MessageLookupByLibrary.simpleMessage('ANNEHMEN'), "discardButton": MessageLookupByLibrary.simpleMessage("VERWERFEN"), - "fixErrorsBeforeSubmittingPrompt": MessageLookupByLibrary.simpleMessage("Bitte behebe die Fehler, gekennzeichnet in Rot"), + "fixErrorsBeforeSubmittingPrompt": + MessageLookupByLibrary.simpleMessage("Bitte behebe die Fehler, gekennzeichnet in Rot"), "newProductTitle": MessageLookupByLibrary.simpleMessage("Neues Produkt"), "saveButton": MessageLookupByLibrary.simpleMessage("SPEICHERN"), "newProductName": MessageLookupByLibrary.simpleMessage("Produktname *"), @@ -96,9 +123,11 @@ class MessageLookup extends MessageLookupByLibrary { "fieldRequiredError": MessageLookupByLibrary.simpleMessage("Dieses Feld wird benötigt!"), "newProductNameToShort": MessageLookupByLibrary.simpleMessage("Dieser Name scheint zu kurz zu sein"), "addedProduct": MessageLookupByLibrary.simpleMessage(' hinzugefügt'), //"\$name" - "productWasAlreadyInList": MessageLookupByLibrary.simpleMessage(' ist bereits in der Liste. Die Menge wurde um 1 erhöht'), //"\$name" ist + "productWasAlreadyInList": MessageLookupByLibrary.simpleMessage( + ' ist bereits in der Liste. Die Menge wurde um 1 erhöht'), //"\$name" ist "searchProductHint": MessageLookupByLibrary.simpleMessage("Produktsuche"), - "noMoreProductsMessage": MessageLookupByLibrary.simpleMessage("Es konnten keine weiteren Produkte mit dem Namen gefunden werden"), + "noMoreProductsMessage": + MessageLookupByLibrary.simpleMessage("Es konnten keine weiteren Produkte mit dem Namen gefunden werden"), "codeText": MessageLookupByLibrary.simpleMessage("Code: "), "removed": MessageLookupByLibrary.simpleMessage("entfernt"), "changePrimaryColor": MessageLookupByLibrary.simpleMessage("Hauptfarbe"), @@ -128,11 +157,29 @@ class MessageLookup extends MessageLookupByLibrary { "bePatient": MessageLookupByLibrary.simpleMessage('Der Server bearbeitet diese Anfrage bereits'), "logout": MessageLookupByLibrary.simpleMessage('Ausloggen'), "deleteListTitle": MessageLookupByLibrary.simpleMessage('Lösche Liste '), - "deleteListText": MessageLookupByLibrary.simpleMessage('Soll diese Liste wirklich gelöscht werden? Das kann NICHT rückgängig gemacht werden!'), + "deleteListText": MessageLookupByLibrary.simpleMessage( + 'Soll diese Liste wirklich gelöscht werden? Das kann NICHT rückgängig gemacht werden!'), "exportAsPdf": MessageLookupByLibrary.simpleMessage('Als PDF exportieren'), "boughtProducts": MessageLookupByLibrary.simpleMessage('Eingekauft'), "nothingBoughtYet": MessageLookupByLibrary.simpleMessage('Noch nichts eingekauft'), "reorderItems": MessageLookupByLibrary.simpleMessage('Reihenfolge'), - "refresh": MessageLookupByLibrary.simpleMessage('Aktualisieren') + "refresh": MessageLookupByLibrary.simpleMessage('Aktualisieren'), + "okayButton": MessageLookupByLibrary.simpleMessage('OKAY'), + "requestPasswordResetButton": MessageLookupByLibrary.simpleMessage("PASSWORT ZURÜCKSETZUNG BEANTRAGEN"), + "requestPasswordResetTitle": MessageLookupByLibrary.simpleMessage("Passwort zurücksetzen"), + "requestPasswordResetSuccess": MessageLookupByLibrary.simpleMessage( + 'Die Passwort zurücksetzen Email wurde erfolgreich an die Adresse gesendet, sollte diese existieren. Weitere Schritte für das abschließen des Resets sind in der Email enthalten.'), + "settings": MessageLookupByLibrary.simpleMessage('Einstellungen'), + "about": MessageLookupByLibrary.simpleMessage('Über'), + "codeOnGithub": MessageLookupByLibrary.simpleMessage('Schau doch mal in den Code auf GitHub rein'), + "playstoreEntry": MessageLookupByLibrary.simpleMessage('Play Store Eintrag'), + "iconSource": MessageLookupByLibrary.simpleMessage('Wer hat dieses schicke Icon gemacht? Finde es heraus!'), + "scanditCredit": + MessageLookupByLibrary.simpleMessage('hat diesen super Scanner in der App zur Verfügung gestellt'), + "aboutText": MessageLookupByLibrary.simpleMessage( + 'In jahrelanger Handarbeit geschmiedet mit dem einzigen Ziel, die Einkaufsplanung mit anderen zu vereinfachen und dabei seine Lieblingsprodukte blitzschnell per Kamera zu erfassen.'), + "freeText": MessageLookupByLibrary.simpleMessage('Kostenlos, Werbefrei, für immer!'), + "questionsErrors": MessageLookupByLibrary.simpleMessage( + 'Bei Fragen, Anregungen, Fehlern oder sonstigen Belangen kann jederzeit auf GitHub vorbeigeschaut werden, um ein Issue zu eröffnen.'), }; } diff --git a/lib/localization/nssl_messages_en.dart b/lib/localization/nssl_messages_en.dart index 336c2d5..5c6cd0e 100644 --- a/lib/localization/nssl_messages_en.dart +++ b/lib/localization/nssl_messages_en.dart @@ -18,18 +18,31 @@ class MessageLookup extends MessageLookupByLibrary { "rename": MessageLookupByLibrary.simpleMessage("Rename"), "remove": MessageLookupByLibrary.simpleMessage("Remove"), "addProduct": MessageLookupByLibrary.simpleMessage('Add Product'), - "addProductWithoutSearch": MessageLookupByLibrary.simpleMessage('Insert the name of the product, without searching in the database'), + "addProductWithoutSearch": + MessageLookupByLibrary.simpleMessage('Insert the name of the product, without searching in the database'), "productName": MessageLookupByLibrary.simpleMessage('product name'), "messageDeleteAllCrossedOut": MessageLookupByLibrary.simpleMessage("You have deleted all crossed out items"), "undo": MessageLookupByLibrary.simpleMessage("UNDO"), - "removedShoppingListMessage": MessageLookupByLibrary.simpleMessage(" removed"), //"Removed \${User.shoppingLists} " + "removedShoppingListMessage": + MessageLookupByLibrary.simpleMessage(" removed"), //"Removed \${User.shoppingLists} " "noListsInDrawerMessage": MessageLookupByLibrary.simpleMessage("here is the place for your lists"), "notLoggedInYet": MessageLookupByLibrary.simpleMessage("Not logged in yet"), "newNameOfListHint": MessageLookupByLibrary.simpleMessage('The new name of the new list'), "listName": MessageLookupByLibrary.simpleMessage('listname'), "renameListTitle": MessageLookupByLibrary.simpleMessage("Rename List"), "renameListHint": MessageLookupByLibrary.simpleMessage('The name of the new list'), + "chooseListToAddTitle": MessageLookupByLibrary.simpleMessage('Which list to add?'), "addNewListTitle": MessageLookupByLibrary.simpleMessage("Add new List"), + "recipeCreateError": MessageLookupByLibrary.simpleMessage("Could not create recipe"), + "recipeFromShareTitle": MessageLookupByLibrary.simpleMessage("To which list to add?"), + "recipeFromShareNew": MessageLookupByLibrary.simpleMessage("NEW"), + "recipeName": MessageLookupByLibrary.simpleMessage("Recipe"), + "recipeNameHint": MessageLookupByLibrary.simpleMessage("Recipe ID or URL"), + "addNewRecipeTitle": MessageLookupByLibrary.simpleMessage("Add new recipe"), + "importNewRecipe": MessageLookupByLibrary.simpleMessage("Import recipe"), + "importNewRecipeTitle": MessageLookupByLibrary.simpleMessage("Import new recipe"), + "chooseAddListDialog": MessageLookupByLibrary.simpleMessage("Shopping"), + "chooseAddRecipeDialog": MessageLookupByLibrary.simpleMessage("Chefkoch"), "youHaveActionItemMessage": MessageLookupByLibrary.simpleMessage('You have '), //\$action \$item "archived": MessageLookupByLibrary.simpleMessage('archived'), "deleted": MessageLookupByLibrary.simpleMessage('deleted'), @@ -43,9 +56,11 @@ class MessageLookup extends MessageLookupByLibrary { "wasRemovedSuccessfullyMessage": MessageLookupByLibrary.simpleMessage(" was removed successfully"), "loginSuccessfullMessage": MessageLookupByLibrary.simpleMessage("Login successfull."), "nameEmailRequiredError": MessageLookupByLibrary.simpleMessage('Name or Email is required.'), - "usernameToShortError": MessageLookupByLibrary.simpleMessage('Your username has to be at least 4 characters long'), + "usernameToShortError": + MessageLookupByLibrary.simpleMessage('Your username has to be at least 4 characters long'), "emailRequiredError": MessageLookupByLibrary.simpleMessage('EMail is required.'), - "emailIncorrectFormatError": MessageLookupByLibrary.simpleMessage('The email seems to be in the incorrect format.'), + "emailIncorrectFormatError": + MessageLookupByLibrary.simpleMessage('The email seems to be in the incorrect format.'), "chooseAPassword": MessageLookupByLibrary.simpleMessage('Please choose a password.'), "login": MessageLookupByLibrary.simpleMessage("Login"), "usernameOrEmailForLoginHint": MessageLookupByLibrary.simpleMessage('Username or email can be used to login'), @@ -57,12 +72,16 @@ class MessageLookup extends MessageLookupByLibrary { "registerTextOnLogin": MessageLookupByLibrary.simpleMessage("Don't have an account? Create one now."), "usernameEmptyError": MessageLookupByLibrary.simpleMessage("Username has to be filled in"), "passwordEmptyError": MessageLookupByLibrary.simpleMessage("Password has to be filled in"), + "passwordTooShortError": MessageLookupByLibrary.simpleMessage("Password has to be at least 6 charactes long"), + "passwordMissingCharactersError": MessageLookupByLibrary.simpleMessage( + "Password has to contain a special character (Can be emoji or any other symbol) and a letter or number"), "emailEmptyError": MessageLookupByLibrary.simpleMessage("Email has to be filled in"), "reenterPasswordError": MessageLookupByLibrary.simpleMessage("Passwords doesn't match or are empty"), "unknownUsernameError": MessageLookupByLibrary.simpleMessage("There is something wrong with your username"), "unknownEmailError": MessageLookupByLibrary.simpleMessage("There is something wrong with your email"), "unknownPasswordError": MessageLookupByLibrary.simpleMessage("There is something wrong with your password"), - "unknownReenterPasswordError": MessageLookupByLibrary.simpleMessage("There is something wrong with your password validation"), + "unknownReenterPasswordError": + MessageLookupByLibrary.simpleMessage("There is something wrong with your password validation"), "registrationSuccessfulMessage": MessageLookupByLibrary.simpleMessage("Registration successfull."), "registrationTitle": MessageLookupByLibrary.simpleMessage("Registration"), "nameEmptyError": MessageLookupByLibrary.simpleMessage('Name is required.'), @@ -80,7 +99,8 @@ class MessageLookup extends MessageLookupByLibrary { "cancelButton": MessageLookupByLibrary.simpleMessage('CANCEL'), "acceptButton": MessageLookupByLibrary.simpleMessage('ACCEPT'), "discardButton": MessageLookupByLibrary.simpleMessage('DISCARD'), - "fixErrorsBeforeSubmittingPrompt": MessageLookupByLibrary.simpleMessage('Please fix the errors in red before submitting.'), + "fixErrorsBeforeSubmittingPrompt": + MessageLookupByLibrary.simpleMessage('Please fix the errors in red before submitting.'), "newProductTitle": MessageLookupByLibrary.simpleMessage('New Product'), "saveButton": MessageLookupByLibrary.simpleMessage('SAVE'), "newProductName": MessageLookupByLibrary.simpleMessage("Product Name *"), @@ -95,7 +115,8 @@ class MessageLookup extends MessageLookupByLibrary { "fieldRequiredError": MessageLookupByLibrary.simpleMessage("This field is required!"), "newProductNameToShort": MessageLookupByLibrary.simpleMessage("This name seems to be to short"), "addedProduct": MessageLookupByLibrary.simpleMessage(' added'), //'Added "\$name"' - "productWasAlreadyInList": MessageLookupByLibrary.simpleMessage(' was already in list. The amount was increased by 1'), //"\$name" was + "productWasAlreadyInList": + MessageLookupByLibrary.simpleMessage(' was already in list. The amount was increased by 1'), //"\$name" was "searchProductHint": MessageLookupByLibrary.simpleMessage("Search Product"), "noMoreProductsMessage": MessageLookupByLibrary.simpleMessage("No more products found!}"), "codeText": MessageLookupByLibrary.simpleMessage("Code: "), @@ -116,22 +137,30 @@ class MessageLookup extends MessageLookupByLibrary { "successful": MessageLookupByLibrary.simpleMessage("Successful"), "passwordSet": MessageLookupByLibrary.simpleMessage("Your password has been set"), "tokenExpired": MessageLookupByLibrary.simpleMessage("Token expired"), - "tokenExpiredExplanation": - MessageLookupByLibrary.simpleMessage("Your token has expired. Login is required. If this happends multiple times per month, please contact us."), + "tokenExpiredExplanation": MessageLookupByLibrary.simpleMessage( + "Your token has expired. Login is required. If this happends multiple times per month, please contact us."), "noListLoaded": MessageLookupByLibrary.simpleMessage("No List Loaded"), "renameListItem": MessageLookupByLibrary.simpleMessage("Rename Product"), "renameListItemHint": MessageLookupByLibrary.simpleMessage("The new name of the product"), "renameListItemLabel": MessageLookupByLibrary.simpleMessage("new product name"), "discardNewTheme": MessageLookupByLibrary.simpleMessage('Discard new theme?'), "forgotPassword": MessageLookupByLibrary.simpleMessage('Forgot password?'), - "bePatient": MessageLookupByLibrary.simpleMessage('Please be patient, the server is processing your request already'), + "bePatient": + MessageLookupByLibrary.simpleMessage('Please be patient, the server is processing your request already'), "logout": MessageLookupByLibrary.simpleMessage('Logout'), "deleteListTitle": MessageLookupByLibrary.simpleMessage('Delete List '), - "deleteListText": MessageLookupByLibrary.simpleMessage('Do you really want to delete the list? This CAN\'T be undone!'), + "deleteListText": + MessageLookupByLibrary.simpleMessage('Do you really want to delete the list? This CAN\'T be undone!'), "exportAsPdf": MessageLookupByLibrary.simpleMessage('Export as PDF'), "boughtProducts": MessageLookupByLibrary.simpleMessage('Bought Products'), "nothingBoughtYet": MessageLookupByLibrary.simpleMessage('Nothing bought yet'), "reorderItems": MessageLookupByLibrary.simpleMessage('Reorder'), "refresh": MessageLookupByLibrary.simpleMessage('Refresh'), + "okayButton": MessageLookupByLibrary.simpleMessage('OKAY'), + "requestPasswordResetButton": MessageLookupByLibrary.simpleMessage("REQUEST PASSWORD RESET"), + "requestPasswordResetTitle": MessageLookupByLibrary.simpleMessage("Password Reset"), + "requestPasswordResetSuccess": MessageLookupByLibrary.simpleMessage( + 'If the email exists, the password request was successfully requested. Further instructions can be found in the email, that was send to the address.'), + "settings": MessageLookupByLibrary.simpleMessage('Settings'), }; } diff --git a/lib/localization/nssl_strings.dart b/lib/localization/nssl_strings.dart index b6af7bf..fc17aa2 100644 --- a/lib/localization/nssl_strings.dart +++ b/lib/localization/nssl_strings.dart @@ -11,298 +11,231 @@ class NSSLStrings { static Future load(Locale locale) { return initializeMessages(locale.toString()).then((dynamic _) { - // ignore: strong_mode_uses_dynamic_as_bottom return NSSLStrings(locale); + // ignore: strong_mode_uses_dynamic_as_bottom }); } - static NSSLStrings? of(BuildContext? context) { - if (context == null) return null; - - return Localizations.of(context, NSSLStrings); + static NSSLStrings of(BuildContext context) { + return Localizations.of(context, NSSLStrings)!; } // static final NSSLStrings instance = NSSLStrings(); - String options() => - Intl.message('Options', name: 'options', locale: _localeName); - String changeTheme() => - Intl.message('Change Theme', name: 'changeTheme', locale: _localeName); + String options() => Intl.message('Options', name: 'options', locale: _localeName); + String changeTheme() => Intl.message('Change Theme', name: 'changeTheme', locale: _localeName); String scanPB() => Intl.message('SCAN', name: 'scanPB', locale: _localeName); String addPB() => Intl.message('ADD', name: 'addPB', locale: _localeName); - String searchPB() => - Intl.message('SEARCH', name: 'searchPB', locale: _localeName); - String deleteCrossedOutPB() => Intl.message('DELETE CROSSED OUT', - name: 'deleteCrossedOutPB', locale: _localeName); - String addListPB() => - Intl.message('ADD LIST', name: 'addListPB', locale: _localeName); - String contributors() => - Intl.message('Contributors', name: 'contributors', locale: _localeName); - String rename() => - Intl.message('Rename', name: 'rename', locale: _localeName); - String remove() => - Intl.message('Remove', name: 'remove', locale: _localeName); - String addProduct() => - Intl.message('Add Product', name: 'addProduct', locale: _localeName); - String addProductWithoutSearch() => Intl.message( - 'Insert the name of the product, without searching in the database', - name: 'addProductWithoutSearch', - locale: _localeName); - String productName() => - Intl.message('Product name', name: 'productName', locale: _localeName); + String searchPB() => Intl.message('SEARCH', name: 'searchPB', locale: _localeName); + String deleteCrossedOutPB() => Intl.message('DELETE CROSSED OUT', name: 'deleteCrossedOutPB', locale: _localeName); + String addListPB() => Intl.message('ADD LIST', name: 'addListPB', locale: _localeName); + String contributors() => Intl.message('Contributors', name: 'contributors', locale: _localeName); + String rename() => Intl.message('Rename', name: 'rename', locale: _localeName); + String remove() => Intl.message('Remove', name: 'remove', locale: _localeName); + String addProduct() => Intl.message('Add Product', name: 'addProduct', locale: _localeName); + String addProductWithoutSearch() => Intl.message('Insert the name of the product, without searching in the database', + name: 'addProductWithoutSearch', locale: _localeName); + String productName() => Intl.message('Product name', name: 'productName', locale: _localeName); String messageDeleteAllCrossedOut() => - Intl.message('You have deleted all crossed out items', - name: 'messageDeleteAllCrossedOut', locale: _localeName); + Intl.message('You have deleted all crossed out items', name: 'messageDeleteAllCrossedOut', locale: _localeName); String undo() => Intl.message('UNDO', name: 'undo', locale: _localeName); String noListsInDrawerMessage() => - Intl.message('Here is the place for your lists', - name: 'noListsInDrawerMessage', locale: _localeName); - String notLoggedInYet() => Intl.message('Not logged in yet', - name: 'notLoggedInYet', locale: _localeName); - String newNameOfListHint() => Intl.message('The new name of the new list', - name: 'newNameOfListHint', locale: _localeName); - String listName() => - Intl.message('Listname', name: 'listName', locale: _localeName); - String renameListTitle() => - Intl.message('Rename List', name: 'renameListTitle', locale: _localeName); - String renameListHint() => Intl.message('The name of the new list', - name: 'renameListHint', locale: _localeName); - String addNewListTitle() => Intl.message('Add new List', - name: 'addNewListTitle', locale: _localeName); - String youHaveActionItemMessage() => Intl.message('You have ', - name: 'youHaveActionItemMessage', locale: _localeName); - String archived() => - Intl.message('archived', name: 'archived', locale: _localeName); - String deleted() => - Intl.message('deleted', name: 'deleted', locale: _localeName); - String youHaveActionNameMessage() => Intl.message('You have ', - name: 'youHaveActionNameMessage', locale: _localeName); - String demoteMenu() => - Intl.message('Demote', name: 'demoteMenu', locale: _localeName); - String promoteMenu() => - Intl.message('Promote', name: 'promoteMenu', locale: _localeName); - String contributorUser() => - Intl.message(" - User", name: 'contributorUser', locale: _localeName); - String contributorAdmin() => - Intl.message(" - Admin", name: 'contributorAdmin', locale: _localeName); + Intl.message('Here is the place for your lists', name: 'noListsInDrawerMessage', locale: _localeName); + String notLoggedInYet() => Intl.message('Not logged in yet', name: 'notLoggedInYet', locale: _localeName); + String newNameOfListHint() => + Intl.message('The new name of the new list', name: 'newNameOfListHint', locale: _localeName); + String listName() => Intl.message('Listname', name: 'listName', locale: _localeName); + String renameListTitle() => Intl.message('Rename List', name: 'renameListTitle', locale: _localeName); + String renameListHint() => Intl.message('The name of the new list', name: 'renameListHint', locale: _localeName); + String chooseListToAddTitle() => + Intl.message('Which list to add?', name: 'chooseListToAddTitle', locale: _localeName); + String addNewListTitle() => Intl.message('Add new List', name: 'addNewListTitle', locale: _localeName); + String recipeCreateError() => Intl.message('Could not create recipe', name: 'recipeCreateError', locale: _localeName); + String recipeFromShareTitle() => + Intl.message('To which list to add?', name: 'recipeFromShareTitle', locale: _localeName); + String recipeFromShareNew() => Intl.message('NEW', name: 'recipeFromShareNew', locale: _localeName); + String recipeName() => Intl.message('Recipe', name: 'recipeName', locale: _localeName); + String recipeNameHint() => Intl.message('Recipe ID or URL', name: 'recipeNameHint', locale: _localeName); + String addNewRecipeTitle() => Intl.message('Add new recipe', name: 'addNewRecipeTitle', locale: _localeName); + String importNewRecipe() => Intl.message('Import recipe', name: 'importNewRecipe', locale: _localeName); + String importNewRecipeTitle() => Intl.message('Import new recipe', name: 'importNewRecipeTitle', locale: _localeName); + String chooseAddListDialog() => Intl.message('Shopping', name: 'chooseAddListDialog', locale: _localeName); + String chooseAddRecipeDialog() => Intl.message('Chefkoch', name: 'chooseAddRecipeDialog', locale: _localeName); + String youHaveActionItemMessage() => Intl.message('You have ', name: 'youHaveActionItemMessage', locale: _localeName); + String archived() => Intl.message('archived', name: 'archived', locale: _localeName); + String deleted() => Intl.message('deleted', name: 'deleted', locale: _localeName); + String youHaveActionNameMessage() => Intl.message('You have ', name: 'youHaveActionNameMessage', locale: _localeName); + String demoteMenu() => Intl.message('Demote', name: 'demoteMenu', locale: _localeName); + String promoteMenu() => Intl.message('Promote', name: 'promoteMenu', locale: _localeName); + String contributorUser() => Intl.message(" - User", name: 'contributorUser', locale: _localeName); + String contributorAdmin() => Intl.message(" - Admin", name: 'contributorAdmin', locale: _localeName); String genericErrorMessageSnackbar() => - Intl.message('Something went wrong!\n', - name: 'genericErrorMessageSnackbar', locale: _localeName); - String nameOfNewContributorHint() => Intl.message('Name of new Contributor', - name: 'nameOfNewContributorHint', locale: _localeName); + Intl.message('Something went wrong!\n', name: 'genericErrorMessageSnackbar', locale: _localeName); + String nameOfNewContributorHint() => + Intl.message('Name of new Contributor', name: 'nameOfNewContributorHint', locale: _localeName); String wasRemovedSuccessfullyMessage() => - Intl.message(' was removed successfully', - name: 'wasRemovedSuccessfullyMessage', locale: _localeName); - String loginSuccessfulMessage() => Intl.message('Login successfull.', - name: 'loginSuccessfullMessage', locale: _localeName); - String nameEmailRequiredError() => Intl.message('Name or Email is required.', - name: 'nameEmailRequiredError', locale: _localeName); - String usernameToShortError() => - Intl.message('Your username has to be at least 4 characters long', - name: 'usernameToShortError', locale: _localeName); - String emailRequiredError() => Intl.message('EMail is required.', - name: 'emailRequiredError', locale: _localeName); - String emailIncorrectFormatError() => - Intl.message('The email seems to be in the incorrect format.', - name: 'emailIncorrectFormatError', locale: _localeName); - String chooseAPassword() => Intl.message('Please choose a password.', - name: 'chooseAPassword', locale: _localeName); + Intl.message(' was removed successfully', name: 'wasRemovedSuccessfullyMessage', locale: _localeName); + String loginSuccessfulMessage() => + Intl.message('Login successfull, lists will be loaded.', name: 'loginSuccessfullMessage', locale: _localeName); + String nameEmailRequiredError() => + Intl.message('Name or Email is required.', name: 'nameEmailRequiredError', locale: _localeName); + String usernameToShortError() => Intl.message('Your username has to be at least 4 characters long', + name: 'usernameToShortError', locale: _localeName); + String emailRequiredError() => Intl.message('EMail is required.', name: 'emailRequiredError', locale: _localeName); + String emailIncorrectFormatError() => Intl.message('The email seems to be in the incorrect format.', + name: 'emailIncorrectFormatError', locale: _localeName); + String chooseAPassword() => Intl.message('Please choose a password.', name: 'chooseAPassword', locale: _localeName); String login() => Intl.message('Login', name: 'login', locale: _localeName); String usernameOrEmailForLoginHint() => - Intl.message('Username or email can be used to login', - name: 'usernameOrEmailForLoginHint', locale: _localeName); - String usernameOrEmailTitle() => Intl.message('Username or Email', - name: 'usernameOrEmailTitle', locale: _localeName); - String emailTitle() => - Intl.message('Email', name: 'emailTitle', locale: _localeName); - String choosenPasswordHint() => Intl.message('The password you have choosen', - name: 'choosenPasswordHint', locale: _localeName); - String password() => - Intl.message('Password', name: 'password', locale: _localeName); - String loginButton() => - Intl.message('LOGIN', name: 'loginButton', locale: _localeName); + Intl.message('Username or email can be used to login', name: 'usernameOrEmailForLoginHint', locale: _localeName); + String usernameOrEmailTitle() => Intl.message('Username or Email', name: 'usernameOrEmailTitle', locale: _localeName); + String emailTitle() => Intl.message('Email', name: 'emailTitle', locale: _localeName); + String choosenPasswordHint() => + Intl.message('The password you have choosen', name: 'choosenPasswordHint', locale: _localeName); + String password() => Intl.message('Password', name: 'password', locale: _localeName); + String loginButton() => Intl.message('LOGIN', name: 'loginButton', locale: _localeName); String registerTextOnLogin() => - Intl.message('Don\'t have an account? Create one now.', - name: 'registerTextOnLogin', locale: _localeName); - String usernameEmptyError() => Intl.message('Username has to be filled in', - name: 'usernameEmptyError', locale: _localeName); - String passwordEmptyError() => Intl.message('Password has to be filled in', - name: 'passwordEmptyError', locale: _localeName); - String emailEmptyError() => Intl.message('Email has to be filled in', - name: 'emailEmptyError', locale: _localeName); + Intl.message('Don\'t have an account? Create one now.', name: 'registerTextOnLogin', locale: _localeName); + String usernameEmptyError() => + Intl.message('Username has to be filled in', name: 'usernameEmptyError', locale: _localeName); + String passwordEmptyError() => + Intl.message('Password has to be filled in', name: 'passwordEmptyError', locale: _localeName); + String passwordTooShortError() => + Intl.message('Password has to be at least 6 charactes long', name: 'passwordTooShortError', locale: _localeName); + String passwordMissingCharactersError() => Intl.message( + 'Password has to contain a special character (Can be emoji or any other symbol) and a letter or number', + name: 'passwordMissingCharactersError', + locale: _localeName); + String emailEmptyError() => Intl.message('Email has to be filled in', name: 'emailEmptyError', locale: _localeName); String reenterPasswordError() => - Intl.message('Passwords doesn\'t match or are empty', - name: 'reenterPasswordError', locale: _localeName); + Intl.message('Passwords doesn\'t match or are empty', name: 'reenterPasswordError', locale: _localeName); String unknownUsernameError() => - Intl.message('There is something wrong with your username', - name: 'unknownUsernameError', locale: _localeName); + Intl.message('There is something wrong with your username', name: 'unknownUsernameError', locale: _localeName); String unknownEmailError() => - Intl.message('There is something wrong with your email', - name: 'unknownEmailError', locale: _localeName); + Intl.message('There is something wrong with your email', name: 'unknownEmailError', locale: _localeName); String unknownPasswordError() => - Intl.message('There is something wrong with your password', - name: 'unknownPasswordError', locale: _localeName); - String unknownReenterPasswordError() => - Intl.message('There is something wrong with your password validation', - name: 'unknownReenterPasswordError', locale: _localeName); + Intl.message('There is something wrong with your password', name: 'unknownPasswordError', locale: _localeName); + String unknownReenterPasswordError() => Intl.message('There is something wrong with your password validation', + name: 'unknownReenterPasswordError', locale: _localeName); String registrationSuccessfulMessage() => - Intl.message('Registration successfull.', - name: 'registrationSuccessfullMessage', locale: _localeName); - String registrationTitle() => Intl.message('Registration', - name: 'registrationTitle', locale: _localeName); - String nameEmptyError() => Intl.message('Name is required.', - name: 'nameEmptyError', locale: _localeName); - String chooseAPasswordPrompt() => Intl.message('Please choose a password.', - name: 'chooseAPasswordPrompt', locale: _localeName); + Intl.message('Registration successfull.', name: 'registrationSuccessfullMessage', locale: _localeName); + String registrationTitle() => Intl.message('Registration', name: 'registrationTitle', locale: _localeName); + String nameEmptyError() => Intl.message('Name is required.', name: 'nameEmptyError', locale: _localeName); + String chooseAPasswordPrompt() => + Intl.message('Please choose a password.', name: 'chooseAPasswordPrompt', locale: _localeName); String reenterPasswordPrompt() => - Intl.message('Please reenter your password.', - name: 'reenterPasswordPromt', locale: _localeName); - String passwordsDontMatchError() => Intl.message('Passwords don\'t match', - name: 'passwordsDontMatchError', locale: _localeName); + Intl.message('Please reenter your password.', name: 'reenterPasswordPromt', locale: _localeName); + String passwordsDontMatchError() => + Intl.message('Passwords don\'t match', name: 'passwordsDontMatchError', locale: _localeName); String usernameRegisterHint() => - Intl.message('The name to login and to be found by others', - name: 'usernameRegisterHint', locale: _localeName); - String username() => - Intl.message('Username', name: 'username', locale: _localeName); + Intl.message('The name to login and to be found by others', name: 'usernameRegisterHint', locale: _localeName); + String username() => Intl.message('Username', name: 'username', locale: _localeName); String emailRegisterHint() => - Intl.message('The email to login and to be found by others', - name: 'emailRegisterHint', locale: _localeName); + Intl.message('The email to login and to be found by others', name: 'emailRegisterHint', locale: _localeName); String passwordRegisterHint() => - Intl.message('The password to secure your account', - name: 'passwordRegisterHint', locale: _localeName); + Intl.message('The password to secure your account', name: 'passwordRegisterHint', locale: _localeName); String retypePasswordHint() => - Intl.message('Re-type your password for validation', - name: 'retypePasswordHint', locale: _localeName); - String retypePasswordTitle() => Intl.message('Re-type Password', - name: 'retypePasswordTitle', locale: _localeName); - String registerButton() => - Intl.message('REGISTER', name: 'registerButton', locale: _localeName); - String discardNewProduct() => Intl.message('Discard new product?', - name: 'discardNewProduct', locale: _localeName); - String cancelButton() => - Intl.message('CANCEL', name: 'cancelButton', locale: _localeName); - String acceptButton() => - Intl.message('ACCEPT', name: 'acceptButton', locale: _localeName); - String discardButton() => - Intl.message('DISCARD', name: 'discardButton', locale: _localeName); - String fixErrorsBeforeSubmittingPrompt() => - Intl.message('Please fix the errors in red before submitting.', - name: 'fixErrorsBeforeSubmittingPrompt', locale: _localeName); - String newProductTitle() => - Intl.message('New Product', name: 'newProductTitle', locale: _localeName); - String saveButton() => - Intl.message('SAVE', name: 'saveButton', locale: _localeName); - String newProductName() => Intl.message('Product Name *', - name: 'newProductName', locale: _localeName); - String newProductNameHint() => Intl.message('How is this product called?', - name: 'newProductNameHint', locale: _localeName); - String newProductBrandName() => Intl.message('Brand Name *', - name: 'newProductBrandName', locale: _localeName); + Intl.message('Re-type your password for validation', name: 'retypePasswordHint', locale: _localeName); + String retypePasswordTitle() => Intl.message('Re-type Password', name: 'retypePasswordTitle', locale: _localeName); + String registerButton() => Intl.message('REGISTER', name: 'registerButton', locale: _localeName); + String discardNewProduct() => Intl.message('Discard new product?', name: 'discardNewProduct', locale: _localeName); + String cancelButton() => Intl.message('CANCEL', name: 'cancelButton', locale: _localeName); + String acceptButton() => Intl.message('ACCEPT', name: 'acceptButton', locale: _localeName); + String discardButton() => Intl.message('DISCARD', name: 'discardButton', locale: _localeName); + String fixErrorsBeforeSubmittingPrompt() => Intl.message('Please fix the errors in red before submitting.', + name: 'fixErrorsBeforeSubmittingPrompt', locale: _localeName); + String newProductTitle() => Intl.message('New Product', name: 'newProductTitle', locale: _localeName); + String saveButton() => Intl.message('SAVE', name: 'saveButton', locale: _localeName); + String newProductName() => Intl.message('Product Name *', name: 'newProductName', locale: _localeName); + String newProductNameHint() => + Intl.message('How is this product called?', name: 'newProductNameHint', locale: _localeName); + String newProductBrandName() => Intl.message('Brand Name *', name: 'newProductBrandName', locale: _localeName); String newProductBrandNameHint() => - Intl.message('Which company sells this product?', - name: 'newProductBrandNameHint', locale: _localeName); - String newProductWeight() => - Intl.message('Weight', name: 'newProductWeight', locale: _localeName); + Intl.message('Which company sells this product?', name: 'newProductBrandNameHint', locale: _localeName); + String newProductWeight() => Intl.message('Weight', name: 'newProductWeight', locale: _localeName); String newProductWeightHint() => - Intl.message('What is the normal packaging size?', - name: 'newProductWeightHint', locale: _localeName); - String newProductAddToList() => Intl.message('Add to current list', - name: 'newProductAddToList', locale: _localeName); - String newProductAddedToList() => Intl.message(' added to list ', - name: 'newProductAddedToList', locale: _localeName); + Intl.message('What is the normal packaging size?', name: 'newProductWeightHint', locale: _localeName); + String newProductAddToList() => Intl.message('Add to current list', name: 'newProductAddToList', locale: _localeName); + String newProductAddedToList() => Intl.message(' added to list ', name: 'newProductAddedToList', locale: _localeName); String newProductStarExplanation() => - Intl.message('* indicates required field', - name: 'newProductStarExplanation', locale: _localeName); - String fieldRequiredError() => Intl.message('This field is required!', - name: 'fieldRequiredError', locale: _localeName); + Intl.message('* indicates required field', name: 'newProductStarExplanation', locale: _localeName); + String fieldRequiredError() => + Intl.message('This field is required!', name: 'fieldRequiredError', locale: _localeName); String newProductNameToShort() => - Intl.message('This name seems to be to short', - name: 'newProductNameToShort', locale: _localeName); - String addedProduct() => - Intl.message(' added', name: 'addedProduct', locale: _localeName); - String productWasAlreadyInList() => - Intl.message(' was already in list. The amount was increased by 1', - name: 'productWasAlreadyInList', locale: _localeName); - String searchProductHint() => Intl.message('Search Product', - name: 'searchProductHint', locale: _localeName); - String noMoreProductsMessage() => Intl.message('No more products found!', - name: 'noMoreProductsMessage', locale: _localeName); - String codeText() => - Intl.message('Code: ', name: 'codeText', locale: _localeName); - String removed() => - Intl.message('removed', name: 'removed', locale: _localeName); - String changePrimaryColor() => Intl.message('Primary Color', - name: 'changePrimaryColor', locale: _localeName); - String changeAccentColor() => Intl.message('Accent Color', - name: 'changeAccentColor', locale: _localeName); - String changeDarkTheme() => - Intl.message('Dark Theme', name: 'changeDarkTheme', locale: _localeName); - String changeAccentTextColor() => Intl.message('Dark Icons', - name: 'changeAccentTextColor', locale: _localeName); - String autoSync() => - Intl.message('Auto-Sync', name: 'autoSync', locale: _localeName); - String changePasswordButton() => Intl.message('CHANGE PASSWORD', - name: 'changePasswordButton', locale: _localeName); - String oldPassword() => Intl.message('Current password', - name: 'currentPassword', locale: _localeName); + Intl.message('This name seems to be to short', name: 'newProductNameToShort', locale: _localeName); + String addedProduct() => Intl.message(' added', name: 'addedProduct', locale: _localeName); + String productWasAlreadyInList() => Intl.message(' was already in list. The amount was increased by 1', + name: 'productWasAlreadyInList', locale: _localeName); + String searchProductHint() => Intl.message('Search Product', name: 'searchProductHint', locale: _localeName); + String noMoreProductsMessage() => + Intl.message('No more products found!', name: 'noMoreProductsMessage', locale: _localeName); + String codeText() => Intl.message('Code: ', name: 'codeText', locale: _localeName); + String removed() => Intl.message('removed', name: 'removed', locale: _localeName); + String changePrimaryColor() => Intl.message('Primary Color', name: 'changePrimaryColor', locale: _localeName); + String changeAccentColor() => Intl.message('Accent Color', name: 'changeAccentColor', locale: _localeName); + String changeDarkTheme() => Intl.message('Dark Theme', name: 'changeDarkTheme', locale: _localeName); + String changeAccentTextColor() => Intl.message('Dark Icons', name: 'changeAccentTextColor', locale: _localeName); + String autoSync() => Intl.message('Auto-Sync', name: 'autoSync', locale: _localeName); + String changePasswordButton() => Intl.message('CHANGE PASSWORD', name: 'changePasswordButton', locale: _localeName); + String oldPassword() => Intl.message('Current password', name: 'currentPassword', locale: _localeName); String oldPasswordHint() => - Intl.message('Current password that should be changed', - name: 'currentPasswordHint', locale: _localeName); - String newPassword() => - Intl.message('New password', name: 'newPassword', locale: _localeName); - String newPasswordHint() => Intl.message('The new password you have chosen', - name: 'newPasswordHint', locale: _localeName); - String new2Password() => Intl.message('Repeat new password', - name: 'repeatNewPassword', locale: _localeName); + Intl.message('Current password that should be changed', name: 'currentPasswordHint', locale: _localeName); + String newPassword() => Intl.message('New password', name: 'newPassword', locale: _localeName); + String newPasswordHint() => + Intl.message('The new password you have chosen', name: 'newPasswordHint', locale: _localeName); + String new2Password() => Intl.message('Repeat new password', name: 'repeatNewPassword', locale: _localeName); String new2PasswordHint() => - Intl.message('repeat the new password you have chosen', - name: 'repeatNewPasswordHint', locale: _localeName); - String changePasswordPD() => Intl.message('Change Password', - name: 'changePasswordPD', locale: _localeName); - String successful() => - Intl.message('Successful', name: 'successful', locale: _localeName); - String passwordSet() => Intl.message('Your password has been set', - name: 'passwordSet', locale: _localeName); - String tokenExpired() => - Intl.message('Token expired', name: 'tokenExpired', locale: _localeName); + Intl.message('repeat the new password you have chosen', name: 'repeatNewPasswordHint', locale: _localeName); + String changePasswordPD() => Intl.message('Change Password', name: 'changePasswordPD', locale: _localeName); + String successful() => Intl.message('Successful', name: 'successful', locale: _localeName); + String passwordSet() => Intl.message('Your password has been set', name: 'passwordSet', locale: _localeName); + String tokenExpired() => Intl.message('Token expired', name: 'tokenExpired', locale: _localeName); String tokenExpiredExplanation() => Intl.message( 'Your token has expired. Login is required. If this happends multiple times per month, please contact us.', name: 'tokenExpiredExplanation', locale: _localeName); - String noListLoaded() => - Intl.message('No List Loaded', name: 'noListLoaded', locale: _localeName); - String renameListItem() => Intl.message('Rename Product', - name: 'renameListItem', locale: _localeName); - String renameListItemHint() => Intl.message('The new name of the product', - name: 'renameListItemHint', locale: _localeName); - String renameListItemLabel() => Intl.message('new product name', - name: 'renameListItemLabel', locale: _localeName); - String discardNewTheme() => Intl.message('Discard new theme?', - name: 'discardNewTheme', locale: _localeName); - String forgotPassword() => Intl.message('Forgot password?', - name: 'forgotPassword', locale: _localeName); - String bePatient() => Intl.message( - 'Please be patient, the server is processing your request already', - name: 'bePatient', + String noListLoaded() => Intl.message('No List Loaded', name: 'noListLoaded', locale: _localeName); + String renameListItem() => Intl.message('Rename Product', name: 'renameListItem', locale: _localeName); + String renameListItemHint() => + Intl.message('The new name of the product', name: 'renameListItemHint', locale: _localeName); + String renameListItemLabel() => Intl.message('new product name', name: 'renameListItemLabel', locale: _localeName); + String discardNewTheme() => Intl.message('Discard new theme?', name: 'discardNewTheme', locale: _localeName); + String forgotPassword() => Intl.message('Forgot password?', name: 'forgotPassword', locale: _localeName); + String bePatient() => Intl.message('Please be patient, the server is processing your request already', + name: 'bePatient', locale: _localeName); + String logout() => Intl.message('Logout', name: 'logout', locale: _localeName); + String deleteListTitle() => Intl.message('Delete List', name: 'deleteListTitle', locale: _localeName); + String deleteListText() => Intl.message('Do you really want to delete the list? This CAN\'T be undone!', + name: 'deleteListText', locale: _localeName); + String exportAsPdf() => Intl.message('Export as PDF', name: 'exportAsPdf', locale: _localeName); + String boughtProducts() => Intl.message('Bought Products', name: 'boughtProducts', locale: _localeName); + String nothingBoughtYet() => Intl.message('Nothing bought yet', name: 'nothingBoughtYet', locale: _localeName); + String reorderItems() => Intl.message('Reorder', name: 'reorderItems', locale: _localeName); + + String refresh() => Intl.message('Refresh', name: "refresh", locale: _localeName); + String settings() => Intl.message('Settings', name: "settings", locale: _localeName); + String about() => Intl.message('About', name: "about", locale: _localeName); + String codeOnGithub() => Intl.message('View Source Code on GitHub', name: "codeOnGithub", locale: _localeName); + String playstoreEntry() => Intl.message('Entry on Play Store', name: "playstoreEntry", locale: _localeName); + String iconSource() => Intl.message('Source of the App Icon', name: "iconSource", locale: _localeName); + String scanditCredit() => + Intl.message('has provided the Scanner for this App', name: "scanditCredit", locale: _localeName); + String aboutText() => Intl.message( + 'Forged over many years with the only intention to make it easier for multiple people to plan their shopping and add their favorite products via camera in an unmatched speed.', + name: "aboutText", locale: _localeName); - String logout() => - Intl.message('Logout', name: 'logout', locale: _localeName); - String deleteListTitle() => - Intl.message('Delete List', name: 'deleteListTitle', locale: _localeName); - String deleteListText() => Intl.message( - 'Do you really want to delete the list? This CAN\'T be undone!', - name: 'deleteListText', + String freeText() => + Intl.message('Free of charge, no Advertisments, forever!', name: "freeText", locale: _localeName); + String questionsErrors() => Intl.message('Questions, Errors or everything else can be send by a GitHub issue.', + name: "questionsErrors", locale: _localeName); + String okayButton() => Intl.message('OKAY', name: "okayButton", locale: _localeName); + String requestPasswordResetButton() => + Intl.message('REQUEST PASSWORD RESET', name: 'requestPasswordResetButton', locale: _localeName); + String requestPasswordResetTitle() => + Intl.message('Password Reset', name: "requestPasswordResetTitle", locale: _localeName); + String requestPasswordResetSuccess() => Intl.message( + 'If the email exists, the password request was successfully requested. Further instructions can be found in the email, that was send to the address.', + name: "requestPasswordResetSuccess", locale: _localeName); - String exportAsPdf() => - Intl.message('Export as PDF', name: 'exportAsPdf', locale: _localeName); - String boughtProducts() => Intl.message('Bought Products', - name: 'boughtProducts', locale: _localeName); - String nothingBoughtYet() => Intl.message('Nothing bought yet', - name: 'nothingBoughtYet', locale: _localeName); - String reorderItems() => - Intl.message('Reorder', name: 'reorderItems', locale: _localeName); - String refresh() => - Intl.message('Refresh', name: "refresh", locale: _localeName); //String openAppDrawerTooltip() => Intl.message('Open navigation menu', name: 'openNavigationMenu', locale: _localeName); - } diff --git a/lib/main.dart b/lib/main.dart index a4130b9..2d0492f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,7 +4,10 @@ import 'dart:ui'; import 'package:adaptive_theme/adaptive_theme.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:nssl/options/themes.dart'; +import 'package:nssl/pages/forgot_password.dart'; import 'package:nssl/pages/pages.dart'; import 'package:nssl/manager/manager_export.dart'; import 'package:nssl/models/model_export.dart'; @@ -13,6 +16,7 @@ import 'dart:async'; import 'package:nssl/localization/nssl_strings.dart'; import 'package:nssl/firebase/cloud_messsaging.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; // import 'package:shared_preferences/shared_preferences.dart'; class CustomScrollBehavior extends MaterialScrollBehavior { @@ -24,31 +28,45 @@ class CustomScrollBehavior extends MaterialScrollBehavior { }; } +@pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - // If you're going to use other Firebase services in the background, such as Firestore, - // make sure you call `initializeApp` before using other Firebase services. await Startup.initializeMinFunction(); - //Startup.remoteMessages.add(message); - var dir = - await Startup.fs.systemTempDirectory.childDirectory("message").create(); + var dir = await Startup.fs.systemTempDirectory.childDirectory("message").create(); var file = dir.childFile(DateTime.now().microsecondsSinceEpoch.toString()); await file.writeAsString(jsonEncode(message.data)); } +final appRestartProvider = StateProvider( + (ref) => 0, +); + Future main() async { // iWonderHowLongThisTakes(); - - runApp(FutureBuilder( - builder: (c, t) { - if (t.connectionState == ConnectionState.done) - return NSSLPage(); - else - return Container(color: Colors.green); + runApp(ProviderScope(child: Consumer( + builder: (context, ref, child) { + return FutureBuilder( + builder: (c, t) { + if (t.connectionState == ConnectionState.done) { + ref.watch(appRestartProvider); + return NSSLPage(); + } else + return MaterialApp( + builder: (context, child) { + return Center( + child: SizedBox( + height: 200, + width: 200, + child: SvgPicture.asset("assets/vectors/app_icon.svg"), + ), + ); + }, + ); + }, + future: Startup.initialize(ref) + .then((value) => FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler)), + ); }, - future: Startup.initialize().then((value) => - FirebaseMessaging.onBackgroundMessage( - _firebaseMessagingBackgroundHandler)), - )); + ))); } class NSSL extends StatelessWidget { @@ -58,19 +76,14 @@ class NSSL extends StatelessWidget { } } -class NSSLPage extends StatefulWidget { +class NSSLPage extends ConsumerStatefulWidget { NSSLPage({Key? key}) : super(key: key); - static _NSSLState? state; @override - _NSSLState createState() { - var localState = new _NSSLState(); - state = localState; - return localState; - } + _NSSLState createState() => _NSSLState(); } -class _NSSLState extends State { +class _NSSLState extends ConsumerState { _NSSLState() : super(); final GlobalKey _mainScaffoldKey = GlobalKey(); @@ -81,37 +94,34 @@ class _NSSLState extends State { @override void initState() { super.initState(); - + ref.read(cloudMessagingProvider); //Neded for ref on onMessage subscribeFirebase(context); FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { - CloudMessaging.onMessage(message, setState); + CloudMessaging.onMessage(message); }); FirebaseMessaging.onMessage.listen((event) { - CloudMessaging.onMessage(event, setState); + CloudMessaging.onMessage(event); }); - // FirebaseMessaging.onBackgroundMessage((message) async { - // return; - // }); - // FirebaseMessaging.onBackgroundMessage((message) => CloudMessaging.onMessage(message, setState)); - // firebaseMessaging.configure( - // onMessage: (x) => CloudMessaging.onMessage(x, setState), onLaunch: (x) => Startup.initialize()); - for (var list in User.shoppingLists) + + for (var list in ref.read(shoppingListsProvider).shoppingLists) if (list.messagingEnabled) list.subscribeForFirebaseMessaging(); } Future subscribeFirebase(BuildContext context) async { - if (!Platform.isAndroid) return; + if (!Startup.firebaseSupported()) return; var initMessage = await FirebaseMessaging.instance.getInitialMessage(); if (initMessage != null) { - CloudMessaging.onMessage(initMessage, setState); + CloudMessaging.onMessage(initMessage); } } @override Widget build(BuildContext context) { + var user = ref.watch(userProvider); + return AdaptiveTheme( light: Themes.lightTheme.theme!, dark: Themes.darkTheme.theme, @@ -119,25 +129,24 @@ class _NSSLState extends State { builder: (theme, darkTheme) => MaterialApp( scrollBehavior: CustomScrollBehavior(), title: 'NSSL', - color: Colors.grey[500], localizationsDelegates: >[ new _NSSLLocalizationsDelegate(), GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: const [ - const Locale('en', 'US'), - const Locale('de', 'DE'), + const Locale('en', ''), + const Locale('de', ''), ], theme: theme, darkTheme: darkTheme, debugShowMaterialGrid: materialGrid, - home: User.username == null ? mainAppLoginRegister() : mainAppHome(), + home: user.ownId >= 0 ? mainAppHome() : mainAppLoginRegister(), routes: { '/login': (BuildContext context) => LoginPage(), '/registration': (BuildContext context) => Registration(), '/search': (BuildContext context) => ProductAddPage(), - '/forgot_password': (BuildContext context) => CustomThemePage(), + '/forgot_password': (BuildContext context) => ForgotPasswordPage(), }, showPerformanceOverlay: performanceOverlay, showSemanticsDebugger: false, @@ -147,9 +156,7 @@ class _NSSLState extends State { } Scaffold mainAppHome() => Scaffold( - key: _mainScaffoldKey, - resizeToAvoidBottomInset: false, - body: MainPage() //CustomThemePage()//LoginPage(), + key: _mainScaffoldKey, resizeToAvoidBottomInset: false, body: MainPage() //CustomThemePage()//LoginPage(), ); Scaffold mainAppLoginRegister() => Scaffold( diff --git a/lib/manager/database_manager.dart b/lib/manager/database_manager.dart index 8c3197a..671fedc 100644 --- a/lib/manager/database_manager.dart +++ b/lib/manager/database_manager.dart @@ -1,47 +1,158 @@ import 'dart:async'; -import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:nssl/models/model_export.dart'; import 'package:path/path.dart' as path; import 'package:sqflite_common/sqlite_api.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +class FakeDatabase extends Database { + @override + Batch batch() { + throw UnimplementedError(); + } + + @override + Future close() { + return Future.value(null); + } + + @override + Future delete(String table, {String? where, List? whereArgs}) { + return Future.value(0); + } + + @override + Future devInvokeMethod(String method, [arguments]) { + return Future.value(null); + } + + @override + Future devInvokeSqlMethod(String method, String sql, [List? arguments]) { + return Future.value(null); + } + + @override + Future execute(String sql, [List? arguments]) { + return Future.value(null); + } + + @override + Future getVersion() { + return Future.value(10); + } + + @override + Future insert(String table, Map values, + {String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm}) { + return Future.value(values.length); + } + + @override + bool get isOpen => true; + + @override + String get path => ""; + + @override + Future>> query(String table, + {bool? distinct, + List? columns, + String? where, + List? whereArgs, + String? groupBy, + String? having, + String? orderBy, + int? limit, + int? offset}) { + return Future.value([]); + } + + @override + Future rawDelete(String sql, [List? arguments]) { + return Future.value(0); + } + + @override + Future rawInsert(String sql, [List? arguments]) { + return Future.value(0); + } + + @override + Future>> rawQuery(String sql, [List? arguments]) { + return Future.value([]); + } + + @override + Future rawUpdate(String sql, [List? arguments]) { + return Future.value(0); + } + + @override + Future setVersion(int version) { + return Future.value(null); + } + + @override + Future transaction(Future Function(Transaction txn) action, {bool? exclusive}) { + // TODO: implement transaction + throw UnimplementedError(); + } + + @override + Future update(String table, Map values, + {String? where, List? whereArgs, ConflictAlgorithm? conflictAlgorithm}) { + return Future.value(0); + } +} + class DatabaseManager { - static late Database database; + static late Database database; // = FakeDatabase(); static int _version = 3; static Future initialize() async { + // if (kIsWeb) { + // return; + // } WidgetsFlutterBinding.ensureInitialized(); sqfliteFfiInit(); - - var dbPath = path.join((await getApplicationDocumentsDirectory()).path, "db.db"); - database = await databaseFactoryFfi.openDatabase(dbPath, options: OpenDatabaseOptions(version: _version, onCreate: (Database db, int version) async { - await db - .execute("CREATE TABLE ShoppingItems (id INTEGER PRIMARY KEY, name TEXT, amount INTEGER, crossed INTEGER, res_list_id INTEGER, sortorder INTEGER)"); - await db.execute("CREATE TABLE ShoppingLists (id INTEGER PRIMARY KEY, name TEXT, messaging INTEGER, user_id INTEGER)"); - await db.execute( - "CREATE TABLE User (own_id INTEGER, username TEXT, email TEXT, token TEXT, current_list_index INTEGER)"); //TODO for multiple users any indicator of last user - await db.execute( - "CREATE TABLE Themes (id INTEGER PRIMARY KEY, primary_color INTEGER, accent_color INTEGER, brightness TEXT, accent_color_brightness TEXT, user_id INTEGER)"); - }, onUpgrade: _upgradeDatabase)); + String dbPath; + if (kIsWeb) { + dbPath = "db.db"; + } else + dbPath = path.join((await getApplicationDocumentsDirectory()).path, "db.db"); + database = await databaseFactoryFfi.openDatabase(dbPath, + options: OpenDatabaseOptions( + version: _version, + onCreate: (Database db, int version) async { + await db.execute( + "CREATE TABLE ShoppingItems (id INTEGER PRIMARY KEY, name TEXT, amount INTEGER, crossed INTEGER, res_list_id INTEGER, sortorder INTEGER)"); + await db.execute( + "CREATE TABLE ShoppingLists (id INTEGER PRIMARY KEY, name TEXT, messaging INTEGER, user_id INTEGER)"); + await db.execute( + "CREATE TABLE User (own_id INTEGER, username TEXT, email TEXT, token TEXT, current_list_index INTEGER)"); //TODO for multiple users any indicator of last user + await db.execute( + "CREATE TABLE Themes (id INTEGER PRIMARY KEY, primary_color INTEGER, accent_color INTEGER, brightness TEXT, accent_color_brightness TEXT, user_id INTEGER)"); + }, + onUpgrade: _upgradeDatabase)); } static Future _upgradeDatabase(Database db, int oldVersion, int newVersion) async { //user_id new on ShoppingLists and Themes var list = (await db.rawQuery("SELECT * FROM User LIMIT 1")); - if (list.length != 0) User.ownId = list.first["own_id"] as int?; - + int? userId; + if (list.length != 0) userId = list.first["own_id"] as int?; bool userExists = list.length != 0; if (oldVersion == 1) { var lists = await db.rawQuery("SELECT * FROM ShoppingLists"); await db.execute("ALTER TABLE ShoppingLists ADD user_id INTEGER"); - if (lists.length > 0 && userExists) await db.rawUpdate('UPDATE ShoppingLists SET user_id = ?', [User.ownId]); + if (lists.length > 0 && userExists) await db.rawUpdate('UPDATE ShoppingLists SET user_id = ?', [userId]); var theme = await db.rawQuery("SELECT * FROM Themes"); await db.execute("ALTER TABLE Themes ADD user_id INTEGER"); - if (theme.length > 0 && userExists) await db.rawUpdate('UPDATE Themes SET user_id = ?', [User.ownId]); + if (theme.length > 0 && userExists) await db.rawUpdate('UPDATE Themes SET user_id = ?', [userId]); } if (oldVersion < 3) { diff --git a/lib/manager/export_manager.dart b/lib/manager/export_manager.dart index 9b3f28c..a54a44e 100644 --- a/lib/manager/export_manager.dart +++ b/lib/manager/export_manager.dart @@ -7,19 +7,20 @@ import 'package:pdf/widgets.dart'; import 'package:nssl/models/model_export.dart'; class ExportManager { - static Future exportAsPDF(ShoppingList list, [fm.BuildContext? context]) async { + static Future exportAsPDF(ShoppingList list, List shoppingItems, + [fm.BuildContext? context]) async { // if (!await PermissionRequestManager.requestPermission(PermissionGroup.storage)) { // return; // } - var paragraphs = list.shoppingItems!.map((f) => Paragraph(text: "${f!.amount}x ${f.name}")); + var paragraphs = shoppingItems.map((f) => Paragraph(text: "${f.amount}x ${f.name}")); final pdf = Document(); pdf.addPage(MultiPage( pageFormat: PdfPageFormat.a4, header: (Context context) { return Container( alignment: Alignment.center, - child: Text(list.name!, style: Theme.of(context).header0.copyWith(color: PdfColors.black))); + child: Text(list.name, style: Theme.of(context).header0.copyWith(color: PdfColors.black))); }, build: (Context c) => paragraphs.toList())); diff --git a/lib/manager/startup_manager.dart b/lib/manager/startup_manager.dart index 7dfd3ee..9d0ea08 100644 --- a/lib/manager/startup_manager.dart +++ b/lib/manager/startup_manager.dart @@ -3,28 +3,36 @@ import 'dart:convert'; import 'dart:io'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:nssl/firebase/cloud_messsaging.dart'; import 'package:nssl/manager/database_manager.dart'; import 'package:nssl/models/model_export.dart'; import 'package:nssl/options/themes.dart'; -import 'package:nssl/server_communication/return_classes.dart'; -import 'package:nssl/server_communication/s_c.dart'; import 'package:scandit_flutter_datacapture_barcode/scandit_flutter_datacapture_barcode.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:file/local.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nssl/firebase_options.dart'; class Startup { static SharedPreferences? sharedPreferences; static List remoteMessages = []; static const LocalFileSystem fs = const LocalFileSystem(); + static bool firebaseSupported() => kIsWeb || Platform.isAndroid || Platform.isIOS || Platform.isMacOS; static Future initializeMinFunction() async { - if (!Platform.isAndroid) return true; - return Firebase.initializeApp().then((value) async => await ScanditFlutterDataCaptureBarcode.initialize()).then((value) => true); + if (!firebaseSupported()) return true; + var initTask = Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + if (!kIsWeb && !Platform.isMacOS) + return initTask.then((value) async => await ScanditFlutterDataCaptureBarcode.initialize()).then((value) => true); + + return initTask.then((value) => true); } - static Future loadMessagesFromFolder(Function setState) async { + static Future loadMessagesFromFolder(WidgetRef ref) async { var dir = await Startup.fs.systemTempDirectory.childDirectory("message").create(); var subFiles = dir.listSync(); if (subFiles.length == 0) return; @@ -35,7 +43,8 @@ class Startup { .forEach((subFile) { var str = subFile.readAsStringSync(); var remoteMessage = RemoteMessage(data: jsonDecode(str)); - CloudMessaging.onMessage(remoteMessage, setState); + ref.read(cloudMessagingProvider); + CloudMessaging.onMessage(remoteMessage); subFile.delete(); }); } @@ -52,92 +61,27 @@ class Startup { }); } - static Future initialize() async { - // var t = SharedPreferences.getInstance(); + static Future initialize(WidgetRef ref) async { WidgetsFlutterBinding.ensureInitialized(); var f1 = initializeMinFunction(); await DatabaseManager.initialize(); - await User.load(); - // sharedPreferences = await t; + var user = await ref.read(userFromDbProvider.future); - if (User.username == null || User.username == "" || User.eMail == null || User.eMail == "") return false; + if (user == null || user.username == "" || user.eMail == "") return false; + ref.read(themeProvider); await Themes.loadTheme(); - User.shoppingLists = await ShoppingList.load(); - if (User.shoppingLists.length == 0) return true; - User.currentList = - User.shoppingLists.firstWhere((x) => x.id == User.currentListIndex, orElse: () => User.shoppingLists.first); + var provider = ref.read(shoppingListsProvider); await f1; - return true; - - // FileManager.createFolder("ShoppingListsCo"); - // FileManager.createFile("token.txt"); - // FileManager.createFile("User.txt"); - // FileManager.createFile("listList.txt"); - - // User.token = await FileManager.readAsString("token.txt"); - - // var userData = await FileManager.readAsLines("User.txt"); - // if(userData.where((s)=> s.isNotEmpty).length == 2) { - // User.username = userData[0]; - // User.eMail = userData[1]; - // } - // else { - // User.username = null; - // User.eMail = null; - // } - // for (var list in dir.listSync()) - // if (list != null) - // User.shoppingLists.add(await ShoppingList - // .load(int.parse(list.path.split('/').last.split('.')[0]))); - - // await Themes.loadTheme(); + await provider.load(); - // if (User.shoppingLists.length > 0) { - // var listId = int.parse(await FileManager.readAsString("lastList.txt")); - // User.currentList = User.shoppingLists.firstWhere((x) => x.id == listId); - // } else { - // User.currentList = ShoppingList() - // ..name = "No List yet" - // ..id = 1 - // ..shoppingItems = []; - // } - // User.currentListIndex = User.currentList.id; - // await User.save(); + return true; } - static Future initializeNewListsFromServer(Function setState) async { - var res = await ShoppingListSync.getLists(null); - - if (res.statusCode == 200) { - var result = GetListsResult.fromJson(res.body); - - User.shoppingLists.clear(); - await DatabaseManager.database.rawDelete("DELETE FROM ShoppingLists where user_id = ?", [User.ownId]); - - var crossedOut = - (await DatabaseManager.database.rawQuery("SELECT id, crossed FROM ShoppingItems WHERE crossed = 1")); - result.shoppingLists.forEach((resu) { - var list = ShoppingList(resu.id, resu.name)..shoppingItems = []; - - for (var item in resu.products!) - list.shoppingItems!.add(ShoppingItem(item.name) - ..id = item.id - ..amount = item.amount - ..crossedOut = - (crossedOut.firstWhere((x) => x["id"] == item.id, orElse: () => {"crossed": 0})["crossed"] == 0 - ? false - : true)); - User.shoppingLists.add(list); - list.save(); - }); - } - User.currentList = - User.shoppingLists.firstWhere((x) => x.id == User.currentListIndex, orElse: () => User.shoppingLists.first); + static Future initializeNewListsFromServer(WidgetRef ref) async { + var provider = ref.read(shoppingListsProvider); + await provider.reloadAllLists(); - var args = []; - args.add(() {}); - Function.apply(setState, args); return true; } } diff --git a/lib/models/shopping_item.dart b/lib/models/shopping_item.dart index f77c8da..612b194 100644 --- a/lib/models/shopping_item.dart +++ b/lib/models/shopping_item.dart @@ -1,3 +1,7 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:nssl/helper/iterable_extensions.dart'; + class TestClass { int test; String? o; @@ -7,41 +11,98 @@ class TestClass { TestClass.intOnly(this.test); } +final shoppingItemsProvider = StateProvider>(((ref) => [])); + +final shoppingItemsPerListProvider = Provider.family, int>((ref, listId) { + var items = ref.watch(shoppingItemsProvider); + return items.where((element) => element.listId == listId).toList(); +}); + +final shoppingItemProvider = Provider.family((ref, itemId) { + var items = ref.watch(shoppingItemsProvider); + return items.firstOrNull((element) => element.id == itemId); +}); + +@immutable class ShoppingItem { - //extends JsonDecoder{ - int amount = 1; - String? name; - int? id; - DateTime? created; - DateTime? changed; - bool crossedOut = false; - int? sortOrder; + final int amount; + final String name; + final int id; + final DateTime? created; + final DateTime? changed; + final bool crossedOut; + final int sortOrder; + final int listId; - ShoppingItem(this.name); + int get sortWithOffset => sortOrder + (crossedOut ? 0xFFFFFFFF : 0); + + const ShoppingItem( + this.name, + this.listId, + this.sortOrder, { + this.amount = 1, + this.id = -1, + this.crossedOut = false, + this.created, + this.changed, + }); @override String toString() { - return name! + "\u{1F}" + amount.toString() + "\u{1F}" + id.toString(); + return name + "\u{1F}" + amount.toString() + "\u{1F}" + id.toString(); } /// Creates a copy, with only including [amount] and [name] ShoppingItem copy() { - return ShoppingItem(name)..amount = amount; + return ShoppingItem(name, listId, sortOrder, amount: amount); } /// Creates an identical clone, where all fields are the same as /// the parent item ShoppingItem clone() { - return ShoppingItem(name) - ..amount = amount - ..id = id - ..created = created - ..changed = changed - ..crossedOut = crossedOut - ..sortOrder = sortOrder; + return ShoppingItem(name, listId, sortOrder, + amount: amount, id: id, changed: changed, created: created, crossedOut: crossedOut); + } + + ShoppingItem cloneWith( + {String? newName, + int? newListId, + int? newAmount, + int? newId, + DateTime? newCreated, + DateTime? newChanged, + bool? newCrossedOut, + int? newSortOrder}) { + return ShoppingItem(newName ?? name, newListId ?? listId, newSortOrder ?? sortOrder, + amount: newAmount ?? amount, + id: newId ?? id, + changed: newChanged ?? changed, + created: newCreated ?? created, + crossedOut: newCrossedOut ?? crossedOut); } - ShoppingItem.fromJson(String s) : name = s; + // ShoppingItem.fromJson(String s) : name = s; Map toJson() => {'name': name}; + + @override + bool operator ==(final Object other) => + other is ShoppingItem && + other.name == name && + other.amount == amount && + other.id == id && + other.crossedOut == crossedOut && + other.sortOrder == sortOrder && + other.listId == listId; + + @override + int get hashCode => Object.hash(name, amount, id, crossedOut, sortOrder, listId); + + void exchange(ShoppingItem newItem, WidgetRef ref) { + var items = ref.watch(shoppingItemsProvider.notifier); + var newState = items.state.toList(); + newState.remove(this); + newState.add(newItem); + items.state = newState; + } } diff --git a/lib/models/shopping_list.dart b/lib/models/shopping_list.dart index e85d644..fc00296 100644 --- a/lib/models/shopping_list.dart +++ b/lib/models/shopping_list.dart @@ -1,189 +1,321 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:nssl/firebase/cloud_messsaging.dart'; -import 'package:nssl/models/shopping_item.dart'; +import 'package:nssl/helper/iterable_extensions.dart'; +import 'package:nssl/models/model_export.dart'; import 'package:nssl/manager/manager_export.dart'; -import 'package:nssl/models/user.dart'; import 'dart:async'; import 'package:nssl/server_communication/return_classes.dart'; import 'package:nssl/server_communication/shopping_list_sync.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class ShoppingList { - static ShoppingList empty = ShoppingList.messaging(1, "", false); +final shoppingListsProvider = ChangeNotifierProvider((ref) { + var userId = ref.watch(userIdProvider); + if (userId == null) return ShoppingListController(ref, -1); + + var manager = ShoppingListController(ref, userId); + + return manager; +}); + +final listProvider = Provider.family((ref, indx) { + var shoppingListController = ref.watch(shoppingListsProvider); + if (indx + 1 > shoppingListController.shoppingLists.length) return null; + + return shoppingListController.shoppingLists[indx]; +}); + +final currentListProvider = Provider((ref) { + var index = ref.watch(currentListIndexProvider); + if (index == null) return null; + return ref.watch(listProvider.create(index)); +}); + +final currentShoppingItemsProvider = Provider>((ref) { + var list = ref.watch(currentListProvider); + if (list == null || list.id < 0) return []; + return ref.watch(shoppingItemsPerListProvider.create(list.id)); +}); + +final shoppingListByIndexProvider = Provider.family((ref, indx) { + var shoppingListController = ref.watch(shoppingListsProvider); + if (indx > shoppingListController.shoppingLists.length) return null; - int? id; + return shoppingListController.shoppingLists[indx]; +}); - String? name; - List? shoppingItems = []; - bool messagingEnabled = true; +final shoppingListByIdProvider = Provider.family((ref, id) { + var shoppingListController = ref.watch(shoppingListsProvider); - ShoppingList(this.id, this.name); + return shoppingListController.shoppingLists.firstOrNull((element) => element.id == id); +}); - ShoppingList.messaging(this.id, this.name, this.messagingEnabled); +class ShoppingListController with ChangeNotifier { + static late Ref _ref; + static late int _userId; + List shoppingLists = []; - Future save() async { + ShoppingListController(Ref ref, int userId) { + _ref = ref; + _userId = userId; + } + + Future save(ShoppingList list) async { await DatabaseManager.database.transaction((z) async { - await z.execute( - 'INSERT OR REPLACE INTO ShoppingLists(id, name, messaging, user_id) VALUES(?, ?, ?, ?)', - [id, name, messagingEnabled ? 1 : 0, User.ownId]); - - await z.rawDelete( - "DELETE FROM ShoppingItems WHERE res_list_id = ? and id not in (?)", - [id, shoppingItems!.map((e) => e!.id).join(",")]); - for (var item in shoppingItems!) { + await z.execute('INSERT OR REPLACE INTO ShoppingLists(id, name, messaging, user_id) VALUES(?, ?, ?, ?)', + [list.id, list.name, list.messagingEnabled ? 1 : 0, _userId]); + var shoppingItems = _ref.read(shoppingItemsPerListProvider.create(list.id)); + await z.rawDelete("DELETE FROM ShoppingItems WHERE res_list_id = ? and id not in (?)", + [list.id, shoppingItems.map((e) => e.id).join(",")]); + for (var item in shoppingItems) { await z.execute( "INSERT OR REPLACE INTO ShoppingItems(id, name, amount, crossed, res_list_id, sortorder) VALUES (?, ?, ?, ?, ?, ?)", - [ - item!.id, - item.name, - item.amount, - item.crossedOut ? 1 : 0, - id, - item.sortOrder - ]); + [item.id, item.name, item.amount, item.crossedOut ? 1 : 0, list.id, item.sortOrder]); } }); } - Future addSingleItem(ShoppingItem item, {int index = -1}) async { - if (index < 0) index = shoppingItems!.length; - shoppingItems!.insert(index, item); + Future addSingleItem(ShoppingList list, ShoppingItem item, {int index = -1}) async { + // var shoppingItems = _ref.read(shoppingItemsPerListProvider.create(list.id)); + // if (index < 0) index = shoppingItems.length; + // shoppingItems.insert(index, item); + + // var newList = ShoppingList(list.id, list.name, messagingEnabled: list.messagingEnabled); + // _exchangeLists(list, newList); + var sip = _ref.watch(shoppingItemsProvider.notifier); + var newState = sip.state.toList(); + newState.add(item); + sip.state = newState; + await DatabaseManager.database.execute( "INSERT OR REPLACE INTO ShoppingItems(id, name, amount, crossed, res_list_id, sortorder) VALUES (?, ?, ?, ?, ?, ?)", - [ - item.id, - item.name, - item.amount, - item.crossedOut ? 1 : 0, - id, - item.sortOrder - ]); - } - - Future deleteSingleItem(ShoppingItem item) async { - shoppingItems!.remove(item); - await DatabaseManager.database - .rawDelete("DELETE FROM ShoppingItems WHERE id = ?", [item.id]); - } - - static Future> load() async { - var lists = await DatabaseManager.database.rawQuery( - "SELECT * FROM ShoppingLists WHERE user_id = ?", [User.ownId]); - - var items = await DatabaseManager.database.rawQuery( - "SELECT * FROM ShoppingItems ORDER BY res_list_id, sortorder"); - - // TODO: if db ordering enough for us, or do we want to order by ourself in code? - // return lists - // .map((x) => ShoppingList() - // ..id = x["id"] - // ..messagingEnabled = x["messaging"] == 0 ? false : true - // ..name = x["name"] - // ..shoppingItems = (items - // .where((y) => y["res_list_id"] == x["id"]) - // .map((y) => ShoppingItem(y["name"]) - // ..amount = y["amount"] - // ..crossedOut = y["crossed"] == 0 ? false : true - // ..id = y["id"] - // ..sortOrder = y["sortorder"]) - // .toList() - // ..sort((a, b) => a.sortOrder?.compareTo(b.sortOrder)))) - // .toList(); - - return lists - .map((x) => ShoppingList(x["id"] as int?, x["name"] as String?) - ..messagingEnabled = x["messaging"] == 0 ? false : true - ..shoppingItems = items - .where((y) => y["res_list_id"] == x["id"]) - .map((y) => ShoppingItem(y["name"] as String?) - ..amount = y["amount"] as int - ..crossedOut = y["crossed"] == 0 ? false : true - ..id = y["id"] as int? - ..sortOrder = y["sortorder"] as int?) - .toList()) + [item.id, item.name, item.amount, item.crossedOut ? 1 : 0, list.id, item.sortOrder]); + save(list); + notifyListeners(); + } + + Future deleteSingleItemById(ShoppingList list, int itemId) async { + var sip = _ref.watch(shoppingItemsProvider.notifier); + var newState = sip.state.toList(); + newState.removeWhere((x) => x.id == itemId); + sip.state = newState; + // var newList = ShoppingList(list.id, list.name, list.shoppingItems.where((element) => element != item).toList(), + // messagingEnabled: list.messagingEnabled); + // _exchangeLists(list, newList); + await DatabaseManager.database.rawDelete("DELETE FROM ShoppingItems WHERE id = ?", [itemId]); + save(list); + } + + Future deleteSingleItem(ShoppingList list, ShoppingItem item) async { + var sip = _ref.watch(shoppingItemsProvider.notifier); + var newState = sip.state.toList(); + newState.remove(item); + sip.state = newState; + // var newList = ShoppingList(list.id, list.name, list.shoppingItems.where((element) => element != item).toList(), + // messagingEnabled: list.messagingEnabled); + // _exchangeLists(list, newList); + await DatabaseManager.database.rawDelete("DELETE FROM ShoppingItems WHERE id = ?", [item.id]); + save(list); + } + + Future> load() async { + var lists = await DatabaseManager.database.rawQuery("SELECT * FROM ShoppingLists WHERE user_id = ?", [_userId]); + + var items = await DatabaseManager.database.rawQuery("SELECT * FROM ShoppingItems ORDER BY res_list_id, sortorder"); + + int curSortOrder = 0; + var newShoppingItems = items.map( + (y) { + var sortOrder = y["sortorder"] as int?; + if (sortOrder != null) + curSortOrder = sortOrder; + else + curSortOrder++; + return ShoppingItem( + y["name"] as String, + y["res_list_id"] as int, + curSortOrder, + amount: y["amount"] as int, + crossedOut: y["crossed"] == 0 ? false : true, + id: y["id"] as int, + ); + }, + ).toList(); + + shoppingLists = lists + .map((x) => + ShoppingList(x["id"] as int, x["name"] as String, messagingEnabled: x["messaging"] == 0 ? false : true)) .toList(); + + var sip = _ref.watch(shoppingItemsProvider.notifier); + sip.state = newShoppingItems; + + notifyListeners(); + return shoppingLists; } - Future refresh([BuildContext? context]) async { - var res = await ShoppingListSync.getList(id, context); -// if (res.statusCode == 401) { -// showInSnackBar(loc.notLoggedInYet() + res.reasonPhrase); -// return; -// } -// if (res.statusCode != 200) { -// showInSnackBar(loc.genericErrorMessageSnackbar()); -// return; -// } - var newList = GetListResult.fromJson(res.body); + Future refresh(ShoppingList list, [BuildContext? context]) async { + var res = await ShoppingListSync.getList(list.id, context); + + var newListResult = GetListResult.fromJson(res.body); List> items; - items = (await DatabaseManager.database.rawQuery( - "SELECT id, crossed, sortorder FROM ShoppingItems WHERE res_list_id = ?", - [id])); - - shoppingItems!.clear(); - for (var item in newList.products!) - shoppingItems!.add(ShoppingItem(item.name) - ..id = item.id - ..amount = item.amount - ..changed = item.changed - ..created = item.created - ..crossedOut = (items.firstWhere((x) => x["id"] == item.id, - orElse: () => {"crossed": 0})["crossed"] == - 0 - ? false - : true) - ..sortOrder = item.sortOrder); - - shoppingItems!.sort((a, b) => a!.sortOrder!.compareTo(b!.sortOrder!)); - save(); - } - - static Future reloadAllLists([BuildContext? cont]) async { - var result = - GetListsResult.fromJson((await ShoppingListSync.getLists(cont)).body); - User.shoppingLists.clear(); - - await DatabaseManager.database - .delete("ShoppingLists", where: "user_id = ?", whereArgs: [User.ownId]); - //await DatabaseManager.database.rawDelete("DELETE FROM ShoppingLists where user_id = ?", [User.ownId]); + items = (await DatabaseManager.database + .rawQuery("SELECT id, crossed, sortorder FROM ShoppingItems WHERE res_list_id = ?", [list.id])); + + var shoppingItems = []; + var sip = _ref.watch(shoppingItemsProvider.notifier); + var newState = sip.state.toList(); + newState.removeWhere((item) => item.listId == list.id); + + for (var item in newListResult.products!) + shoppingItems.add(ShoppingItem( + item.name, + list.id, + item.sortOrder, + id: item.id, + amount: item.amount, + changed: item.changed, + created: item.created, + crossedOut: + (items.firstWhere((x) => x["id"] == item.id, orElse: () => {"crossed": 0})["crossed"] == 0 ? false : true), + )); + + shoppingItems.sort((a, b) => a.sortWithOffset.compareTo(b.sortWithOffset)); + newState.addAll(shoppingItems); + var newList = ShoppingList(list.id, list.name, messagingEnabled: list.messagingEnabled); + _exchangeLists(list, newList); + sip.state = newState; + save(newList); + + notifyListeners(); + } + + void _exchangeLists(ShoppingList list, ShoppingList newList) { + var index = shoppingLists.indexOf(list); + shoppingLists.remove(list); + shoppingLists.insert(index, newList); + notifyListeners(); + } + + Future reloadAllLists([BuildContext? cont]) async { + var res = (await ShoppingListSync.getLists(cont)); + if (res.statusCode != 200) return; + + // DatabaseManager.database.rawDelete("DELETE FROM ShoppingLists where user_id = ?", _ref.read())) //TODO Should everything be deleted from this user? + + var result = GetListsResult.fromJson(res.body); + shoppingLists.clear(); + await DatabaseManager.database.delete("ShoppingLists", where: "user_id = ?", whereArgs: [_userId]); List> items; items = (await DatabaseManager.database - .rawQuery("SELECT id, crossed, sortorder FROM ShoppingItems")); + .rawQuery("SELECT id, crossed, sortorder FROM ShoppingItems where crossed = 1 or sortorder > 0")); + var shoppintItemsState = _ref.watch(shoppingItemsProvider.notifier); + var shoppingItems = []; for (var res in result.shoppingLists) { - var list = ShoppingList(res.id, res.name); - - for (var item in res.products!) - list.shoppingItems!.add(ShoppingItem(item.name) - ..id = item.id - ..amount = item.amount - ..crossedOut = (items.firstWhere((x) => x["id"] == item.id, - orElse: () => {"crossed": 0})["crossed"] == - 0 + int currentSortOrder = -1; + for (var item in res.products) { + var order = items.firstWhere((x) => x["id"] == item.id, + orElse: () => {"sortorder": item.sortOrder})["sortorder"] as int; + if (order == -1 || currentSortOrder == order) + order = ++currentSortOrder; + else + currentSortOrder = order; + shoppingItems.add(ShoppingItem( + item.name, + res.id, + order, + id: item.id, + amount: item.amount, + changed: item.changed, + created: item.created, + crossedOut: (items.firstWhere((x) => x["id"] == item.id, orElse: () => {"crossed": 0})["crossed"] == 0 ? false - : true) - ..sortOrder = (items.firstWhere((x) => x["id"] == item.id, - orElse: () => {"sortorder": 0})["sortorder"])); - - if (list.shoppingItems!.any((element) => element!.sortOrder == null)) - for (int i = 0; i < list.shoppingItems!.length; i++) - list.shoppingItems![i]!.sortOrder = i; + : true), + )); + } - list.shoppingItems! - .sort((a, b) => a!.sortOrder!.compareTo(b!.sortOrder!)); - User.shoppingLists.add(list); + // if (shoppingItems.any((element) => element.sortOrder == null)) + // for (int i = 0; i < shoppingItems.length; i++) { + // var oldItem = shoppingItems[i]; + // shoppingItems.removeAt(i); + // shoppingItems.insert( + // i, + // ShoppingItem(oldItem.name, oldItem.listId, i, + // id: oldItem.id, + // crossedOut: oldItem.crossedOut, + // amount: oldItem.amount, + // changed: oldItem.changed, + // created: oldItem.created)); + // } + var list = ShoppingList(res.id, res.name); + shoppingLists.add(list); list.subscribeForFirebaseMessaging(); - list.save(); + save(list); } + shoppingItems.sort((a, b) => a.sortWithOffset.compareTo(b.sortWithOffset)); + shoppintItemsState.state = shoppingItems; + notifyListeners(); + } + + void addList(ShoppingList shoppingList) { + shoppingLists.add(shoppingList); + shoppingList.subscribeForFirebaseMessaging(); + save(shoppingList); + notifyListeners(); + } + + void removeList(int listId) { + shoppingLists.removeWhere((x) => x.id == listId); + + firebaseMessaging?.unsubscribeFromTopic(listId.toString() + "shoppingListTopic"); + notifyListeners(); + } + + void saveAndNotify(ShoppingList list) { + save(list); + notifyListeners(); } + void toggleFirebaseMessaging(int listId) { + var list = shoppingLists.firstWhere((element) => element.id == listId); + var newList = ShoppingList(listId, list.name, messagingEnabled: !list.messagingEnabled); + newList.messagingEnabled ? list.subscribeForFirebaseMessaging() : list.unsubscribeFromFirebaseMessaging(); + _exchangeLists(list, newList); + save(newList); + } + + void rename(int id, String name) { + var list = shoppingLists.firstWhere((element) => element.id == id); + + var newList = ShoppingList(id, name, messagingEnabled: list.messagingEnabled); + _exchangeLists(list, newList); + saveAndNotify(list); + } +} + +@immutable +class ShoppingList { + static ShoppingList empty = const ShoppingList.messaging(1, "", false); + + final int id; + final String name; + // final List shoppingItems; + final bool messagingEnabled; + + const ShoppingList(this.id, this.name, /*this.shoppingItems,*/ {this.messagingEnabled = true}); + + const ShoppingList.messaging(this.id, this.name, /*this.shoppingItems,*/ this.messagingEnabled); + void subscribeForFirebaseMessaging() { + if (kIsWeb) return; firebaseMessaging?.subscribeToTopic(id.toString() + "shoppingListTopic"); } void unsubscribeFromFirebaseMessaging() { - firebaseMessaging - ?.unsubscribeFromTopic(id.toString() + "shoppingListTopic"); + if (kIsWeb) return; + firebaseMessaging?.unsubscribeFromTopic(id.toString() + "shoppingListTopic"); } } diff --git a/lib/models/user.dart b/lib/models/user.dart index 0b13ed2..b6ecabf 100644 --- a/lib/models/user.dart +++ b/lib/models/user.dart @@ -1,43 +1,104 @@ import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:nssl/main.dart'; import 'package:nssl/manager/database_manager.dart'; -import 'package:nssl/models/shopping_list.dart'; import 'package:nssl/server_communication/jwt.dart'; +import 'package:riverpod/riverpod.dart'; -class User { - static String? username; - static String? eMail; - static List shoppingLists = []; - static String? token; - static int? currentListIndex; - static ShoppingList? currentList; - static int? ownId; - - static Future load() async { - var list = (await DatabaseManager.database.rawQuery("SELECT * FROM User LIMIT 1")); - if (list.length == 0) return; - var z = list.first; - User.username = z["username"] as String?; - User.eMail = z["email"] as String?; - User.token = z["token"] as String?; - User.currentListIndex = z["current_list_index"] as int?; - if (!z.containsKey("own_id")) { - await DatabaseManager.database.execute("DROP TABLE User"); - await DatabaseManager.database.execute( - "CREATE TABLE User (own_id INTEGER, username TEXT, email TEXT, token TEXT, current_list_index INTEGER)"); - User.ownId = await JWT.getIdFromToken(User.token); - await save(); - } else - User.ownId = z["own_id"] as int?; +final userFromDbProvider = FutureProvider((ref) async { + ref.watch(appRestartProvider); + var list = (await DatabaseManager.database.rawQuery("SELECT * FROM User LIMIT 1")); + if (list.length == 0) return null; + var z = list.first; + var username = z["username"] as String; + var eMail = z["email"] as String; + var token = z["token"] as String; + var ownId = await JWT.getIdFromToken(token); + var currentListIndex = z["current_list_index"] as int; + var containsOwnId = z.containsKey("own_id"); + if (!containsOwnId) { + await DatabaseManager.database.execute("DROP TABLE User"); + await DatabaseManager.database.execute( + "CREATE TABLE User (own_id INTEGER, username TEXT, email TEXT, token TEXT, current_list_index INTEGER)"); + } else + ownId = z["own_id"] as int; + User.token = token; + var user = User(ownId, username, eMail); + ref.watch(currentListIndexProvider.notifier).state = currentListIndex; + + if (!containsOwnId) user.save(currentListIndex); + return user; +}); + +final userStateProvider = StateProvider((ref) { + return User.empty; +}); + +final userProvider = Provider((ref) { + var fromDb = ref.watch(userFromDbProvider); + var fromState = ref.watch(userStateProvider); + if (fromState.ownId == -1 && !fromDb.hasError && !fromDb.isLoading && fromDb.hasValue) { + return fromDb.valueOrNull ?? fromState; } + return fromState; +}); + +final userIdProvider = Provider((ref) { + return ref.watch(userProvider).ownId; +}); + +final currentListIndexProvider = StateProvider((ref) { + return null; +}); + +@immutable +class User { + static User empty = const User(-1, "", ""); + + final String username; + final String eMail; + // final List shoppingLists = []; + static String token = ""; + final int ownId; + + @override + bool operator ==(final Object other) => + other is User && other.username == username && other.eMail == eMail && other.ownId == ownId; + + @override + int get hashCode => Object.hash(username, eMail, ownId); + + const User(this.ownId, this.username, this.eMail); + + // static Future load() async { + // var list = (await DatabaseManager.database.rawQuery("SELECT * FROM User LIMIT 1")); + // if (list.length == 0) return; + // var z = list.first; + // User.username = z["username"] as String?; + // User.eMail = z["email"] as String?; + // User.token = z["token"] as String?; + // User.currentListIndex = z["current_list_index"] as int?; + // if (!z.containsKey("own_id")) { + // await DatabaseManager.database.execute("DROP TABLE User"); + // await DatabaseManager.database.execute( + // "CREATE TABLE User (own_id INTEGER, username TEXT, email TEXT, token TEXT, current_list_index INTEGER)"); + // User.ownId = await JWT.getIdFromToken(User.token); + // await save(); + // } else + // User.ownId = z["own_id"] as int?; + // } - static Future save() async { + Future save(int? currentListIndex) async { await DatabaseManager.database.rawDelete("DELETE FROM User"); await DatabaseManager.database.execute( "INSERT INTO User(own_id, username, email, token, current_list_index) VALUES(?, ?, ?, ?, ?)", - [User.ownId, User.username, User.eMail, User.token, User.currentListIndex]); + [ownId, username, eMail, token, currentListIndex]); } - static Future delete() async { + Future delete() async { await DatabaseManager.database.rawDelete("DELETE FROM User where own_id = ?", [ownId]); + await DatabaseManager.database.rawDelete( + "DELETE FROM ShoppingItems where res_list_id in( SELECT id FROM ShoppingLists where user_id = ?)", [ownId]); + await DatabaseManager.database.rawDelete("DELETE FROM ShoppingLists where user_id = ?", [ownId]); } } diff --git a/lib/models_json.dart b/lib/models_json.dart index 4e9f8f3..0088db9 100644 --- a/lib/models_json.dart +++ b/lib/models_json.dart @@ -6,8 +6,7 @@ class User { toJson() => {"username": username, "email": email, "pwhash": password}; - static User fromJson(Map data) => - User(data["username"], data["email"], data["pwhash"]); + static User fromJson(Map data) => User(data["username"], data["email"], data["pwhash"]); } class Product { @@ -26,15 +25,14 @@ class Product { } class ShoppingItem { - ShoppingItem(this.id, this.amount, this.name, this.changed, this.created, - this.sortOrder) - : super(); - int? id; + ShoppingItem(this.id, this.amount, this.name, this.changed, this.created, this.sortOrder, this.gtin) : super(); + int id; int amount; - String? name; + String name; + String? gtin; DateTime? changed; DateTime? created; - int? sortOrder; + int sortOrder; toJson() => { "id": id, @@ -42,34 +40,31 @@ class ShoppingItem { "name": name, "changed": changed, "created": created, - "sortOrder": sortOrder + "sortOrder": sortOrder, + "gtin": gtin }; static ShoppingItem fromJson(Map data) => ShoppingItem( data["id"], data["amount"], data["name"], - data["changed"], - data["created"], - data["sortOrder"]); + DateTime.tryParse(data["changed"]), + DateTime.tryParse(data["created"]), + data["order"] ?? data["sortOrder"], + data["gtin"]); } class ShoppingList { - List? products; - int? id; - String? name; + List products; + int id; + String name; - toJson() => { - "products": products?.map((p) => p.toJson()).toList(growable: false), - "id": id, - "name": name - }; + ShoppingList(this.id, this.name, this.products); - static ShoppingList fromJson(Map data) => ShoppingList() - ..id = data["id"] - ..name = data["name"] - ..products = - (data["products"] as List).map(ShoppingItem.fromJson).toList(); + toJson() => {"products": products.map((p) => p.toJson()).toList(growable: false), "id": id, "name": name}; + + static ShoppingList fromJson(Map data) => ShoppingList( + data["id"] as int, data["name"] as String, (data["products"] as List).map(ShoppingItem.fromJson).toList()); } class Contributor { @@ -79,6 +74,5 @@ class Contributor { String? name; toJson() => {"id": id, "listId": listId, "name": name}; - static Contributor fromJson(Map data) => - Contributor(data["id"], data["listId"], data["name"]); + static Contributor fromJson(Map data) => Contributor(data["id"], data["listId"], data["name"]); } diff --git a/lib/options/themes.dart b/lib/options/themes.dart index 37dcb6d..9d34913 100644 --- a/lib/options/themes.dart +++ b/lib/options/themes.dart @@ -3,8 +3,18 @@ import 'package:flutter/material.dart'; import 'package:nssl/manager/database_manager.dart'; import 'package:nssl/models/user.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final themeProvider = ChangeNotifierProvider((ref) { + return Themes(ref); +}); + +class Themes with ChangeNotifier { + static late Ref _ref; + Themes(Ref ref) { + _ref = ref; + } -class Themes { static NSSLThemeData lightTheme = NSSLThemeData( ThemeData( primarySwatch: Colors.blue, @@ -47,27 +57,23 @@ class Themes { static Future saveTheme(ThemeData t, MaterialColor primary, MaterialAccentColor accent) async { // await DatabaseManager.database.rawDelete("DELETE FROM Themes"); - var id = ((t.brightness == Brightness.light ? 1 : 2) << 32) + User.ownId!; + var userId = _ref.watch(userIdProvider); + if (userId == null) return; + var id = ((t.brightness == Brightness.light ? 1 : 2) << 32) + userId; (await SharedPreferences.getInstance()).setInt("lastTheme", id); tm = t.brightness == Brightness.dark ? ThemeMode.dark : ThemeMode.light; await DatabaseManager.database.rawInsert( "INSERT OR REPLACE INTO Themes(id, primary_color, accent_color, brightness, accent_color_brightness, user_id) VALUES(?, ?, ?, ?, ?, ?)", - [ - id, - Colors.primaries.indexOf(primary), - Colors.accents.indexOf(accent), - t.brightness.toString(), - "", - User.ownId - ]); + [id, Colors.primaries.indexOf(primary), Colors.accents.indexOf(accent), t.brightness.toString(), "", userId]); } static Future loadTheme() async { late var t; var lastId = (await SharedPreferences.getInstance()).getInt("lastTheme") ?? 0; + var userId = _ref.watch(userIdProvider); try { - if (User.ownId != null) - t = (await DatabaseManager.database.rawQuery("SELECT * FROM Themes where user_id = ?", [User.ownId])); + if (userId != null) + t = (await DatabaseManager.database.rawQuery("SELECT * FROM Themes where user_id = ?", [userId])); } catch (e) { var temp = (await DatabaseManager.database.rawQuery("SELECT * FROM Themes")).first; diff --git a/lib/pages/about.dart b/lib/pages/about.dart new file mode 100644 index 0000000..0e51b23 --- /dev/null +++ b/lib/pages/about.dart @@ -0,0 +1,127 @@ +import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:nssl/localization/nssl_strings.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class AboutPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(NSSLStrings.of(context).about()), + ), + body: buildBody(context), + ); + } + + Widget buildBody(BuildContext context) { + var iconColor = AdaptiveTheme.of(context).mode == AdaptiveThemeMode.dark ? Colors.white : Colors.black; + return ListView( + children: [ + Container( + margin: EdgeInsets.all(16.0), + child: Column( + children: [ + Center( + child: SvgPicture.asset( + "assets/vectors/nssl_icon.svg", + width: 200, + ), + ), + Container( + margin: EdgeInsets.only( + top: 8.0, + ), + child: Center( + child: Text( + "Non Sucking Shopping List", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ), + ], + ), + ), + Divider(), + ListTile( + title: Text( + NSSLStrings.of(context).freeText(), + style: TextStyle(fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + ), + ListTile( + title: Text( + NSSLStrings.of(context).aboutText(), + textAlign: TextAlign.center, + ), + // "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."), + ), + ListTile( + title: Text( + NSSLStrings.of(context).questionsErrors(), + textAlign: TextAlign.center, + ), + ), + Divider(), + ListTile( + title: Text( + "Entwickelt von susch19 (Sascha Hering)", + ), + ), + ListTile( + title: Text("Version 0.40.0"), + ), + Divider(), + ListTile( + leading: SvgPicture.asset( + "assets/vectors/github.svg", + color: iconColor, + width: 32, + ), + title: Text(NSSLStrings.of(context).codeOnGithub()), + onTap: () { + var urlString = Uri.parse("https://github.com/susch19/nssl"); + canLaunchUrl(urlString).then((value) { + if (value) launchUrl(urlString); + }); + }, + ), + Divider(), + ListTile( + leading: SvgPicture.asset("assets/vectors/nssl_icon.svg", alignment: Alignment.center, width: 32), + title: Text(NSSLStrings.of(context).iconSource()), + onTap: () { + var urlString = Uri.parse("https://www.flaticon.com/free-icon/check-list_306470"); + canLaunchUrl(urlString).then((value) { + if (value) launchUrl(urlString); + }); + }, + ), + Divider(), + ListTile( + leading: SvgPicture.asset("assets/vectors/google_play.svg", alignment: Alignment.center, width: 32), + title: Text(NSSLStrings.of(context).playstoreEntry()), + onTap: () { + var urlString = Uri.parse("https://play.google.com/store/apps/details?id=de.susch19.nssl"); + canLaunchUrl(urlString).then((value) { + if (value) launchUrl(urlString); + }); + }, + ), + Divider(), + ListTile( + leading: Image.asset("assets/images/scandit.png", alignment: Alignment.center, width: 128, color: iconColor), + title: Text(NSSLStrings.of(context).scanditCredit()), + onTap: () { + var urlString = Uri.parse("https://scandit.com"); + canLaunchUrl(urlString).then((value) { + if (value) launchUrl(urlString); + }); + }, + ), + ], + ); + } +} diff --git a/lib/pages/barcode_scanner_page.dart b/lib/pages/barcode_scanner_page.dart index d43d909..ecd0751 100644 --- a/lib/pages/barcode_scanner_page.dart +++ b/lib/pages/barcode_scanner_page.dart @@ -5,7 +5,6 @@ */ import 'package:flutter/material.dart'; -import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:scandit_flutter_datacapture_barcode/scandit_flutter_datacapture_barcode.dart'; import 'package:scandit_flutter_datacapture_barcode/scandit_flutter_datacapture_barcode_capture.dart'; @@ -50,7 +49,7 @@ class _BarcodeScannerScreenState extends State @override void initState() { super.initState(); - WidgetsBinding.instance?.addObserver(this); + WidgetsBinding.instance.addObserver(this); // Use the recommended camera settings for the BarcodeCapture mode. _camera?.applySettings(BarcodeCapture.recommendedCameraSettings); @@ -92,16 +91,13 @@ class _BarcodeScannerScreenState extends State // Add a barcode capture overlay to the data capture view to render the location of captured barcodes on top of // the video preview. This is optional, but recommended for better visual feedback. - var overlay = BarcodeCaptureOverlay.withBarcodeCaptureForView( - _barcodeCapture, _captureView) + var overlay = BarcodeCaptureOverlay.withBarcodeCaptureForView(_barcodeCapture, _captureView) ..viewfinder = RectangularViewfinder.withStyleAndLineStyle( - RectangularViewfinderStyle.square, - RectangularViewfinderLineStyle.light); + RectangularViewfinderStyle.square, RectangularViewfinderLineStyle.light); // Adjust the overlay's barcode highlighting to match the new viewfinder styles and improve the visibility of feedback. // With 6.10 we will introduce this visual treatment as a new style for the overlay. - overlay.brush = Brush( - Color.fromARGB(0, 0, 0, 0), Color.fromARGB(255, 255, 255, 255), 3); + overlay.brush = Brush(Color.fromARGB(0, 0, 0, 0), Color.fromARGB(255, 255, 255, 255), 3); _captureView.addOverlay(overlay); @@ -118,9 +114,8 @@ class _BarcodeScannerScreenState extends State Widget build(BuildContext context) { Widget child; if (_isPermissionMessageVisible) { - child = PlatformText('No permission to access the camera!', - style: TextStyle( - fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black)); + child = Text('No permission to access the camera!', + style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black)); } else { child = _captureView; } @@ -137,23 +132,19 @@ class _BarcodeScannerScreenState extends State } @override - void didScan( - BarcodeCapture barcodeCapture, BarcodeCaptureSession session) async { + void didScan(BarcodeCapture barcodeCapture, BarcodeCaptureSession session) async { _barcodeCapture.isEnabled = false; var code = session.newlyRecognizedBarcodes.first; - var data = (code.data == null || code.data?.isEmpty == true) - ? code.rawData - : code.data; + var data = (code.data == null || code.data?.isEmpty == true) ? code.rawData : code.data; Navigator.pop(context, data); } @override - void didUpdateSession( - BarcodeCapture barcodeCapture, BarcodeCaptureSession session) {} + void didUpdateSession(BarcodeCapture barcodeCapture, BarcodeCaptureSession session) {} @override void dispose() { - WidgetsBinding.instance?.removeObserver(this); + WidgetsBinding.instance.removeObserver(this); _barcodeCapture.removeListener(this); _barcodeCapture.isEnabled = false; _camera?.switchToDesiredState(FrameSourceState.off); diff --git a/lib/pages/bought_items.dart b/lib/pages/bought_items.dart index ecdde90..cd0e257 100644 --- a/lib/pages/bought_items.dart +++ b/lib/pages/bought_items.dart @@ -1,119 +1,222 @@ +import 'dart:collection'; +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:nssl/localization/nssl_strings.dart'; import 'package:nssl/models/model_export.dart'; import 'package:nssl/server_communication//s_c.dart'; import 'package:nssl/server_communication/return_classes.dart'; import 'package:nssl/helper/iterable_extensions.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final _historyDateTimesProvider = StateProvider>((ref) { + return []; +}); +final _shoppingItemsProvider = StateProvider>((ref) { + return []; +}); +final _searchModeProvider = StateProvider.autoDispose((ref) { + return false; +}); + +final _shoppingItemsGroupedProvider = Provider.family, DateTime>((ref, arg) { + var items = ref.watch(_shoppingItemsProvider); + + var forDate = DateTime.utc(arg.year, arg.month, arg.day); + return items.where((element) { + var changed = element.changed!; + var changedFormat = DateTime.utc(changed.year, changed.month, changed.day); + return changedFormat == forDate; + }); +}); + +final _filterProvider = StateProvider.autoDispose((ref) { + return ""; +}); +final _filterAsLowercaseProvider = Provider.autoDispose((ref) { + return ref.watch(_filterProvider).toLowerCase(); +}); + +final _filteredShoppingItemsGroupedProvider = Provider.family.autoDispose, DateTime>((ref, arg) { + var items = ref.watch(_shoppingItemsGroupedProvider(arg)); + var isFiltering = ref.watch(_searchModeProvider); + var filter = ref.watch(_filterAsLowercaseProvider); + return items.where((element) => !isFiltering || element.name.toLowerCase().contains(filter)); +}); + +final _filteredDateTimeProvider = Provider.autoDispose>( + (ref) { + var dates = ref.watch(_historyDateTimesProvider); + var isFiltering = ref.watch(_searchModeProvider); + var filter = ref.watch(_filterAsLowercaseProvider); + if (!isFiltering || filter.isEmpty) return dates; + + return dates.where((element) => ref + .read(_filteredShoppingItemsGroupedProvider(element)) + .any((element) => element.name.toLowerCase().contains(filter))); + }, +); + +final _tabCountProvider = Provider.autoDispose((ref) => ref.watch(_filteredDateTimeProvider).length); + +final _shoppingItemsFromServerProvider = + FutureProvider.autoDispose.family, int>((ref, listId) async { + var o = await ShoppingListSync.getList(listId, null, bought: true); + + if (o.statusCode == 500) { + return []; + } + + var z = GetBoughtListResult.fromJson(o.body); + + var shoppingItems = []; + + shoppingItems.addAll(z.products.map((f) => ShoppingItem(f.name, listId, f.sortOrder, + id: f.id, amount: f.amount, changed: f.changed, created: f.created, crossedOut: false))); + + ref.read(_shoppingItemsProvider.notifier).state = shoppingItems; + HashSet dates = HashSet(); + + DateTime dateTimeToDate(DateTime dateTime) { + return DateTime.utc(dateTime.year, dateTime.month, dateTime.day); + } -class BoughtItemsPage extends StatefulWidget { + for (var item in shoppingItems) dates.add(dateTimeToDate(item.changed!)); + var dateList = dates.toList(); + dateList.sort(((a, b) => b.compareTo(a))); + ref.read(_historyDateTimesProvider.notifier).state = dateList; + + return shoppingItems; +}); + +class BoughtItemsPage extends ConsumerStatefulWidget { BoughtItemsPage(this.listId, {Key? key, this.title}) : super(key: key); final String? title; final int listId; @override - _BoughtItemsPagePageState createState() => - new _BoughtItemsPagePageState(listId); + _BoughtItemsPagePageState createState() => new _BoughtItemsPagePageState(listId); } -class _BoughtItemsPagePageState extends State - with SingleTickerProviderStateMixin { +class _BoughtItemsPagePageState extends ConsumerState with TickerProviderStateMixin { final GlobalKey _mainScaffoldKey = GlobalKey(); var tec = TextEditingController(); - var shoppingItems = []; - var currentList = ShoppingList(0, "empty"); - var shoppingItemsGrouped = new Map>(); int k = 1; int listId; - TabController? _controller; + + late TabController _controller; @override void initState() { super.initState(); + _controller = TabController( + length: 0, + initialIndex: 0, + vsync: this, + ); + tec.addListener(() { + ref.read(_filterProvider.notifier).state = tec.text; + }); } @override void dispose() { - _controller!.dispose(); + _controller.dispose(); super.dispose(); } - _BoughtItemsPagePageState(this.listId) { - currentList = - User.shoppingLists.firstWhere((element) => element.id == listId); - - ShoppingListSync.getList(listId, null, bought: true).then((o) { - if (o.statusCode == 500) { - showInSnackBar("Internal Server Error"); - return; - } - var z = GetBoughtListResult.fromJson(o.body); - if (z.products.length <= 0) - showInSnackBar(NSSLStrings.of(context)!.nothingBoughtYet(), - duration: Duration(seconds: 10)); - else { - shoppingItems.addAll(z.products.map((f) => ShoppingItem(f.name) - ..id = f.id - ..amount = f.amount - ..changed = f.changed - ..created = f.created - ..crossedOut = false)); - DateTime date; - shoppingItems.sort((x, y) => y.changed!.compareTo(x.changed!)); - for (var item in shoppingItems) { - date = dateTimeToDate(item.changed!); - if (!shoppingItemsGrouped.containsKey(dateTimeToDate(item.changed!))) - shoppingItemsGrouped[date] = []; - shoppingItemsGrouped[date]!.add(item); - } - } - - setState(() { - _controller = TabController( - vsync: this, length: shoppingItemsGrouped.keys.length); - }); - }); + void didUpdateWidget(covariant BoughtItemsPage oldWidget) { + super.didUpdateWidget(oldWidget); + + var count = ref.watch(_tabCountProvider); + if (count != _controller.length) { + final oldIndex = _controller.index; + _controller.dispose(); + _controller = TabController( + length: count, + initialIndex: max(0, min(oldIndex, count)), + vsync: this, + ); + } } + _BoughtItemsPagePageState(this.listId); + DateTime dateTimeToDate(DateTime dateTime) { return DateTime.utc(dateTime.year, dateTime.month, dateTime.day); } @override Widget build(BuildContext context) { - if (_controller == null) - return Scaffold( + var fromServer = ref.watch(_shoppingItemsFromServerProvider(listId)); + + return fromServer.when( + loading: () { + return Scaffold( + appBar: AppBar( + title: Text(NSSLStrings.of(context).boughtProducts()), + actions: [], + ), + body: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + child: SizedBox(width: 40.0, height: 40.0, child: CircularProgressIndicator()), + padding: const EdgeInsets.only(top: 16.0), + ) + ], + )); + }, + data: (data) { + didUpdateWidget(this.widget); + return Scaffold( + key: _mainScaffoldKey, appBar: AppBar( - title: Text(NSSLStrings.of(context)!.boughtProducts()), - actions: [], - ), - body: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - child: SizedBox( - width: 40.0, - height: 40.0, - child: CircularProgressIndicator()), - padding: const EdgeInsets.only(top: 16.0), - ) + title: !ref.watch(_searchModeProvider) + ? Text(NSSLStrings.of(context).boughtProducts()) + : TextField( + decoration: InputDecoration(hintText: NSSLStrings.of(context).searchProductHint()), + controller: tec, + maxLines: 1, + autofocus: true, + ), + bottom: TabBar( + controller: _controller, + isScrollable: true, + indicator: getIndicator(), + tabs: createTabs(), + ), + actions: [ + ref.watch(_searchModeProvider) + ? IconButton( + onPressed: () { + ref.read(_searchModeProvider.notifier).state = false; + }, + icon: Icon(Icons.search_off)) + : IconButton( + onPressed: () { + ref.read(_searchModeProvider.notifier).state = true; + }, + icon: Icon(Icons.search)) ], - )); - - return Scaffold( - key: _mainScaffoldKey, - appBar: AppBar( - title: Text(NSSLStrings.of(context)!.boughtProducts()), - bottom: TabBar( - controller: _controller, - isScrollable: true, - indicator: getIndicator(), - tabs: createTabs(), - ), - ), - body: TabBarView( - controller: _controller, - children: createChildren(), - ), + ), + body: TabBarView( + controller: _controller, + children: createChildren(), + ), + ); + }, + error: (error, stackTrace) { + return Scaffold( + appBar: AppBar( + title: Text(NSSLStrings.of(context).boughtProducts()), + actions: [], + ), + body: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [Text("An error occured $error")], + )); + }, ); } @@ -134,26 +237,30 @@ class _BoughtItemsPagePageState extends State ); } - void showInSnackBar(String value, - {Duration? duration, SnackBarAction? action}) { + void showInSnackBar(String value, {Duration? duration, SnackBarAction? action}) { ScaffoldMessenger.of(context).removeCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(value), - duration: duration ?? Duration(seconds: 3), - action: action)); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(value), duration: duration ?? Duration(seconds: 3), action: action)); } List createTabs() { var tabs = []; - for (var item in shoppingItemsGrouped.keys) { - tabs.add(Tab(text: "${item.year}-${item.month}-${item.day}")); + var dates = ref.watch(_filteredDateTimeProvider); + for (var item in dates) { + tabs.add( + Tab(text: "${item.year}-${item.month.toString().padLeft(2, '0')}-${item.day.toString().padLeft(2, '0')}")); } + return tabs; } List createChildren() { + var currentList = ref.watch(currentListProvider); var children = []; - for (var item in shoppingItemsGrouped.keys) { + if (currentList == null) return children; + var dates = ref.watch(_filteredDateTimeProvider); + for (var item in dates) { + var items = ref.watch(_filteredShoppingItemsGroupedProvider(item)); children.add(SafeArea( top: false, bottom: false, @@ -163,42 +270,38 @@ class _BoughtItemsPagePageState extends State child: Card( child: Center( child: ListView( - children: shoppingItemsGrouped[item]! - .map( - (i) => ListTile( - title: Text(i.name!), - leading: Text(i.amount.toString() + "x"), - onTap: () async { - var existingItem = currentList.shoppingItems - ?.firstOrNull((item) => item?.name == i.name); - if (existingItem != null) { - var answer = - await ShoppingListSync.changeProductAmount( - currentList.id!, - existingItem.id, - i.amount, - context); - var p = - ChangeListItemResult.fromJson((answer).body); - existingItem.amount = p.amount; - existingItem.changed = p.changed; - } else { - var p = AddListItemResult.fromJson( - (await ShoppingListSync.addProduct(listId, - i.name, null, i.amount, context)) - .body); - var newItem = ShoppingItem(p.name) - ..amount = i.amount - ..id = p.productId; - - currentList.addSingleItem(newItem); - } - showInSnackBar( - "${i.amount}x ${i.name}${NSSLStrings.of(context)?.newProductAddedToList()}${currentList.name}"); - }, - ), - ) - .toList(growable: false), + children: items.map( + (i) { + return ListTile( + title: Text(i.name), + leading: Text(i.amount.toString() + "x"), + onTap: () async { + var shoppingItems = ref.read(currentShoppingItemsProvider); + var existingItem = shoppingItems.firstOrNull((item) => item.name == i.name); + var listsProvider = ref.read(shoppingListsProvider); + if (existingItem != null) { + var answer = await ShoppingListSync.changeProductAmount( + currentList.id, existingItem.id, i.amount, context); + var p = ChangeListItemResult.fromJson((answer).body); + listsProvider.addSingleItem( + currentList, existingItem.cloneWith(newAmount: p.amount, newChanged: p.changed)); + } else { + var p = AddListItemResult.fromJson( + (await ShoppingListSync.addProduct(listId, i.name, null, i.amount, context)).body); + int sortOrder = 0; + if (shoppingItems.length > 0) sortOrder = shoppingItems.last.sortOrder + 1; + var newItem = + ShoppingItem(p.name, currentList.id, sortOrder, amount: i.amount, id: p.productId); + + listsProvider.addSingleItem(currentList, newItem); + } + + showInSnackBar( + "${i.amount}x ${i.name}${NSSLStrings.of(context).newProductAddedToList()}${currentList.name}"); + }, + ); + }, + ).toList(growable: false), ), ), ), diff --git a/lib/pages/change_password.dart b/lib/pages/change_password.dart index 4226a0e..97c622f 100644 --- a/lib/pages/change_password.dart +++ b/lib/pages/change_password.dart @@ -5,6 +5,8 @@ import 'package:nssl/pages/login.dart'; import 'package:nssl/server_communication/return_classes.dart'; import 'package:nssl/server_communication/s_c.dart'; +import '../helper/password_service.dart'; + class ChangePasswordPage extends StatefulWidget { ChangePasswordPage({Key? key, this.scaffoldKey}) : super(key: key); @@ -16,49 +18,67 @@ class ChangePasswordPage extends StatefulWidget { class ChangePasswordPageState extends State { ChangePasswordPageState() : super(); GlobalKey _scaffoldKey = GlobalKey(); - var oldPwInput = ForInput(); - var newPwInput = ForInput(); - var newPw2Input = ForInput(); + var _oldPwInput = ForInput(); + var _newPwInput = ForInput(); + var _newPw2Input = ForInput(); + bool _initialized = false; void showInSnackBar(String value) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(value), duration: Duration(seconds: 3))); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(value), duration: Duration(seconds: 3))); } void _handleSubmitted() { bool error = false; _resetInput(); - if (_validateEmpty(oldPwInput.textEditingController)) { - oldPwInput.decoration = InputDecoration( - labelText: oldPwInput.decoration!.labelText, - helperText: oldPwInput.decoration!.helperText, - errorText: NSSLStrings.of(context)!.passwordEmptyError()); + if (_validateEmpty(_oldPwInput.textEditingController)) { + _oldPwInput.decoration = InputDecoration( + labelText: _oldPwInput.decoration!.labelText, + helperText: _oldPwInput.decoration!.helperText, + errorText: NSSLStrings.of(context).passwordEmptyError()); error = true; } - if (_validateEmpty(newPwInput.textEditingController)) { - newPwInput.decoration = InputDecoration( - labelText: newPwInput.decoration!.labelText, - helperText: newPwInput.decoration!.helperText, - errorText: NSSLStrings.of(context)!.passwordEmptyError()); + var errorCode = PasswordService.checkNewPassword(_newPwInput.textEditingController.text); + + if (errorCode != PasswordErrorCode.none) { + String errorText = ""; + switch (errorCode) { + case PasswordErrorCode.empty: + errorText = NSSLStrings.of(context).passwordEmptyError(); + break; + case PasswordErrorCode.none: + break; + case PasswordErrorCode.tooShort: + errorText = NSSLStrings.of(context).passwordTooShortError(); + break; + case PasswordErrorCode.missingCharacters: + errorText = NSSLStrings.of(context).passwordMissingCharactersError(); + break; + } + + _newPwInput.decoration = InputDecoration( + labelText: _newPwInput.decoration!.labelText, + helperText: _newPwInput.decoration!.helperText, + errorText: errorText); error = true; } - if (_validateEmpty(newPw2Input.textEditingController)) { - newPw2Input.decoration = InputDecoration( - labelText: newPw2Input.decoration!.labelText, - helperText: newPw2Input.decoration!.helperText, - errorText: NSSLStrings.of(context)!.passwordEmptyError()); + if (_validateEmpty(_newPw2Input.textEditingController)) { + _newPw2Input.decoration = InputDecoration( + labelText: _newPw2Input.decoration!.labelText, + helperText: _newPw2Input.decoration!.helperText, + errorText: NSSLStrings.of(context).passwordEmptyError()); error = true; } - setState(() => {}); - if (error == true) return; - if (newPwInput.textEditingController.text != - newPw2Input.textEditingController.text) { - newPw2Input.decoration = InputDecoration( - labelText: newPw2Input.decoration!.labelText, - helperText: newPw2Input.decoration!.helperText, - errorText: NSSLStrings.of(context)!.passwordsDontMatchError()); - setState(() => {}); + if (error == true) { + setState(() {}); + return; + } + if (_newPwInput.textEditingController.text != _newPw2Input.textEditingController.text) { + _newPw2Input.decoration = InputDecoration( + labelText: _newPw2Input.decoration!.labelText, + helperText: _newPw2Input.decoration!.helperText, + errorText: NSSLStrings.of(context).passwordsDontMatchError()); + setState(() {}); return; } _changePassword(); @@ -68,46 +88,38 @@ class ChangePasswordPageState extends State { _changePassword() async { var res = await UserSync.changePassword( - oldPwInput.textEditingController.text, - newPwInput.textEditingController.text, - User.token, - context); + _oldPwInput.textEditingController.text, _newPwInput.textEditingController.text, User.token, context); if (res.statusCode != 200) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(res.reasonPhrase!), duration: Duration(seconds: 3))); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(res.reasonPhrase!), duration: Duration(seconds: 3))); return; } var obj = Result.fromJson(res.body); - if (!obj.success!) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(obj.error!), duration: Duration(seconds: 3))); + if (!obj.success) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(obj.error), duration: Duration(seconds: 3))); return; } var dialog = AlertDialog( - title: Text(NSSLStrings.of(context)!.successful()), + title: Text(NSSLStrings.of(context).successful()), content: SingleChildScrollView( child: ListBody( - children: [Text(NSSLStrings.of(context)!.passwordSet())], + children: [Text(NSSLStrings.of(context).passwordSet())], ), ), actions: [ - TextButton( - child: const Text("OK"), - onPressed: () => Navigator.popUntil(context, (r) => r.isFirst)), + TextButton(child: const Text("OK"), onPressed: () => Navigator.popUntil(context, (r) => r.isFirst)), ]); showDialog(context: context, builder: (BuildContext context) => dialog); } _resetInput() { - oldPwInput.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.oldPasswordHint(), - labelText: NSSLStrings.of(context)!.oldPassword()); - newPwInput.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.newPasswordHint(), - labelText: NSSLStrings.of(context)!.newPassword()); - newPw2Input.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.new2PasswordHint(), - labelText: NSSLStrings.of(context)!.new2Password()); + _oldPwInput.decoration = InputDecoration( + helperText: NSSLStrings.of(context).oldPasswordHint(), labelText: NSSLStrings.of(context).oldPassword()); + _newPwInput.decoration = InputDecoration( + helperText: NSSLStrings.of(context).newPasswordHint(), labelText: NSSLStrings.of(context).newPassword()); + _newPw2Input.decoration = InputDecoration( + helperText: NSSLStrings.of(context).new2PasswordHint(), labelText: NSSLStrings.of(context).new2Password()); + _initialized = true; } @override @@ -117,45 +129,45 @@ class ChangePasswordPageState extends State { @override Widget build(BuildContext context) { - _resetInput(); + if (!_initialized) _resetInput(); return Scaffold( key: _scaffoldKey, resizeToAvoidBottomInset: false, - appBar: AppBar(title: Text(NSSLStrings.of(context)!.changePasswordPD())), + appBar: AppBar(title: Text(NSSLStrings.of(context).changePasswordPD())), body: Container( padding: const EdgeInsets.symmetric(horizontal: 32.0), child: Column(mainAxisAlignment: MainAxisAlignment.start, children: [ Flexible( child: TextField( - key: oldPwInput.key, - decoration: oldPwInput.decoration, - focusNode: oldPwInput.focusNode, + key: _oldPwInput.key, + decoration: _oldPwInput.decoration, + focusNode: _oldPwInput.focusNode, obscureText: true, - controller: oldPwInput.textEditingController, + controller: _oldPwInput.textEditingController, onSubmitted: (val) { - FocusScope.of(context).requestFocus(newPwInput.focusNode); + FocusScope.of(context).requestFocus(_newPwInput.focusNode); }, ), ), Flexible( child: TextField( - key: newPwInput.key, - decoration: newPwInput.decoration, - focusNode: newPwInput.focusNode, + key: _newPwInput.key, + decoration: _newPwInput.decoration, + focusNode: _newPwInput.focusNode, obscureText: true, - controller: newPwInput.textEditingController, + controller: _newPwInput.textEditingController, onSubmitted: (val) { - FocusScope.of(context).requestFocus(newPw2Input.focusNode); + FocusScope.of(context).requestFocus(_newPw2Input.focusNode); }, ), ), Flexible( child: TextField( - key: newPw2Input.key, - decoration: newPw2Input.decoration, - focusNode: newPw2Input.focusNode, + key: _newPw2Input.key, + decoration: _newPw2Input.decoration, + focusNode: _newPw2Input.focusNode, obscureText: true, - controller: newPw2Input.textEditingController, + controller: _newPw2Input.textEditingController, onSubmitted: (val) { _handleSubmitted(); }, @@ -167,7 +179,7 @@ class ChangePasswordPageState extends State { child: ElevatedButton( // child: Center( child: Text( - NSSLStrings.of(context)!.changePasswordButton(), + NSSLStrings.of(context).changePasswordButton(), ), // ), onPressed: _handleSubmitted, diff --git a/lib/pages/contributors.dart b/lib/pages/contributors.dart index 92dea2c..9044400 100644 --- a/lib/pages/contributors.dart +++ b/lib/pages/contributors.dart @@ -4,17 +4,17 @@ import 'package:nssl/models/model_export.dart'; import 'package:nssl/server_communication//s_c.dart'; import 'dart:async'; import 'package:nssl/server_communication/return_classes.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class ContributorsPage extends StatefulWidget { +class ContributorsPage extends ConsumerStatefulWidget { ContributorsPage(this.listId, {Key? key, this.title}) : super(key: key); final String? title; final int listId; @override - _ContributorsPagePageState createState() => - new _ContributorsPagePageState(listId); + _ContributorsPagePageState createState() => new _ContributorsPagePageState(listId); } -class _ContributorsPagePageState extends State { +class _ContributorsPagePageState extends ConsumerState { final GlobalKey _mainScaffoldKey = GlobalKey(); GlobalKey _iff = GlobalKey(); GlobalKey _ib = GlobalKey(); @@ -36,10 +36,8 @@ class _ContributorsPagePageState extends State { return; } GetContributorsResult z = GetContributorsResult.fromJson(o.body); - if (!z.success! || z.contributors.length <= 0) - showInSnackBar( - NSSLStrings.of(context)!.genericErrorMessageSnackbar() + - o.reasonPhrase!, + if (!z.success || z.contributors.length <= 0) + showInSnackBar(NSSLStrings.of(context).genericErrorMessageSnackbar() + o.reasonPhrase!, duration: Duration(seconds: 10)); else setState(() => conList.addAll(z.contributors)); @@ -54,9 +52,7 @@ class _ContributorsPagePageState extends State { title: Form( child: TextField( key: _iff, - decoration: InputDecoration( - hintText: NSSLStrings.of(context)! - .nameOfNewContributorHint()), + decoration: InputDecoration(hintText: NSSLStrings.of(context).nameOfNewContributorHint()), onSubmitted: (x) => _addContributor(x), autofocus: true, controller: tec))), @@ -74,10 +70,8 @@ class _ContributorsPagePageState extends State { Future _addContributor(String value) async { var o = await ShoppingListSync.addContributor(listId, value, context); AddContributorResult z = AddContributorResult.fromJson(o.body); - if (!z.success!) - showInSnackBar( - NSSLStrings.of(context)!.genericErrorMessageSnackbar() + z.error!, - duration: Duration(seconds: 10)); + if (!z.success) + showInSnackBar(NSSLStrings.of(context).genericErrorMessageSnackbar() + z.error, duration: Duration(seconds: 10)); else setState(() => conList.add(ContributorResult() ..name = z.name @@ -88,25 +82,20 @@ class _ContributorsPagePageState extends State { Widget buildBody() { bool? isAdmin = false; if (conList.length > 0) { - isAdmin = conList - .firstWhere( - (x) => x.name!.toLowerCase() == User.username!.toLowerCase()) - .isAdmin; + var user = ref.watch(userProvider); + isAdmin = conList.firstWhere((x) => x.name!.toLowerCase() == user.username.toLowerCase()).isAdmin; var listView = ListView.builder( itemBuilder: (c, i) { return ListTile( title: Text(conList[i].name! + (conList[i].isAdmin! - ? NSSLStrings.of(context)!.contributorAdmin() - : NSSLStrings.of(context)!.contributorUser())), - trailing: isAdmin! && - conList[i].name!.toLowerCase() != - User.username!.toLowerCase() + ? NSSLStrings.of(context).contributorAdmin() + : NSSLStrings.of(context).contributorUser())), + trailing: isAdmin! && conList[i].name!.toLowerCase() != user.username.toLowerCase() ? PopupMenuButton( padding: EdgeInsets.zero, onSelected: popupMenuClicked, - itemBuilder: (BuildContext context) => - >[ + itemBuilder: (BuildContext context) => >[ PopupMenuItem( value: conList[i].userId.toString() + "\u{1E}ChangeRight", //x.id.toString() + "\u{1E}" + 'Rename', @@ -115,18 +104,14 @@ class _ContributorsPagePageState extends State { ? const Icon(Icons.arrow_downward) : const Icon(Icons.arrow_upward)), title: (conList[i].isAdmin! - ? Text(NSSLStrings.of(context)! - .demoteMenu()) - : Text(NSSLStrings.of(context)! - .promoteMenu())))), + ? Text(NSSLStrings.of(context).demoteMenu()) + : Text(NSSLStrings.of(context).promoteMenu())))), const PopupMenuDivider(), // ignore: list_element_type_not_assignable PopupMenuItem( value: conList[i].userId.toString() + "\u{1E}Remove", //x.id.toString() + "\u{1E}" + 'Remove', child: ListTile( - leading: const Icon(Icons.delete), - title: Text( - NSSLStrings.of(context)!.remove()))) + leading: const Icon(Icons.delete), title: Text(NSSLStrings.of(context).remove()))) ]) : const Text(""), onTap: () => {}); @@ -138,21 +123,17 @@ class _ContributorsPagePageState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Container( - child: SizedBox( - width: 40.0, height: 40.0, child: CircularProgressIndicator()), + child: SizedBox(width: 40.0, height: 40.0, child: CircularProgressIndicator()), padding: const EdgeInsets.only(top: 16.0), ) ], ); } - void showInSnackBar(String value, - {Duration? duration, SnackBarAction? action}) { + void showInSnackBar(String value, {Duration? duration, SnackBarAction? action}) { ScaffoldMessenger.of(context).removeCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(value), - duration: duration ?? Duration(seconds: 3), - action: action)); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(value), duration: duration ?? Duration(seconds: 3), action: action)); } Future popupMenuClicked(String value) async { @@ -161,14 +142,12 @@ class _ContributorsPagePageState extends State { switch (command) { case "Remove": var userId = int.parse(splitted[0]); - var res = - await ShoppingListSync.deleteContributor(listId, userId, context); + var res = await ShoppingListSync.deleteContributor(listId, userId, context); var enres = Result.fromJson(res.body); - if (!enres.success!) - showInSnackBar(enres.error!); + if (!enres.success) + showInSnackBar(enres.error); else { - showInSnackBar(conList.firstWhere((x) => x.userId == userId).name! + - " was removed successfully"); + showInSnackBar(conList.firstWhere((x) => x.userId == userId).name! + " was removed successfully"); setState(() => conList.removeWhere((x) => x.userId == userId)); } break; @@ -176,8 +155,8 @@ class _ContributorsPagePageState extends State { var userId = int.parse(splitted[0]); var res = await ShoppingListSync.changeRight(listId, userId, context); var enres = Result.fromJson(res.body); - if (!enres.success!) - showInSnackBar(enres.error!); + if (!enres.success) + showInSnackBar(enres.error); else { ShoppingListSync.getContributors(listId, context).then((o) { if (o.statusCode == 500) { @@ -185,10 +164,8 @@ class _ContributorsPagePageState extends State { return; } GetContributorsResult z = GetContributorsResult.fromJson(o.body); - if (!z.success! || z.contributors.length <= 0) - showInSnackBar( - NSSLStrings.of(context)!.genericErrorMessageSnackbar() + - z.error!, + if (!z.success || z.contributors.length <= 0) + showInSnackBar(NSSLStrings.of(context).genericErrorMessageSnackbar() + z.error, duration: Duration(seconds: 10)); else conList.clear(); diff --git a/lib/pages/custom_theme_page.dart b/lib/pages/custom_theme_page.dart index e592ea7..462d665 100644 --- a/lib/pages/custom_theme_page.dart +++ b/lib/pages/custom_theme_page.dart @@ -46,15 +46,15 @@ class CustomThemePageState extends State { return await (showDialog( context: context, builder: (BuildContext context) => AlertDialog( - content: Text(NSSLStrings.of(context)!.discardNewTheme(), style: dialogTextStyle), + content: Text(NSSLStrings.of(context).discardNewTheme(), style: dialogTextStyle), actions: [ TextButton( - child: Text(NSSLStrings.of(context)!.cancelButton()), + child: Text(NSSLStrings.of(context).cancelButton()), onPressed: () { Navigator.of(context).pop(false); }), TextButton( - child: Text(NSSLStrings.of(context)!.discardButton()), + child: Text(NSSLStrings.of(context).discardButton()), onPressed: () { Navigator.of(context).pop(true); }), @@ -111,7 +111,7 @@ class CustomThemePageState extends State { // backgroundColor: td.scaffoldBackgroundColor, key: _scaffoldKey, appBar: AppBar( - title: Text(NSSLStrings.of(context)!.changeTheme() + title: Text(NSSLStrings.of(context).changeTheme() // , style: textColorTheme ), // backgroundColor: td.primaryColor, @@ -124,7 +124,7 @@ class CustomThemePageState extends State { child: ListView(padding: const EdgeInsets.all(16.0), children: [ Column(mainAxisSize: MainAxisSize.min, children: [ Text( - NSSLStrings.of(context)!.changePrimaryColor(), + NSSLStrings.of(context).changePrimaryColor(), // style: td.textTheme.subtitle1, ), Slider( @@ -137,7 +137,7 @@ class CustomThemePageState extends State { ]), Column(mainAxisSize: MainAxisSize.min, children: [ Text( - NSSLStrings.of(context)!.changeAccentColor(), + NSSLStrings.of(context).changeAccentColor(), // style: td.textTheme.subtitle1, ), Slider( @@ -150,7 +150,7 @@ class CustomThemePageState extends State { ]), Row(children: [ Text( - NSSLStrings.of(context)!.changeDarkTheme(), + NSSLStrings.of(context).changeDarkTheme(), // style: td.textTheme.subtitle1, ), Checkbox( diff --git a/lib/pages/forgot_password.dart b/lib/pages/forgot_password.dart new file mode 100644 index 0000000..6611246 --- /dev/null +++ b/lib/pages/forgot_password.dart @@ -0,0 +1,118 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:nssl/localization/nssl_strings.dart'; +import 'package:nssl/server_communication/s_c.dart'; + +class ForgotPasswordPage extends StatefulWidget { + ForgotPasswordPage({Key? key, this.scaffoldKey}) : super(key: key); + + final GlobalKey? scaffoldKey; + @override + ForgotPasswordPageState createState() => ForgotPasswordPageState(); +} + +class PersonData { + String name = ''; + String email = ''; + String password = ''; +} + +class ForInput { + TextEditingController textEditingController = TextEditingController(); + String errorText = ''; + GlobalKey key = GlobalKey(); + InputDecoration? decoration; + FocusNode focusNode = FocusNode(); +} + +class ForgotPasswordPageState extends State { + ForgotPasswordPageState() : super(); + GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _formKey = GlobalKey(); + + var emailInput = ForInput(); + var submit = ForInput(); + var validateMode = AutovalidateMode.disabled; + + void showInSnackBar(String value) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(value), duration: Duration(seconds: 3))); + } + + Future _handleSubmitted() async { + final FormState form = _formKey.currentState!; + if (!form.validate()) { + validateMode = AutovalidateMode.onUserInteraction; + return; + } + + String email = emailInput.textEditingController.text; + + if (emailInput.textEditingController.text.length > 0 && + _validateEmail(emailInput.textEditingController.text) == null) { + var res = await UserSync.resetPassword(email, context); + if (!HelperMethods.reactToRespone(res, context, scaffoldState: _scaffoldKey.currentState)) return; + await showDialog( + context: context, + builder: (BuildContext context) => + AlertDialog(content: Text(NSSLStrings.of(context).requestPasswordResetSuccess()), actions: [ + TextButton( + child: Text(NSSLStrings.of(context).okayButton()), + onPressed: () { + Navigator.of(context).pop(true); + }) + ])); + Navigator.pop(context); + } + } + + String? _validateEmail(String? value) { + if (value == null) return value; + if (value.isEmpty) return NSSLStrings.of(context).emailRequiredError(); + RegExp email = RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'); + if (!email.hasMatch(value)) return NSSLStrings.of(context).emailIncorrectFormatError(); + return null; + } + + _resetInput() { + emailInput.decoration = InputDecoration(labelText: "E-Mail"); + } + + @override + Widget build(BuildContext context) { + _resetInput(); + return Scaffold( + key: _scaffoldKey, + resizeToAvoidBottomInset: true, + appBar: AppBar(title: Text(NSSLStrings.of(context).requestPasswordResetTitle())), + body: Form( + key: _formKey, + autovalidateMode: validateMode, + child: ListView(padding: const EdgeInsets.symmetric(horizontal: 32.0), children: [ + ListTile( + title: TextFormField( + key: emailInput.key, + decoration: emailInput.decoration, + controller: emailInput.textEditingController, + keyboardType: TextInputType.emailAddress, + autofillHints: [AutofillHints.username, AutofillHints.email], + autocorrect: false, + autofocus: true, + validator: _validateEmail, + onSaved: (s) => _handleSubmitted(), + ), + ), + ListTile( + title: Container( + child: ElevatedButton( + key: submit.key, + child: Center(child: Text(NSSLStrings.of(context).requestPasswordResetButton())), + onPressed: _handleSubmitted, + ), + padding: const EdgeInsets.only(top: 16.0)), + ), + ]), + ), + ); + } +} diff --git a/lib/pages/login.dart b/lib/pages/login.dart index 2c0331b..8af6dcd 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -1,13 +1,14 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:nssl/firebase/cloud_messsaging.dart'; import 'package:nssl/localization/nssl_strings.dart'; -import 'package:nssl/main.dart'; import 'package:nssl/models/model_export.dart'; import 'package:nssl/server_communication/return_classes.dart'; import 'package:nssl/server_communication/s_c.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class LoginPage extends StatefulWidget { +class LoginPage extends ConsumerStatefulWidget { LoginPage({Key? key, this.scaffoldKey}) : super(key: key); final GlobalKey? scaffoldKey; @@ -29,7 +30,7 @@ class ForInput { FocusNode focusNode = FocusNode(); } -class LoginPageState extends State { +class LoginPageState extends ConsumerState { LoginPageState() : super(); GlobalKey _scaffoldKey = GlobalKey(); final GlobalKey _formKey = GlobalKey(); @@ -40,8 +41,7 @@ class LoginPageState extends State { var validateMode = AutovalidateMode.disabled; void showInSnackBar(String value) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(value), duration: Duration(seconds: 3))); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(value), duration: Duration(seconds: 3))); } Future _handleSubmitted() async { @@ -52,39 +52,19 @@ class LoginPageState extends State { validateMode = AutovalidateMode.onUserInteraction; return; } - //form.save(); - /* var validate = _validateName(nameInput.textEditingController.text); - if (validate != null) { - nameInput.decoration = InputDecoration( - labelText: nameInput.decoration.labelText, - helperText: nameInput.decoration.helperText, - errorText: validate); - error = true; - } - if (_validatePassword(pwInput.textEditingController.text) != null) { - pwInput.decoration = InputDecoration( - labelText: pwInput.decoration.labelText, - helperText: pwInput.decoration.helperText, - errorText: _validatePassword(pwInput.textEditingController.text)); - error = true; - } - setState(() => {});*/ -// if (error == true) return; String name = nameInput.textEditingController.text; String password = pwInput.textEditingController.text; if (_validateEmail(nameInput.textEditingController.text) != null) { var res = await UserSync.login(name, password, context); - if (!HelperMethods.reactToRespone(res, context, - scaffoldState: _scaffoldKey.currentState)) + if (!HelperMethods.reactToRespone(res, context, scaffoldState: _scaffoldKey.currentState)) return; else _handleLoggedIn(LoginResult.fromJson(res.body)); } else { var res = await UserSync.loginEmail(name, password, context); - if (!HelperMethods.reactToRespone(res, context, - scaffoldState: _scaffoldKey.currentState)) + if (!HelperMethods.reactToRespone(res, context, scaffoldState: _scaffoldKey.currentState)) return; else _handleLoggedIn(LoginResult.fromJson(res.body)); @@ -92,67 +72,54 @@ class LoginPageState extends State { } Future _handleLoggedIn(LoginResult res) async { - if (!res.success!) { - showInSnackBar(res.error!); + if (!res.success) { + showInSnackBar(res.error); return; } - showInSnackBar(NSSLStrings.of(context)!.loginSuccessfulMessage()); - bool firstBoot = User.username == null; + showInSnackBar(NSSLStrings.of(context).loginSuccessfulMessage()); + + var userState = ref.watch(userStateProvider.notifier); User.token = res.token; - User.username = res.username; - User.eMail = res.eMail; - User.ownId = res.id; - await User.save(); - firebaseMessaging?.subscribeToTopic(res.username! + "userTopic"); - if (firstBoot) { - await _getAllListsInit(); - if (User.shoppingLists.length > 0) { - User.currentList = User.shoppingLists.first; - User.currentListIndex = 1; - await User.save(); - } - runApp(NSSL()); - } else - Navigator.pop(context); - } + var user = User(res.id, res.username, res.eMail); - Future _getAllListsInit() async { - await ShoppingList.reloadAllLists(context); - setState(() => {}); + if (!kIsWeb) firebaseMessaging?.subscribeToTopic(res.username + "userTopic"); + + var listController = ref.read(shoppingListsProvider); + await listController.reloadAllLists(context); + + user.save(0); + ref.watch(currentListIndexProvider.notifier).state = 0; + userState.state = user; } String? _validateName(String? value) { - if (value!.isEmpty) - return NSSLStrings.of(context)!.nameEmailRequiredError(); - if (value.length < 4) - return NSSLStrings.of(context)!.usernameToShortError(); + if (value!.isEmpty) return NSSLStrings.of(context).nameEmailRequiredError(); + if (value.length < 4) return NSSLStrings.of(context).usernameToShortError(); return null; } String? _validateEmail(String value) { - if (value.isEmpty) return NSSLStrings.of(context)!.emailRequiredError(); + if (value.isEmpty) return NSSLStrings.of(context).emailRequiredError(); RegExp email = RegExp( r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'); - if (!email.hasMatch(value)) - return NSSLStrings.of(context)!.emailIncorrectFormatError(); + + if (!email.hasMatch(value)) return NSSLStrings.of(context).emailIncorrectFormatError(); return null; } String? _validatePassword(String? value) { - if (pwInput.textEditingController.text.isEmpty) - return NSSLStrings.of(context)!.passwordEmptyError(); + if (pwInput.textEditingController.text.isEmpty) return NSSLStrings.of(context).passwordEmptyError(); return null; } _resetInput() { nameInput.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.usernameOrEmailForLoginHint(), - labelText: NSSLStrings.of(context)!.usernameOrEmailTitle()); + helperText: NSSLStrings.of(context).usernameOrEmailForLoginHint(), + labelText: NSSLStrings.of(context).usernameOrEmailTitle()); pwInput.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.choosenPasswordHint(), - labelText: NSSLStrings.of(context)!.password()); + helperText: NSSLStrings.of(context).choosenPasswordHint(), labelText: NSSLStrings.of(context).password()); } @override @@ -168,7 +135,7 @@ class LoginPageState extends State { return Scaffold( key: _scaffoldKey, resizeToAvoidBottomInset: true, - appBar: AppBar(title: Text(NSSLStrings.of(context)!.login())), + appBar: AppBar(title: Text(NSSLStrings.of(context).login())), body: Form( key: _formKey, autovalidateMode: validateMode, @@ -183,10 +150,7 @@ class LoginPageState extends State { //onChanged: (input) => nameInput.errorText = _validateName(input), controller: nameInput.textEditingController, keyboardType: TextInputType.emailAddress, - autofillHints: [ - AutofillHints.username, - AutofillHints.email - ], + autofillHints: [AutofillHints.username, AutofillHints.email], autocorrect: false, autofocus: true, validator: _validateName, @@ -210,8 +174,7 @@ class LoginPageState extends State { title: Container( child: ElevatedButton( key: submit.key, - child: Center( - child: Text(NSSLStrings.of(context)!.loginButton())), + child: Center(child: Text(NSSLStrings.of(context).loginButton())), onPressed: _handleSubmitted, ), padding: const EdgeInsets.only(top: 16.0)), @@ -221,25 +184,25 @@ class LoginPageState extends State { padding: const EdgeInsets.only(top: 40.0), child: TextButton( onPressed: () { - User.username == null + var userId = ref.read(userIdProvider); + userId == null || userId < 0 ? Navigator.pushNamed(context, "/registration") : Navigator.popAndPushNamed(context, "/registration"); }, - child: Text(NSSLStrings.of(context)!.registerTextOnLogin()), + child: Text(NSSLStrings.of(context).registerTextOnLogin()), + ), + ), + ), + ListTile( + title: Container( + child: TextButton( + onPressed: () { + Navigator.pushNamed(context, "/forgot_password"); + }, + child: Text(NSSLStrings.of(context).forgotPassword()), ), ), ), -// ListTile( -// title: Container( -// child: TextButton( -// onPressed: () { -// Navigator.pushNamed(context, "/forgot_password"); -// }, -// child: -// Text(NSSLStrings.of(context).forgotPassword()), -// ), -// ), -// ), //padding: EdgeInsets.only( // top: MediaQuery.of(context).size.height / 5), ] diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index d607ac3..5841c56 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:io'; import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:flutter/foundation.dart'; +import 'package:nssl/helper/choose_dialog.dart'; import 'package:nssl/helper/simple_dialog.dart'; import 'package:nssl/manager/export_manager.dart'; import 'package:nssl/options/themes.dart'; @@ -13,27 +15,25 @@ import 'package:nssl/helper/simple_dialog_single_input.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import 'package:nssl/localization/nssl_strings.dart'; -import 'package:nssl/firebase/cloud_messsaging.dart'; - +import 'package:nssl/helper/iterable_extensions.dart'; +import 'package:share_handler/share_handler.dart'; import '../main.dart'; import 'barcode_scanner_page.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter/services.dart' show PlatformException; -class MainPage extends StatefulWidget { +class MainPage extends ConsumerStatefulWidget { @override MainPageState createState() => MainPageState(); } -class MainPageState extends State - with TickerProviderStateMixin, WidgetsBindingObserver { - BuildContext? cont; - +class MainPageState extends ConsumerState with TickerProviderStateMixin, WidgetsBindingObserver { final ScrollController _mainController = ScrollController(); final ScrollController _drawerController = ScrollController(); String? ean = ""; bool performanceOverlay = false; bool materialGrid = false; - bool isReorderingItems = false; AnimationController? _controller; Animation? _drawerContentsOpacity; @@ -41,20 +41,22 @@ class MainPageState extends State bool _showDrawerContents = true; bool insideSortAndOrderCrossedOut = false; bool insideUpdateOrderIndicies = false; + SharedMedia? media; @override Future didChangeAppLifecycleState(AppLifecycleState state) async { if (state.index == 0) { - await Startup.loadMessagesFromFolder(setState); + await Startup.loadMessagesFromFolder(ref); } } @override void initState() { super.initState(); - WidgetsBinding.instance!.addObserver(this); - Startup.deleteMessagesFromFolder(); - Startup.initializeNewListsFromServer(setState); + initPlatformState(); + WidgetsBinding.instance.addObserver(this); + if (!kIsWeb) Startup.deleteMessagesFromFolder(); + Startup.initializeNewListsFromServer(ref); _controller = AnimationController( vsync: this, @@ -75,254 +77,98 @@ class MainPageState extends State @override Widget build(BuildContext context) { + var currentList = ref.watch(currentListProvider); return Scaffold( appBar: AppBar( title: Text( - User.currentList?.name ?? NSSLStrings.of(context)!.noListLoaded(), + currentList?.name ?? NSSLStrings.of(context).noListLoaded(), ), - actions: isReorderingItems - ? [] - : [ - PopupMenuButton( - onSelected: selectedOption, - itemBuilder: (BuildContext context) => - >[ - PopupMenuItem( - value: 'Options', - child: Text( - NSSLStrings.of(context)!.changeTheme())), - PopupMenuItem( - value: 'deleteCrossedOut', - child: Text(NSSLStrings.of(context)! - .deleteCrossedOutPB())), - PopupMenuItem( - value: 'reorderItems', - child: Text( - NSSLStrings.of(context)!.reorderItems())), - ]) - ]), - body: buildBody(context), - floatingActionButton: isReorderingItems ? acceptReordingFAB() : null, + actions: _getMainDropdownActions(context)), + body: ShoppingListWidget(this), + floatingActionButton: acceptReordingFAB(), drawer: _buildDrawer(context), - persistentFooterButtons: isReorderingItems + persistentFooterButtons: ref.watch(_isReorderingProvider) || currentList == null ? [] : [ TextButton( - child: Text(NSSLStrings.of(context)!.addPB()), - onPressed: () => _addWithoutSearchDialog(context)) + child: Text(NSSLStrings.of(context).addPB()), onPressed: () => _addWithoutSearchDialog(context)) ] + - (Platform.isAndroid - ? [ - TextButton( - child: Text(NSSLStrings.of(context)!.scanPB()), - onPressed: _getEAN) - ] + (!kIsWeb && Platform.isAndroid + ? [TextButton(child: Text(NSSLStrings.of(context).scanPB()), onPressed: () => _getEAN(currentList))] : []) + - [ - TextButton( - child: Text(NSSLStrings.of(context)!.searchPB()), - onPressed: search) - ]); + [TextButton(child: Text(NSSLStrings.of(context).searchPB()), onPressed: search)]); } - Widget buildBody(BuildContext context) { - cont = context; + void _onReorderItems(int oldIndex, int newIndex) { + var currentList = ref.watch(currentShoppingItemsProvider); - if (User.currentList == null || User.currentList!.shoppingItems == null) - return const Text(""); - if (User.currentList!.shoppingItems!.any((item) => item?.sortOrder == null)) - updateOrderIndiciesAndSave(); + if (currentList.isEmpty) return; - User.currentList!.shoppingItems! - .sort((a, b) => a!.sortOrder!.compareTo(b!.sortOrder!)); - var lv; - if (User.currentList!.shoppingItems!.length > 0) { - var mainList = User.currentList!.shoppingItems!.map((x) { - if (x == null || x.name == null) return Text("Null"); - // return Text(x.name!); - - var lt = ListTile( - key: ValueKey(x), - title: Wrap( - children: [ - Text( - x.name ?? "", - maxLines: 2, - softWrap: true, - style: TextStyle( - decoration: x.crossedOut - ? TextDecoration.lineThrough - : TextDecoration.none), - ), - ], - ), - leading: PopupMenuButton( - child: FittedBox( - child: Row(children: [ - Text(x.amount.toString() + "x"), - const Icon(Icons.expand_more, size: 16.0), - SizedBox(height: 38.0), //for larger clickable size (2 Lines) - ]), - ), - initialValue: x.amount.toString(), - onSelected: (y) => shoppingItemChange(x, int.parse(y) - x.amount), - itemBuilder: buildChangeMenuItems, - ), - trailing: isReorderingItems ? Icon(Icons.reorder) : null, - onTap: isReorderingItems ? null : (() => crossOutMainListItem(x)), - onLongPress: isReorderingItems ? null : (() => renameListItem(x)), - ); - - if (isReorderingItems) { - return lt; - } else { - return Dismissible( - key: ValueKey(x), - child: lt, - onDismissed: (DismissDirection d) => handleDismissMain(d, x), - direction: DismissDirection.startToEnd, - background: Container( - decoration: BoxDecoration(color: Theme.of(context).primaryColor), - child: ListTile( - leading: Icon(Icons.delete, - // color: Theme.of(context).accentIconTheme.color, - size: 36.0), - ), - ), - ); - } - }).toList(growable: true); + ShoppingItem olditem = currentList[oldIndex]; + currentList.sort((a, b) => a.sortOrder.compareTo(b.sortOrder)); - if (isReorderingItems) { - lv = ReorderableListView( - onReorder: _onReorderItems, - scrollDirection: Axis.vertical, - children: mainList); - } else { - lv = CustomScrollView( - controller: _mainController, - slivers: [ - SliverFixedExtentList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - return Container( - alignment: FractionalOffset.center, - child: mainList[index], - ); - }, childCount: mainList.length), - itemExtent: 50.0) - ], - physics: AlwaysScrollableScrollPhysics(), - ); - } + currentList.remove(olditem); + if (newIndex > oldIndex) { + currentList.insert(newIndex - 1, olditem); } else - lv = ListView( - physics: const AlwaysScrollableScrollPhysics(), - children: [ListTile(title: const Text(""))], - ); - return RefreshIndicator( - child: lv, - onRefresh: _handleMainListRefresh, - ); - } - - void _onReorderItems(int oldIndex, int newIndex) { - if (User.currentList == null) return; - - ShoppingItem? item = User.currentList!.shoppingItems![oldIndex]; - if (item?.crossedOut ?? false) return; - setState( - () { - item!.sortOrder = newIndex; - for (var old = oldIndex + 1; old < newIndex; old++) { - item = User.currentList!.shoppingItems![old]; - if (!item!.crossedOut) - User.currentList!.shoppingItems![old]!.sortOrder = - User.currentList!.shoppingItems![old]!.sortOrder! - 1; - } - for (var newI = newIndex; newI < oldIndex; newI++) { - item = User.currentList!.shoppingItems![newI]; - if (!item!.crossedOut) - User.currentList!.shoppingItems![newI]!.sortOrder = - User.currentList!.shoppingItems![newI]!.sortOrder! - 1; - } - }, - ); - } - - void sortAndOrderCrossedOut() { - final crossedOffset = 0xFFFFFFFF; - setState(() { - for (var crossedOut in User.currentList?.shoppingItems - ?.where((x) => x!.crossedOut && x.sortOrder! < crossedOffset) ?? - []) { - crossedOut?.sortOrder = crossedOut.sortOrder! + crossedOffset; + currentList.insert(newIndex, olditem); + + var newList = []; + int currentSortOrder = 0; + for (int i = 0; i < currentList.length; i++) { + var currItem = currentList[i]; + if (i < oldIndex && i < newIndex) { + newList.add(currItem); + currentSortOrder = currentList[i].sortOrder; + } else if (i >= oldIndex || i >= newIndex) { + newList.add(currItem.cloneWith(newSortOrder: ++currentSortOrder)); } - for (var notCrossedOut in User.currentList?.shoppingItems - ?.where((x) => !x!.crossedOut && x.sortOrder! > crossedOffset) ?? - []) { - notCrossedOut!.sortOrder = notCrossedOut.sortOrder! - crossedOffset; - } - }); - } - - void updateOrderIndiciesAndSave({bool syncToServer = false}) async { - var i = 1; - for (var item in User.currentList?.shoppingItems ?? []) { - item?.sortOrder = i; - i++; } - sortAndOrderCrossedOut(); - User.currentList?.save(); + + var shoppingState = ref.watch(shoppingItemsProvider.notifier); + var newState = shoppingState.state.toList(); + newState.removeElements(currentList); + newState.addAll(newList); + shoppingState.state = newState; } - void showInSnackBar(String value, - {Duration? duration, SnackBarAction? action}) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(value), - duration: duration ?? Duration(seconds: 3), - action: action)); + void showInSnackBar(String value, {Duration? duration, SnackBarAction? action}) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(value), duration: duration ?? Duration(seconds: 3), action: action)); } - void showInDrawerSnackBar(String value, - {Duration? duration, SnackBarAction? action}) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(value), - duration: duration ?? Duration(seconds: 3), - action: action)); + void showInDrawerSnackBar(String value, {Duration? duration, SnackBarAction? action}) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(value), duration: duration ?? Duration(seconds: 3), action: action)); } - Future register() => Navigator.pushNamed(cont!, "/registration"); + Future register() => Navigator.pushNamed(context, "/registration"); - Future search() => Navigator.pushNamed(cont!, "/search"); + Future search() => Navigator.pushNamed(context, "/search"); - Future login() => Navigator.pushNamed(cont!, "/login"); + Future login() => Navigator.pushNamed(context, "/login"); - Future addProduct() => Navigator.pushNamed(cont!, "/addProduct"); + Future addProduct() => Navigator.pushNamed(context, "/addProduct"); void handleDismissMain(DismissDirection dir, ShoppingItem s) async { - var list = User.currentList; - final String action = (dir == DismissDirection.endToStart) - ? NSSLStrings.of(context)!.archived() - : NSSLStrings.of(context)!.deleted(); - var index = list!.shoppingItems!.indexOf(s); - await list.deleteSingleItem(s); - setState(() {}); + var list = ref.watch(currentListProvider); + + if (list == null) return; + var listProvider = ref.read(shoppingListsProvider); + + final String action = + (dir == DismissDirection.endToStart) ? NSSLStrings.of(context).archived() : NSSLStrings.of(context).deleted(); + await listProvider.deleteSingleItem(list, s); + ShoppingListSync.deleteProduct(list.id, s.id, context); - updateOrderIndiciesAndSave(); - showInSnackBar( - NSSLStrings.of(context)!.youHaveActionItemMessage() + - "${s.name} $action", + + showInSnackBar(NSSLStrings.of(context).youHaveActionItemMessage() + "${s.name} $action", action: SnackBarAction( - label: NSSLStrings.of(context)!.undo(), + label: NSSLStrings.of(context).undo(), onPressed: () { - setState(() { - list.addSingleItem(s, index: index); - ShoppingListSync.changeProductAmount( - list.id, s.id, s.amount, context); - ScaffoldMessenger.of(context).removeCurrentSnackBar(); - updateOrderIndiciesAndSave(); - }); + listProvider.addSingleItem(list, s); + ShoppingListSync.changeProductAmount(list.id, s.id, s.amount, context); + ScaffoldMessenger.of(context).removeCurrentSnackBar(); }), duration: Duration(seconds: 10)); } @@ -332,17 +178,15 @@ class MainPageState extends State case "Login/Register": login(); break; - case "Options": + case "options": await Navigator.push( - cont!, + context, MaterialPageRoute( - builder: (BuildContext context) => CustomThemePage(), + builder: (BuildContext context) => SettingsPage(), fullscreenDialog: true, )) - .whenComplete(() => AdaptiveTheme.of(context).setTheme( - light: Themes.lightTheme.theme!, - dark: Themes.darkTheme.theme, - notify: true)); + .whenComplete(() => AdaptiveTheme.of(context) + .setTheme(light: Themes.lightTheme.theme!, dark: Themes.darkTheme.theme, notify: true)); break; case "PerformanceOverlay": setState(() => performanceOverlay = !performanceOverlay); @@ -353,30 +197,38 @@ class MainPageState extends State case "materialGrid": setState(() => materialGrid = !materialGrid); break; + case "logout": + _logout(); + break; case "ChangePassword": Navigator.push( - cont!, + context, MaterialPageRoute( builder: (BuildContext context) => ChangePasswordPage(), fullscreenDialog: true, )); break; case "reorderItems": - setState(() => isReorderingItems = !isReorderingItems); + var reordering = ref.watch(_isReorderingProvider.notifier); + reordering.state = !reordering.state; + break; + case "recipeImport": + addRecipeDialog(true); break; } } - void changeCurrentList(int index) => setState(() { - setState(() => User.currentList = User.shoppingLists[index]); - User.currentListIndex = User.shoppingLists[index].id; - User.save(); - Navigator.of(context).pop(); - }); + void changeCurrentList(int index) { + var currList = ref.watch(currentListIndexProvider.notifier); + currList.state = index; + var currUser = ref.read(userProvider); + currUser.save(index); + Navigator.of(context).pop(); + } - Future _getEAN() async { + Future _getEAN(ShoppingList currentList) async { ean = await Navigator.push( - cont!, + context, MaterialPageRoute( builder: (BuildContext context) => BarcodeScannerScreen(), fullscreenDialog: true, @@ -384,135 +236,273 @@ class MainPageState extends State if (ean == null || ean == "" || ean == "Permissions denied") return; - var list = User.currentList; - var firstRequest = await ProductSync.getProduct(ean, cont); + var firstRequest = await ProductSync.getProduct(ean, context); var z = jsonDecode((firstRequest).body); var k = ProductAddPage.fromJson(z); - if (k.success!) { + if (k.success) { RegExp reg = RegExp("([0-9]+[.,]?[0-9]*(\\s)?[gkmlGKML]{1,2})"); - String? name = - reg.hasMatch(k.name!) ? k.name : "${k.name} ${k.quantity}${k.unit}"; - var item = list?.shoppingItems - ?.firstWhere((x) => x!.name == name, orElse: () => null); - ShoppingItem afterAdd; + String? name = reg.hasMatch(k.name!) ? k.name : "${k.name} ${k.quantity}${k.unit}"; + var shoppingItems = ref.read(shoppingItemsPerListProvider.create(currentList.id)); + var item = shoppingItems.firstOrNull((x) => x.name == name); if (item != null) { - var answer = await ShoppingListSync.changeProductAmount( - list!.id, item.id, 1, cont); + var answer = await ShoppingListSync.changeProductAmount(currentList.id, item.id, 1, context); var p = ChangeListItemResult.fromJson((answer).body); - setState(() { - item.amount = p.amount; - item.changed = p.changed; - }); + var newItem = item.cloneWith(newAmount: p.amount, newChanged: p.changed); + item.exchange(newItem, ref); } else { - var p = AddListItemResult.fromJson( - (await ShoppingListSync.addProduct(list!.id, name, '-', 1, cont)) - .body); - afterAdd = ShoppingItem("${p.name}") - ..amount = 1 - ..id = p.productId; - setState(() { - list.shoppingItems!.add(afterAdd); - updateOrderIndiciesAndSave(); - }); + var p = + AddListItemResult.fromJson((await ShoppingListSync.addProduct(currentList.id, name, '-', 1, context)).body); + + var items = ref.watch(shoppingItemsProvider.notifier); + var newState = items.state.toList(); + int sortOrder = 0; + if (shoppingItems.length > 0) sortOrder = shoppingItems.last.sortOrder + 1; + newState.add(ShoppingItem(p.name, currentList.id, sortOrder, amount: 1, id: p.productId)); + items.state = newState; } - list.save(); + var provider = ref.read(shoppingListsProvider); + provider.save(currentList); + return; } Navigator.push( context, MaterialPageRoute( - builder: (BuildContext context) => AddProductToDatabase(ean), - fullscreenDialog: true)); + builder: (BuildContext context) => AddProductToDatabase(ean), fullscreenDialog: true)); + } + + void chooseListToAddDialog() { + var dialog = ChooseDialog.create( + context: context, + title: NSSLStrings.of(context).chooseListToAddTitle(), + titleOption1: NSSLStrings.of(context).chooseAddListDialog(), + onOption1: addListDialog, + titleOption2: NSSLStrings.of(context).chooseAddRecipeDialog(), + onOption2: () => addRecipeDialog(false)); + showDialog(builder: (BuildContext context) => dialog, context: context, barrierDismissible: false); } void addListDialog() { var sd = SimpleDialogSingleInput.create( - hintText: NSSLStrings.of(context)!.newNameOfListHint(), - labelText: NSSLStrings.of(context)!.listName(), + hintText: NSSLStrings.of(context).newNameOfListHint(), + labelText: NSSLStrings.of(context).listName(), onSubmitted: createNewList, - title: NSSLStrings.of(context)!.addNewListTitle(), - context: cont); + title: NSSLStrings.of(context).addNewListTitle(), + context: context); - showDialog( - builder: (BuildContext context) => sd, - context: cont!, - barrierDismissible: false); + showDialog(builder: (BuildContext context) => sd, context: context, barrierDismissible: false); + } + + void addRecipeDialog(bool import) { + var sd = SimpleDialogSingleInput.create( + hintText: NSSLStrings.of(context).recipeNameHint(), + labelText: NSSLStrings.of(context).recipeName(), + onSubmitted: import ? importNewRecipe : createNewRecipe, + title: import ? NSSLStrings.of(context).importNewRecipeTitle() : NSSLStrings.of(context).addNewRecipeTitle(), + context: context); + + showDialog(builder: (BuildContext context) => sd, context: context, barrierDismissible: false); } Future renameListDialog(int listId) { return showDialog( - context: cont!, + context: context, barrierDismissible: false, builder: (BuildContext context) => SimpleDialogSingleInput.create( - hintText: NSSLStrings.of(context)!.renameListHint(), - labelText: NSSLStrings.of(context)!.listName(), + hintText: NSSLStrings.of(context).renameListHint(), + labelText: NSSLStrings.of(context).listName(), onSubmitted: (s) => renameList(listId, s), - title: NSSLStrings.of(context)!.renameListTitle(), - context: cont)); + title: NSSLStrings.of(context).renameListTitle(), + context: context)); + } + + Future createNewRecipe(String idOrUrl) async { + var provider = ref.read(shoppingListsProvider); + var indexProvider = ref.watch(currentListIndexProvider.notifier); + var res = await ShoppingListSync.addRecipe(idOrUrl, context); + if (res.statusCode >= 400) { + showInSnackBar(NSSLStrings.of(context).recipeCreateError() + (res.reasonPhrase ?? "")); + return; + } + var newListRes = GetListResult.fromJson(res.body); + var newList = ShoppingList(newListRes.id!, newListRes.name!); + + var items = ref.watch(shoppingItemsProvider.notifier); + var newState = items.state.toList(); + if (newListRes.products != null) + newState.addAll(newListRes.products!.map((e) => ShoppingItem(e.name, newList.id, e.sortOrder, + amount: e.amount, id: e.id, created: e.created, changed: e.changed))); + + items.state = newState; + + provider.addList(newList); + + indexProvider.state = provider.shoppingLists.indexOf(newList); + provider.save(newList); + } + + Future importNewRecipe(String idOrUrl) async { + var list = ref.read(currentListProvider); + if (list == null) return; + + importNewRecipeIntoList(list, idOrUrl); + } + + Future importNewRecipeIntoList(ShoppingList? list, String idOrUrl) async { + if (list == null) return; + + var provider = ref.read(shoppingListsProvider); + var res = await ShoppingListSync.importRecipe(idOrUrl, list.id, context); + + if (res.statusCode >= 400) { + showInSnackBar(NSSLStrings.of(context).recipeCreateError() + (res.reasonPhrase ?? "")); + return; + } + + provider.refresh(list); } Future createNewList(String listName) async { - var res = await ShoppingListSync.addList(listName, cont); + var provider = ref.read(shoppingListsProvider); + var currentListProvider = ref.watch(currentListIndexProvider.notifier); + var res = await ShoppingListSync.addList(listName, context); var newListRes = AddListResult.fromJson(res.body); var newList = ShoppingList(newListRes.id, newListRes.name); - setState(() => User.shoppingLists.add(newList)); - changeCurrentList(User.shoppingLists.indexOf(newList)); - firebaseMessaging - ?.subscribeToTopic(newList.id.toString() + "shoppingListTopic"); - newList.save(); + provider.addList(newList); + + currentListProvider.state = provider.shoppingLists.indexOf(newList); + provider.save(newList); + } + + Future initPlatformState() async { + final handler = ShareHandler.instance; + try { + media = await handler.getInitialSharedMedia(); + + handler.sharedMediaStream.listen((SharedMedia media) { + if (!mounted) return; + handleMediaFromShare(media); + }); + if (!mounted) return; + + handleMediaFromShare(media); + } on PlatformException {} + } + + void handleMediaFromShare(SharedMedia? media) { + if (media == null || media.content == null) return; + + var parsed = Uri.tryParse(media.content!); + + if (parsed == null) return; + + importRecipeFromSharedUri(parsed); + } + + Future importRecipeFromSharedUri(Uri uri) async { + var url = uri.toString(); + var result = await ShoppingListSync.checkRecipeInput(url, context); + + if (result.body != "true") { + showInSnackBar(NSSLStrings.of(context).recipeCreateError()); + return; + } + + var currList = ref.watch(currentListProvider); + var shoppingListsController = ref.watch(shoppingListsProvider); + var list = shoppingListsController.shoppingLists.copy(); + list.sort(((a, b) { + if (a.id == currList?.id) return -1; + if (b.id == currList?.id) return 1; + return 0; + })); + + var dialog = AlertDialog( + title: Text(NSSLStrings.of(context).recipeFromShareTitle()), + content: Container( + width: 80, + child: FractionallySizedBox( + heightFactor: 0.8, + child: ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: list.length, + itemBuilder: (BuildContext context, int index) { + var currentSelected = list[index].id == currList?.id; + return Container( + height: 50, + child: ListTile( + onTap: () { + importNewRecipeIntoList(list[index], url); + Navigator.pop(context, ""); + }, + title: Text( + list[index].name, + style: TextStyle( + fontWeight: currentSelected ? FontWeight.bold : FontWeight.normal, + fontStyle: currentSelected ? FontStyle.italic : FontStyle.normal, + ), + ), + ), + ); + }), + ), + ), + actions: [ + TextButton(child: Text(NSSLStrings.of(context).cancelButton()), onPressed: () => Navigator.pop(context, "")), + TextButton( + child: Text(NSSLStrings.of(context).recipeFromShareNew()), + onPressed: () { + createNewRecipe(url); + Navigator.pop(context, ""); + }), + ], + ); + showDialog(builder: (BuildContext context) => dialog, context: context, barrierDismissible: false); } Widget _buildDrawer(BuildContext context) { + var user = ref.watch(userProvider); var isDarkTheme = AdaptiveTheme.of(context).mode == AdaptiveThemeMode.dark; var userheader = UserAccountsDrawerHeader( - accountName: - Text(User.username ?? NSSLStrings.of(context)!.notLoggedInYet()), - accountEmail: - Text(User.eMail ?? NSSLStrings.of(context)!.notLoggedInYet()), + accountName: Text(user.username == "" ? NSSLStrings.of(context).notLoggedInYet() : user.username), + accountEmail: Text(user.eMail == "" ? NSSLStrings.of(context).notLoggedInYet() : user.username), currentAccountPicture: CircleAvatar( child: Text( - User.username?.substring(0, 2).toUpperCase() ?? "", + user.username.substring(0, 2).toUpperCase(), style: TextStyle(color: isDarkTheme ? Colors.black : Colors.white), ), backgroundColor: isDarkTheme - ? Themes - .darkTheme.theme!.floatingActionButtonTheme.backgroundColor - : Themes - .lightTheme.theme!.floatingActionButtonTheme.backgroundColor), + ? Themes.darkTheme.theme!.floatingActionButtonTheme.backgroundColor + : Themes.lightTheme.theme!.floatingActionButtonTheme.backgroundColor), onDetailsPressed: () { _showDrawerContents = !_showDrawerContents; _showDrawerContents ? _controller!.reverse() : _controller!.forward(); }, ); - - var list = User.shoppingLists.isNotEmpty - ? User.shoppingLists + var shoppingListsController = ref.watch(shoppingListsProvider); + var list = shoppingListsController.shoppingLists.isNotEmpty + ? shoppingListsController.shoppingLists .map((x) => ListTile( - title: Text(x.name ?? ""), - onTap: () => changeCurrentList(User.shoppingLists.indexOf( - User.shoppingLists.firstWhere((y) => y.id == x.id))), + title: Text(x.name), + onTap: () => changeCurrentList(shoppingListsController.shoppingLists + .indexOf(shoppingListsController.shoppingLists.firstWhere((y) => y.id == x.id))), trailing: PopupMenuButton( padding: EdgeInsets.zero, - onSelected: (v) async => - await drawerListItemMenuClicked(v), - itemBuilder: (BuildContext context) => - >[ + onSelected: (v) async => await drawerListItemMenuClicked(v), + itemBuilder: (BuildContext context) => >[ PopupMenuItem( - value: - x.id.toString() + "\u{1E}" + "Contributors", + value: x.id.toString() + "\u{1E}" + "Contributors", child: ListTile( leading: const Icon(Icons.person_add), - title: Text( - NSSLStrings.of(context)!.contributors()), + title: Text(NSSLStrings.of(context).contributors()), ), ), PopupMenuItem( value: x.id.toString() + "\u{1E}" + "BoughtList", child: ListTile( leading: const Icon(Icons.history), - title: Text( - NSSLStrings.of(context)!.boughtProducts()), + title: Text(NSSLStrings.of(context).boughtProducts()), // NSSLStrings.of(context)!.contributors()), ), ), @@ -527,35 +517,24 @@ class MainPageState extends State value: x.id.toString() + "\u{1E}" + 'Rename', child: ListTile( leading: const Icon(Icons.mode_edit), - title: Text( - NSSLStrings.of(context)!.rename()))), + title: Text(NSSLStrings.of(context).rename()))), PopupMenuItem( value: x.id.toString() + "\u{1E}" + 'Auto-Sync', child: ListTile( - leading: Icon(x.messagingEnabled - ? Icons.check_box - : Icons.check_box_outline_blank), - title: Text( - NSSLStrings.of(context)!.autoSync()))), + leading: Icon(x.messagingEnabled ? Icons.check_box : Icons.check_box_outline_blank), + title: Text(NSSLStrings.of(context).autoSync()))), const PopupMenuDivider(), PopupMenuItem( value: x.id.toString() + "\u{1E}" + 'Remove', child: ListTile( - leading: const Icon(Icons.delete), - title: Text( - NSSLStrings.of(context)!.remove()))) + leading: const Icon(Icons.delete), title: Text(NSSLStrings.of(context).remove()))) ]), )) .toList() : [ - ListTile( - title: Text(NSSLStrings.of(context)!.noListsInDrawerMessage())), + ListTile(title: Text(NSSLStrings.of(context).noListsInDrawerMessage())), ]; - var emptyListTiles = []; - for (int i = 0; i < list.length - 2; i++) - emptyListTiles.add(ListTile( - title: const Text(("")), - )); + var d = Scaffold( body: RefreshIndicator( child: ListView( @@ -578,28 +557,23 @@ class MainPageState extends State children: [ ListTile( leading: const Icon(Icons.sync), - title: Text(NSSLStrings.of(context)!.refresh()), + title: Text(NSSLStrings.of(context).refresh()), onTap: () => _handleDrawerRefresh(), ), ListTile( leading: const Icon(Icons.restore_page_outlined), title: Text( - NSSLStrings.of(context)!.changePasswordPD(), + NSSLStrings.of(context).changePasswordPD(), ), onTap: () => selectedOption("ChangePassword"), ), ListTile( leading: const Icon(Icons.exit_to_app), - title: Text(NSSLStrings.of(context)!.logout()), + title: Text(NSSLStrings.of(context).logout()), onTap: () async { - await User.delete(); - User.username = null; - User.eMail = null; - User.token = null; - runApp(NSSL()); + await _logout(); }, - ), - Column(children: emptyListTiles) + ) ], ), ), @@ -612,14 +586,23 @@ class MainPageState extends State onRefresh: _handleDrawerRefresh, displacement: 1.0), persistentFooterButtons: [ - TextButton( - child: Text(NSSLStrings.of(context)!.addListPB()), - onPressed: addListDialog) + TextButton(child: Text(NSSLStrings.of(context).addListPB()), onPressed: chooseListToAddDialog) ]); return Drawer(child: d); } + Future _logout() async { + var user = ref.read(userProvider); + + await user.delete(); + var userState = ref.watch(userStateProvider.notifier); + userState.state = User.empty; + + var restartState = ref.watch(appRestartProvider.notifier); + restartState.state = restartState.state + 1; + } + Future drawerListItemMenuClicked(String value) async { var splitted = value.split('\u{1E}'); int id = int.parse(splitted[0]); @@ -632,101 +615,85 @@ class MainPageState extends State break; case "BoughtList": await Navigator.push( - cont!, + context, MaterialPageRoute( builder: (BuildContext context) => BoughtItemsPage(id), fullscreenDialog: true, )); - setState(() {}); break; case "Rename": renameListDialog(id); break; case "Remove": - var deleteList = User.shoppingLists.firstWhere((x) => x.id == id); + var deleteList = ref.read(shoppingListByIdProvider.create(id)); + if (deleteList == null) return; + var cont = context; showDialog( - context: cont!, + context: context, barrierDismissible: false, builder: (BuildContext context) => SimpleDialogAcceptDeny.create( - title: NSSLStrings.of(cont)?.deleteListTitle() ?? - "" + deleteList.name!, - text: NSSLStrings.of(cont)?.deleteListText() ?? "", + title: NSSLStrings.of(context).deleteListTitle() + deleteList.name, + text: NSSLStrings.of(context).deleteListText(), onSubmitted: (s) async { - var res = Result.fromJson( - (await ShoppingListSync.deleteList(id, cont)).body); - if (!(res.success ?? false)) - showInDrawerSnackBar(res.error!); + var res = Result.fromJson((await ShoppingListSync.deleteList(id, context)).body); + if (!(res.success)) + showInDrawerSnackBar(res.error); else { - showInDrawerSnackBar(deleteList.name! + - " " + - NSSLStrings.of(cont)!.removed()); - if (User.currentList!.id! == id) { - changeCurrentList(User.shoppingLists.indexOf( - User.shoppingLists.firstWhere((l) => l.id != id))); + var currentList = ref.read(currentListProvider); + var shoppingListController = ref.read(shoppingListsProvider); + if (currentList == null || currentList.id == id) { + var other = shoppingListController.shoppingLists.firstOrNull((l) => l.id != id); + if (other != null) changeCurrentList(shoppingListController.shoppingLists.indexOf(other)); } - setState(() => - User.shoppingLists.removeWhere((x) => x.id == id)); + shoppingListController.removeList(deleteList.id); + showInDrawerSnackBar(deleteList.name + " " + NSSLStrings.of(cont).removed()); } }, - context: cont)); + context: context)); break; case "Auto-Sync": - var list = User.shoppingLists.firstWhere((x) => x.id == id); - list.messagingEnabled - ? list.unsubscribeFromFirebaseMessaging() - : list.subscribeForFirebaseMessaging(); - list.messagingEnabled = !list.messagingEnabled; - list.save(); + var shoppingListController = ref.read(shoppingListsProvider); + shoppingListController.toggleFirebaseMessaging(id); + break; case "ExportAsPdf": ExportManager.exportAsPDF( - User.shoppingLists.firstWhere((x) => x.id == id), context); + ref.read(shoppingListByIdProvider.create(id))!, ref.read(shoppingItemsPerListProvider.create(id)), context); break; } } Future _handleDrawerRefresh() async { - await ShoppingList.reloadAllLists(context); - setState(() => {}); + await ref.read(shoppingListsProvider).reloadAllLists(); } - Future _handleMainListRefresh() => - _handleListRefresh(User.currentList!.id); + Future _handleMainListRefresh(int id) => _handleListRefresh(id); - Future _handleListRefresh(int? listId) async { - await User.shoppingLists.firstWhere((s) => s.id == listId).refresh(cont); - setState(() {}); + Future _handleListRefresh(int listId) async { + await ref.read(shoppingListsProvider).refresh(ref.read(shoppingListByIdProvider.create(listId))!); } Future shoppingItemChange(ShoppingItem s, int change) async { var res = ChangeListItemResult.fromJson( - (await ShoppingListSync.changeProductAmount( - User.currentList!.id!, s.id, change, cont)) - .body); - setState(() { - s.id = res.id; - s.amount = res.amount; - s.name = res.name; - s.changed = res.changed; - }); + (await ShoppingListSync.changeProductAmount(s.listId, s.id, change, context)).body); + if (!res.success) return; + s.exchange(s.cloneWith(newAmount: res.amount, newChanged: res.changed), ref); } var amountPopList = >[]; List> buildChangeMenuItems(BuildContext context) { if (amountPopList.length == 0) for (int i = 1; i <= 99; i++) - amountPopList.add(PopupMenuItem( - value: i.toString(), child: Text(i.toString()))); + amountPopList.add(PopupMenuItem(value: i.toString(), child: Text(i.toString()))); return amountPopList; } Future crossOutMainListItem(ShoppingItem x) async { - setState(() => x.crossedOut = !x.crossedOut); - await User.currentList?.save(); - - if (!isReorderingItems) { - sortAndOrderCrossedOut(); - } + var newItem = x.cloneWith(newCrossedOut: !x.crossedOut); + x.exchange(newItem, ref); + var provider = ref.read(shoppingListsProvider); + var currentList = ref.read(currentListProvider); + if (currentList != null) provider.save(currentList); } void _addWithoutSearchDialog(BuildContext extContext) { @@ -735,122 +702,317 @@ class MainPageState extends State barrierDismissible: false, builder: (BuildContext context) => SimpleDialogSingleInput.create( context: context, - title: NSSLStrings.of(context)!.addProduct(), - hintText: NSSLStrings.of(context)!.addProductWithoutSearch(), - labelText: NSSLStrings.of(context)!.productName(), + title: NSSLStrings.of(context).addProduct(), + hintText: NSSLStrings.of(context).addProductWithoutSearch(), + labelText: NSSLStrings.of(context).productName(), onSubmitted: _addWithoutSearch)); } Future renameList(int id, String text) async { - var put = await ShoppingListSync.changeLName(id, text, cont); + var put = await ShoppingListSync.changeLName(id, text, context); showInDrawerSnackBar("${put.statusCode}" + put.reasonPhrase!); var res = Result.fromJson((put.body)); - if (!res.success!) showInDrawerSnackBar(res.error!); + if (!res.success) showInDrawerSnackBar(res.error); + var listsProvider = ref.read(shoppingListsProvider); + listsProvider.rename(id, text); } Future _addWithoutSearch(String value) async { - var list = User.currentList; - var same = list!.shoppingItems! - .where((x) => x!.name!.toLowerCase() == value.toLowerCase()); - if (same.length > 0) { - var res = await ShoppingListSync.changeProductAmount( - list.id, same.first!.id!, 1, cont); + var list = ref.read(currentListProvider); + if (list == null) return; + var shoppingItems = ref.read(currentShoppingItemsProvider); + + var same = shoppingItems.firstOrNull((x) => x.name.toLowerCase() == value.toLowerCase()); + if (same != null) { + var res = await ShoppingListSync.changeProductAmount(list.id, same.id, 1, context); if (res.statusCode != 200) showInSnackBar(res.reasonPhrase!); var product = ChangeListItemResult.fromJson(res.body); - if (!product.success!) showInSnackBar(product.error!); - setState(() { - same.first!.amount = product.amount; - same.first!.changed = product.changed; - }); - same.first; + if (!product.success) showInSnackBar(product.error); + same.exchange( + same.cloneWith( + newAmount: product.amount, + newChanged: product.changed, + newId: product.id, + newName: product.name, + newListId: product.listId), + ref); } else { - var res = - await ShoppingListSync.addProduct(list.id, value, null, 1, cont); + var res = await ShoppingListSync.addProduct(list.id, value, null, 1, context); if (res.statusCode != 200) showInSnackBar(res.reasonPhrase!); var product = AddListItemResult.fromJson(res.body); - if (!product.success!) showInSnackBar(product.error!); - setState(() => list.shoppingItems!.add(ShoppingItem(product.name) - ..id = product.productId - ..amount = 1 - ..crossedOut = false)); - updateOrderIndiciesAndSave(); + if (!product.success) showInSnackBar(product.error); + var sips = ref.watch(shoppingItemsProvider.notifier); + var newState = sips.state.toList(); + var order = 0; + if (shoppingItems.length > 0) order = shoppingItems.last.sortOrder + 1; + + newState.add(ShoppingItem(product.name, list.id, order, id: product.productId, amount: 1, crossedOut: false)); + sips.state = newState; } + var listProv = ref.watch(shoppingListsProvider); + listProv.save(list); } Future _deleteCrossedOutItems() async { - var list = User.currentList; - var sublist = list!.shoppingItems!.where((s) => s!.crossedOut).toList(); - var res = await ShoppingListSync.deleteProducts( - list.id, sublist.map((s) => s!.id).toList(), cont); - if (!Result.fromJson(res.body).success!) return; - setState(() { - for (var item in sublist) list.shoppingItems?.remove(item); - }); - updateOrderIndiciesAndSave(); - showInSnackBar(NSSLStrings.of(context)!.messageDeleteAllCrossedOut(), + var list = ref.read(currentListProvider); + if (list == null) return; + var shoppingList = ref.read(currentShoppingItemsProvider); + + var sublist = shoppingList.where((s) => s.crossedOut).toList(); + var res = await ShoppingListSync.deleteProducts(list.id, sublist.map((s) => s.id).toList(), context); + if (!Result.fromJson(res.body).success) return; + var shoppingItemsState = ref.watch(shoppingItemsProvider.notifier); + var newState = shoppingItemsState.state.toList(); + + newState.removeElements(sublist); + + shoppingItemsState.state = newState; + var listProv = ref.watch(shoppingListsProvider); + listProv.save(list); + showInSnackBar(NSSLStrings.of(context).messageDeleteAllCrossedOut(), duration: Duration(seconds: 10), action: SnackBarAction( - label: NSSLStrings.of(context)!.undo(), + label: NSSLStrings.of(context).undo(), onPressed: () async { var res = await ShoppingListSync.changeProducts( - list.id, - sublist.map((s) => s!.id).toList(), - sublist.map((s) => s!.amount).toList(), - cont); + list.id, sublist.map((s) => s.id).toList(), sublist.map((s) => s.amount).toList(), context); var hashResult = HashResult.fromJson(res.body); int ownHash = 0; - for (var item in sublist) ownHash += item!.id! + item.amount; + for (var item in sublist) ownHash += item.id + item.amount; if (ownHash == hashResult.hash) { - setState(() => list.shoppingItems?.addAll(sublist)); - updateOrderIndiciesAndSave(); - list.save(); + var shoppingItemsState = ref.watch(shoppingItemsProvider.notifier); + var newState = shoppingItemsState.state.toList(); + newState.addAll(sublist); + shoppingItemsState.state = newState; + var listProv = ref.watch(shoppingListsProvider); + listProv.save(list); } else _handleListRefresh(list.id); })); } - renameListItem(ShoppingItem? x) { + renameListItem(ShoppingItem shoppingItem) { showDialog( - context: cont!, + context: context, barrierDismissible: false, builder: (BuildContext context) => SimpleDialogSingleInput.create( - context: cont, - title: NSSLStrings.of(context)!.renameListItem(), - hintText: NSSLStrings.of(context)!.renameListHint(), - labelText: NSSLStrings.of(context)!.renameListItemLabel(), - defaultText: x?.name ?? "", + context: context, + title: NSSLStrings.of(context).renameListItem(), + hintText: NSSLStrings.of(context).renameListHint(), + labelText: NSSLStrings.of(context).renameListItemLabel(), + defaultText: shoppingItem.name, maxLines: 2, onSubmitted: (s) async { + var currentList = ref.read(currentListProvider); + if (currentList == null) return; + var res = ChangeListItemResult.fromJson( - (await ShoppingListSync.changeProductName( - User.currentList!.id, x!.id, s, cont)) - .body); - setState(() { - x.id = res.id; - x.amount = res.amount; - x.name = res.name; - x.changed = res.changed; - }); + (await ShoppingListSync.changeProductName(currentList.id, shoppingItem.id, s, context)).body); + + var items = ref.watch(shoppingItemsProvider.notifier); + var newState = items.state.toList(); + var item = newState.firstWhere((x) => x.id == shoppingItem.id); + newState.remove(item); + newState.add( + item.cloneWith(newName: res.name), + ); + items.state = newState; })); } - Widget acceptReordingFAB() => FloatingActionButton( - child: Icon( - Icons.check, + Widget? acceptReordingFAB() { + var isReordering = ref.watch(_isReorderingProvider); + if (!isReordering) return null; + return FloatingActionButton( + child: Icon( + Icons.check, + ), + onPressed: () async { + var reorderingState = ref.watch(_isReorderingProvider.notifier); + reorderingState.state = false; + var currentList = ref.read(currentListProvider); + var ids = ref.read(currentShoppingItemsProvider); + await ShoppingListSync.reorderProducts(currentList!.id, ids.map((e) => e.id).toList(), context); + }, + ); + } + + List _getMainDropdownActions(BuildContext context) { + var isReorderingItems = ref.watch(_isReorderingProvider); + if (isReorderingItems) return []; + + return [ + // IconButton( + // onPressed: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (BuildContext context) => SettingsPage(), + // fullscreenDialog: true, + // )); + // }, + // icon: Icon(Icons.settings)), + PopupMenuButton( + onSelected: selectedOption, + itemBuilder: (BuildContext context) => >[ + PopupMenuItem( + value: 'deleteCrossedOut', + child: Text( + NSSLStrings.of(context).deleteCrossedOutPB(), + ), + ), + PopupMenuItem( + value: 'reorderItems', + child: Text( + NSSLStrings.of(context).reorderItems(), + ), + ), + PopupMenuItem( + value: 'recipeImport', + child: Text(NSSLStrings.of(context).importNewRecipe()), + ), + PopupMenuItem( + value: 'options', + child: Text( + NSSLStrings.of(context).options(), + ), + ), + PopupMenuItem( + value: 'logout', + child: Text( + NSSLStrings.of(context).logout(), + ), + ), + ]) + ]; + } +} + +final _isReorderingProvider = StateProvider((final _) { + return false; +}); + +class ShoppingListWidget extends ConsumerWidget { + final MainPageState mainPageState; + const ShoppingListWidget(this.mainPageState, {Key? key}) : super(key: key); + + void updateOrderIndiciesAndSave(ShoppingList currentList, List shoppingItems, WidgetRef ref) async { + // var newItems = []; + // var curSortOrder = 0; + // for (var i = 0; i < shoppingItems.length; i++) { + // var curItem = shoppingItems[i]; + // if (curItem.sortWithOffset > curSortOrder) + // newItems.add(curItem); + // else + // newItems.add(curItem.cloneWith(newSortOrder: ++curSortOrder)); + // } + // var itemsState = ref.watch(shoppingItemsProvider.notifier); + // var newState = itemsState.state.toList(); + // newState.removeElements(shoppingItems); + // newState.addAll(newItems); + // itemsState.state = newState; + + // var listProvider = ref.read(shoppingListsProvider); + // listProvider.save(currentList); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + var currentList = ref.watch(currentListProvider); + if (currentList == null) return const Text(""); + var shoppingItems = ref.watch(currentShoppingItemsProvider); + if (shoppingItems.isEmpty) return const Text(""); + + if (shoppingItems.any((item) => item.sortOrder == -1)) updateOrderIndiciesAndSave(currentList, shoppingItems, ref); + + shoppingItems.sort((a, b) => a.sortWithOffset.compareTo(b.sortWithOffset)); + var lv; + if (shoppingItems.length > 0) { + final isReorderingItems = ref.watch(_isReorderingProvider); + var mainList = shoppingItems.map((x) { + return getListTileForShoppingItem(x, isReorderingItems, context); + }).toList(growable: true); + + if (isReorderingItems) { + lv = ReorderableListView( + onReorder: mainPageState._onReorderItems, scrollDirection: Axis.vertical, children: mainList); + } else { + lv = CustomScrollView( + controller: mainPageState._mainController, + slivers: [ + SliverFixedExtentList( + delegate: SliverChildBuilderDelegate((BuildContext context, int index) { + return Container( + alignment: FractionalOffset.center, + child: mainList[index], + ); + }, childCount: mainList.length), + itemExtent: 50.0) + ], + physics: AlwaysScrollableScrollPhysics(), + ); + } + } else + lv = ListView( + physics: const AlwaysScrollableScrollPhysics(), + children: [ListTile(title: const Text(""))], + ); + return RefreshIndicator( + child: lv, + onRefresh: () => mainPageState._handleMainListRefresh(currentList.id), + ); + } + + Widget getListTileForShoppingItem(ShoppingItem? x, bool isReorderingItems, BuildContext context) { + if (x == null || x.name == "") return Text("Null"); + // return Text(x.name!); + + var lt = ListTile( + key: ValueKey(x), + title: Wrap( + children: [ + Text( + x.name, + maxLines: 2, + softWrap: true, + style: TextStyle(decoration: x.crossedOut ? TextDecoration.lineThrough : TextDecoration.none), + ), + ], + ), + leading: PopupMenuButton( + child: FittedBox( + child: Row(children: [ + Text(x.amount.toString() + "x"), + const Icon(Icons.expand_more, size: 16.0), + SizedBox(height: 38.0), //for larger clickable size (2 Lines) + ]), + ), + initialValue: x.amount.toString(), + onSelected: (y) => mainPageState.shoppingItemChange(x, int.parse(y) - x.amount), + itemBuilder: mainPageState.buildChangeMenuItems, + ), + trailing: isReorderingItems ? Icon(Icons.reorder) : null, + onTap: isReorderingItems ? null : (() => mainPageState.crossOutMainListItem(x)), + onLongPress: isReorderingItems ? null : (() => mainPageState.renameListItem(x)), + ); + + if (isReorderingItems) { + return lt; + } else { + return Dismissible( + key: ValueKey(x), + child: lt, + onDismissed: (DismissDirection d) => mainPageState.handleDismissMain(d, x), + direction: DismissDirection.startToEnd, + background: Container( + decoration: BoxDecoration(color: Theme.of(context).primaryColor), + child: ListTile( + leading: Icon(Icons.delete, + // color: Theme.of(context).accentIconTheme.color, + size: 36.0), + ), ), - onPressed: () async { - var ids = []; - ids.addAll(User.currentList!.shoppingItems!.map((e) => e!.clone())); - ids.forEach((element) { - if (element.sortOrder! > 0xffffffff) - element.sortOrder = element.sortOrder! - 0xffffffff; - }); - ids.sort((x, y) => x.sortOrder!.compareTo(y.sortOrder!)); - await ShoppingListSync.reorderProducts( - User.currentList!.id, ids.map((e) => e.id).toList(), context); - setState(() { - isReorderingItems = false; - }); - }, ); + } + } } diff --git a/lib/pages/pages.dart b/lib/pages/pages.dart index d7a0a13..d80328e 100644 --- a/lib/pages/pages.dart +++ b/lib/pages/pages.dart @@ -8,4 +8,6 @@ export 'shopping_item_search.dart'; export 'custom_theme_page.dart'; export 'change_password.dart'; export 'bought_items.dart'; -export 'main_page.dart'; \ No newline at end of file +export 'main_page.dart'; +export 'settings.dart'; +export 'about.dart'; \ No newline at end of file diff --git a/lib/pages/product_add_to_database.dart b/lib/pages/product_add_to_database.dart index 0becf27..c0aa26c 100644 --- a/lib/pages/product_add_to_database.dart +++ b/lib/pages/product_add_to_database.dart @@ -4,9 +4,9 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:nssl/localization/nssl_strings.dart'; import 'package:nssl/models/model_export.dart'; -import 'package:nssl/models/user.dart'; import 'package:nssl/server_communication/return_classes.dart'; import 'package:nssl/server_communication/s_c.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; enum DismissDialogAction { cancel, @@ -14,7 +14,7 @@ enum DismissDialogAction { save, } -class AddProductToDatabase extends StatefulWidget { +class AddProductToDatabase extends ConsumerStatefulWidget { AddProductToDatabase(this.gtin); final String? gtin; @@ -22,7 +22,7 @@ class AddProductToDatabase extends StatefulWidget { AddProductToDatabaseState createState() => AddProductToDatabaseState(gtin); } -class AddProductToDatabaseState extends State { +class AddProductToDatabaseState extends ConsumerState { AddProductToDatabaseState(this.gtin); final GlobalKey _scaffoldKey = GlobalKey(); final GlobalKey _formKey = GlobalKey(); @@ -44,22 +44,20 @@ class AddProductToDatabaseState extends State { if (!_saveNeeded) return true; final ThemeData theme = Theme.of(context); - final TextStyle dialogTextStyle = theme.textTheme.subtitle1! - .copyWith(color: theme.textTheme.caption!.color); + final TextStyle dialogTextStyle = theme.textTheme.subtitle1!.copyWith(color: theme.textTheme.caption!.color); return await (showDialog( context: context, builder: (BuildContext context) => AlertDialog( - content: Text(NSSLStrings.of(context)!.discardNewProduct(), - style: dialogTextStyle), + content: Text(NSSLStrings.of(context).discardNewProduct(), style: dialogTextStyle), actions: [ TextButton( - child: Text(NSSLStrings.of(context)!.cancelButton()), + child: Text(NSSLStrings.of(context).cancelButton()), onPressed: () { Navigator.of(context).pop(false); }), TextButton( - child: Text(NSSLStrings.of(context)!.discardButton()), + child: Text(NSSLStrings.of(context).discardButton()), onPressed: () { Navigator.of(context).pop(true); }) @@ -73,13 +71,12 @@ class AddProductToDatabaseState extends State { Future _handleSubmitted() async { if (_isSendToServer) { - showInSnackBar(NSSLStrings.of(context)!.bePatient()); + showInSnackBar(NSSLStrings.of(context).bePatient()); return false; } final FormState form = _formKey.currentState!; if (!form.validate()) { - showInSnackBar( - NSSLStrings.of(context)!.fixErrorsBeforeSubmittingPrompt()); + showInSnackBar(NSSLStrings.of(context).fixErrorsBeforeSubmittingPrompt()); validateMode = AutovalidateMode.onUserInteraction; return false; } else { @@ -96,38 +93,38 @@ class AddProductToDatabaseState extends State { unit = weight!.substring(match!.start, match.end); } _isSendToServer = true; - var first = (await ProductSync.addNewProduct( - "$productName $brandName", gtin, realWeight, unit, context)); + var first = (await ProductSync.addNewProduct("$productName $brandName", gtin, realWeight, unit, context)); if (first.statusCode != 200) { showInSnackBar(first.reasonPhrase!); _isSendToServer = false; return false; } var res = ProductResult.fromJson(first.body); - if (!res.success!) - showInSnackBar(res.error!); + if (!res.success) + showInSnackBar(res.error); else { - showInSnackBar(NSSLStrings.of(context)!.successful()); + showInSnackBar(NSSLStrings.of(context).successful()); if (putInList) { - var list = User.currentList!; + var list = ref.read(currentListProvider)!; var pres = AddListItemResult.fromJson( - (await ShoppingListSync.addProduct(list.id, - "$productName $brandName $weight", gtin, 1, context)) - .body); - if (!pres.success!) - showInSnackBar(pres.error!); + (await ShoppingListSync.addProduct(list.id, "$productName $brandName $weight", gtin, 1, context)).body); + if (!pres.success) + showInSnackBar(pres.error); else { - setState(() { - list.shoppingItems!.add(ShoppingItem(pres.name) - ..amount = 1 - ..id = pres.productId); - }); + var listController = ref.read(shoppingListsProvider); + var shoppingItems = ref.read(currentShoppingItemsProvider); + + int sortOrder = 0; + if (shoppingItems.length > 0) sortOrder = shoppingItems.last.sortOrder + 1; + + listController.addSingleItem( + list, ShoppingItem(pres.name, list.id, sortOrder, amount: 1, id: pres.productId)); } + _isSendToServer = false; + Navigator.of(context).pop(); } _isSendToServer = false; - Navigator.of(context).pop(); } - _isSendToServer = false; return true; } } @@ -138,26 +135,22 @@ class AddProductToDatabaseState extends State { return Scaffold( key: _scaffoldKey, - appBar: AppBar( - title: Text(NSSLStrings.of(context)!.newProductTitle()), - actions: [ - TextButton( - child: Text(NSSLStrings.of(context)!.saveButton(), - style: theme.textTheme.bodyText2! - .copyWith(color: Colors.white)), - onPressed: () => _handleSubmitted()) - ]), + appBar: AppBar(title: Text(NSSLStrings.of(context).newProductTitle()), actions: [ + TextButton( + child: Text(NSSLStrings.of(context).saveButton(), + style: theme.textTheme.bodyText2!.copyWith(color: Colors.white)), + onPressed: () => _handleSubmitted()) + ]), body: Form( key: _formKey, onWillPop: _onWillPop, autovalidateMode: validateMode, - child: - ListView(padding: const EdgeInsets.all(16.0), children: [ + child: ListView(padding: const EdgeInsets.all(16.0), children: [ Container( child: TextFormField( decoration: InputDecoration( - labelText: NSSLStrings.of(context)!.newProductName(), - hintText: NSSLStrings.of(context)!.newProductNameHint(), + labelText: NSSLStrings.of(context).newProductName(), + hintText: NSSLStrings.of(context).newProductNameHint(), ), autofocus: true, controller: tecProductName, @@ -166,10 +159,8 @@ class AddProductToDatabaseState extends State { Container( child: TextFormField( decoration: InputDecoration( - labelText: - NSSLStrings.of(context)!.newProductBrandName(), - hintText: - NSSLStrings.of(context)!.newProductBrandNameHint()), + labelText: NSSLStrings.of(context).newProductBrandName(), + hintText: NSSLStrings.of(context).newProductBrandNameHint()), autofocus: false, controller: tecBrandName, onSaved: (s) => brandName = s, @@ -177,32 +168,27 @@ class AddProductToDatabaseState extends State { Container( child: TextFormField( decoration: InputDecoration( - labelText: NSSLStrings.of(context)!.newProductWeight(), - hintText: - NSSLStrings.of(context)!.newProductWeightHint()), + labelText: NSSLStrings.of(context).newProductWeight(), + hintText: NSSLStrings.of(context).newProductWeightHint()), autofocus: false, onSaved: (s) => weight = s, controller: tecPackagingSize)), Container( padding: const EdgeInsets.symmetric(vertical: 8.0), - decoration: BoxDecoration( - border: - Border(bottom: BorderSide(color: theme.dividerColor))), + decoration: BoxDecoration(border: Border(bottom: BorderSide(color: theme.dividerColor))), alignment: FractionalOffset.bottomLeft, - child: Text(NSSLStrings.of(context)!.codeText() + gtin!)), + child: Text(NSSLStrings.of(context).codeText() + gtin!)), Container( padding: const EdgeInsets.symmetric(vertical: 8.0), alignment: FractionalOffset.bottomLeft, child: Row(children: [ - Text(NSSLStrings.of(context)!.newProductAddToList()), - Checkbox( - value: putInList, - onChanged: (b) => setState(() => putInList = !putInList)) + Text(NSSLStrings.of(context).newProductAddToList()), + Checkbox(value: putInList, onChanged: (b) => setState(() => putInList = !putInList)) ])), Container( padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Text(NSSLStrings.of(context)!.newProductStarExplanation(), - style: Theme.of(context).textTheme.caption), + child: + Text(NSSLStrings.of(context).newProductStarExplanation(), style: Theme.of(context).textTheme.caption), ), ])), ); @@ -210,9 +196,8 @@ class AddProductToDatabaseState extends State { String? _validateName(String? value) { _saveNeeded = true; - if (value!.isEmpty) return NSSLStrings.of(context)!.fieldRequiredError(); - if (value.length < 3) - return NSSLStrings.of(context)!.newProductNameToShort(); + if (value!.isEmpty) return NSSLStrings.of(context).fieldRequiredError(); + if (value.length < 3) return NSSLStrings.of(context).newProductNameToShort(); return null; } diff --git a/lib/pages/registration.dart b/lib/pages/registration.dart index 7626cc8..0a5e463 100644 --- a/lib/pages/registration.dart +++ b/lib/pages/registration.dart @@ -6,9 +6,11 @@ import 'package:nssl/server_communication/return_classes.dart'; import 'package:nssl/server_communication/s_c.dart'; import 'package:flutter/material.dart'; import 'package:nssl/models/model_export.dart'; +import '../helper/password_service.dart'; import 'login.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class Registration extends StatefulWidget { +class Registration extends ConsumerStatefulWidget { Registration({Key? key}) : super(key: key); static const String routeName = '/Registration'; @@ -17,7 +19,7 @@ class Registration extends StatefulWidget { RegistrationState createState() => RegistrationState(); } -class RegistrationState extends State { +class RegistrationState extends ConsumerState { final GlobalKey _scaffoldKey = GlobalKey(); final GlobalKey _formKey = GlobalKey(); @@ -27,10 +29,10 @@ class RegistrationState extends State { var pw2Input = ForInput(); var submit = ForInput(); var validateMode = AutovalidateMode.disabled; + bool initialized = false; void showInSnackBar(String value) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(value), duration: Duration(seconds: 3))); + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(value), duration: Duration(seconds: 3))); } Future _handleSubmitted() async { @@ -40,59 +42,6 @@ class RegistrationState extends State { return; } - /*ool error = false; - _resetInput(); - - String ni = _validateName(nameInput.textEditingController.text); - String ei = _validateEmail(emailInput.textEditingController.text); - String pi = _validatePassword(pwInput.textEditingController.text); - String p2i = _validatePassword2(pw2Input.textEditingController.text); - - if (ni != null) { - nameInput.decoration = InputDecoration( - labelText: nameInput.decoration.labelText, - helperText: nameInput.decoration.helperText, - errorText: ni); - nameInput.errorText = nameInput.decoration.errorText; - error = true; - } - if (ei != null) { - emailInput.decoration = InputDecoration( - labelText: emailInput.decoration.labelText, - helperText: emailInput.decoration.helperText, - errorText: ei); - emailInput.errorText = emailInput.decoration.errorText; - error = true; - } - if (pi != null) { - pwInput.decoration = InputDecoration( - labelText: pwInput.decoration.labelText, - helperText: pwInput.decoration.helperText, - errorText: pi); - pwInput.errorText = pwInput.decoration.errorText; - error = true; - } - if (p2i != null) { - pw2Input.decoration = InputDecoration( - labelText: pw2Input.decoration.labelText, - helperText: pw2Input.decoration.helperText, - errorText: p2i); - pw2Input.errorText = pw2Input.decoration.errorText; - error = true; - } - if (pwInput.textEditingController.text != - pw2Input.textEditingController.text) { - pw2Input.decoration = InputDecoration( - labelText: pw2Input.decoration.labelText, - helperText: pw2Input.decoration.helperText, - errorText: NSSLStrings.of(context).passwordsDontMatchError()); - pw2Input.errorText = pw2Input.decoration.errorText; - error = true; - } - - setState(() => {}); - - if (error == true) return;*/ String name = nameInput.textEditingController.text; String email = emailInput.textEditingController.text; String password = pwInput.textEditingController.text; @@ -103,11 +52,11 @@ class RegistrationState extends State { return; else { var response = LoginResult.fromJson(res.body); - if (!response.success!) { - showInSnackBar(response.error!); + if (!response.success) { + showInSnackBar(response.error); return; } - showInSnackBar(NSSLStrings.of(context)!.registrationSuccessfulMessage()); + showInSnackBar(NSSLStrings.of(context).registrationSuccessfulMessage()); var x = await UserSync.login(name, password, context); if (x.statusCode != 200) { @@ -115,57 +64,67 @@ class RegistrationState extends State { return; } var loginRes = LoginResult.fromJson(x.body); + var userState = ref.watch(userStateProvider.notifier); User.token = loginRes.token; - User.username = response.username; - User.eMail = response.eMail; - - await User.save(); - Navigator.pop(context); - runApp(NSSL()); + var user = User(loginRes.id, loginRes.username, loginRes.eMail); + userState.state = user; + ref.watch(currentListIndexProvider.notifier).state = 0; + await user.save(0); + + // Navigator.pop(context); + var restartState = ref.watch(appRestartProvider.notifier); + restartState.state = restartState.state + 1; } } String? _validateName(String? value) { if (value!.isEmpty) - return NSSLStrings.of(context)!.usernameEmptyError(); - else if (value.length < 4) - return NSSLStrings.of(context)!.usernameToShortError(); + return NSSLStrings.of(context).usernameEmptyError(); + else if (value.length < 4) return NSSLStrings.of(context).usernameToShortError(); return null; } String? _validateEmail(String? value) { - if (value!.isEmpty) return NSSLStrings.of(context)!.emailEmptyError(); + if (value!.isEmpty) return NSSLStrings.of(context).emailEmptyError(); RegExp email = RegExp( r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'); - if (!email.hasMatch(value)) - return NSSLStrings.of(context)!.emailIncorrectFormatError(); + if (!email.hasMatch(value)) return NSSLStrings.of(context).emailIncorrectFormatError(); return null; } String? _validatePassword(String? value) { - if (pwInput.textEditingController.text.isEmpty) - return NSSLStrings.of(context)!.chooseAPasswordPrompt(); - return null; + var errorCode = PasswordService.checkNewPassword(value ?? ""); + + if (errorCode != PasswordErrorCode.none) { + String errorText = ""; + switch (errorCode) { + case PasswordErrorCode.empty: + return NSSLStrings.of(context).passwordEmptyError(); + case PasswordErrorCode.none: + return null; + case PasswordErrorCode.tooShort: + return NSSLStrings.of(context).passwordTooShortError(); + case PasswordErrorCode.missingCharacters: + return NSSLStrings.of(context).passwordMissingCharactersError(); + } + } } String? _validatePassword2(String? value) { - if (pwInput.textEditingController.text.isEmpty) - return NSSLStrings.of(context)!.reenterPasswordPrompt(); - if (pwInput.textEditingController.text != value) - return NSSLStrings.of(context)!.passwordsDontMatchError(); + if (pwInput.textEditingController.text.isEmpty) return NSSLStrings.of(context).reenterPasswordPrompt(); + if (pwInput.textEditingController.text != value) return NSSLStrings.of(context).passwordsDontMatchError(); return null; } @override Widget build(BuildContext context) { - _resetInput(); + if (!initialized) _resetInput(); return Scaffold( key: _scaffoldKey, - appBar: AppBar( - title: Text(NSSLStrings.of(context)!.registrationTitle())), + appBar: AppBar(title: Text(NSSLStrings.of(context).registrationTitle())), body: Form( key: _formKey, - autovalidateMode: validateMode, + autovalidateMode: validateMode, child: ListView( // physics: const NeverScrollableScrollPhysics(), padding: const EdgeInsets.symmetric(horizontal: 32.0), @@ -177,9 +136,7 @@ class RegistrationState extends State { autocorrect: false, validator: _validateName, onSaved: (s) { - FocusScope - .of(context) - .requestFocus(emailInput.focusNode); + FocusScope.of(context).requestFocus(emailInput.focusNode); }), TextFormField( key: emailInput.key, @@ -192,37 +149,28 @@ class RegistrationState extends State { onSaved: (s) { FocusScope.of(context).requestFocus(pwInput.focusNode); }), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: TextFormField( - key: pwInput.key, - decoration: pwInput.decoration, - controller: pwInput.textEditingController, - focusNode: pwInput.focusNode, - autocorrect: false, - obscureText: true, - validator: _validatePassword, - onSaved: (s) { - FocusScope - .of(context) - .requestFocus(pw2Input.focusNode); - })), - SizedBox(width: 16.0), - Flexible( - child: TextFormField( - key: pw2Input.key, - decoration: pw2Input.decoration, - controller: pw2Input.textEditingController, - focusNode: pw2Input.focusNode, - autocorrect: false, - obscureText: true, - validator: _validatePassword2, - onSaved: (s) { - _handleSubmitted(); - })), - ]), + TextFormField( + key: pwInput.key, + decoration: pwInput.decoration, + controller: pwInput.textEditingController, + focusNode: pwInput.focusNode, + autocorrect: false, + obscureText: true, + validator: _validatePassword, + onSaved: (s) { + FocusScope.of(context).requestFocus(pw2Input.focusNode); + }), + TextFormField( + key: pw2Input.key, + decoration: pw2Input.decoration, + controller: pw2Input.textEditingController, + focusNode: pw2Input.focusNode, + autocorrect: false, + obscureText: true, + validator: _validatePassword2, + onSaved: (s) { + _handleSubmitted(); + }), Row(children: [ Flexible( child: Container( @@ -230,8 +178,7 @@ class RegistrationState extends State { alignment: const FractionalOffset(0.5, 0.5), child: ElevatedButton( child: Center( - child: Text( - NSSLStrings.of(context)!.registerButton()), + child: Text(NSSLStrings.of(context).registerButton()), ), onPressed: _handleSubmitted, ), @@ -258,24 +205,17 @@ class RegistrationState extends State { _resetInput() { nameInput.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.usernameRegisterHint(), - labelText: NSSLStrings.of(context)!.username()); + helperText: NSSLStrings.of(context).usernameRegisterHint(), labelText: NSSLStrings.of(context).username()); emailInput.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.emailRegisterHint(), - labelText: NSSLStrings.of(context)!.emailTitle()); + helperText: NSSLStrings.of(context).emailRegisterHint(), labelText: NSSLStrings.of(context).emailTitle()); pwInput.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.passwordRegisterHint(), - labelText: NSSLStrings.of(context)!.password()); + helperText: NSSLStrings.of(context).passwordRegisterHint(), labelText: NSSLStrings.of(context).password()); pw2Input.decoration = InputDecoration( - helperText: NSSLStrings.of(context)!.retypePasswordHint(), - labelText: NSSLStrings.of(context)!.retypePasswordTitle()); - } - - @override - initState() { - super.initState(); + helperText: NSSLStrings.of(context).retypePasswordHint(), + labelText: NSSLStrings.of(context).retypePasswordTitle()); + initialized = true; } } diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index e69de29..4a76c4f 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -0,0 +1,48 @@ +import 'package:adaptive_theme/adaptive_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:nssl/localization/nssl_strings.dart'; +import 'package:nssl/options/themes.dart'; +import 'package:nssl/pages/pages.dart'; + +class SettingsPage extends StatefulWidget { + SettingsPage(); + + @override + SettingsPageState createState() => SettingsPageState(); +} + +class SettingsPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBar( + title: Text(NSSLStrings.of(context).settings()), + leading: IconButton(onPressed: () => Navigator.pop(context), icon: Icon(Icons.arrow_back)), + ), + body: ListView(children: [ + ListTile( + leading: Icon(Icons.palette), + title: Text(NSSLStrings.of(context).changeTheme()), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => CustomThemePage(), + fullscreenDialog: true, + )) + .whenComplete(() => AdaptiveTheme.of(context) + .setTheme(light: Themes.lightTheme.theme!, dark: Themes.darkTheme.theme, notify: true)); + }, + ), + Divider(), + ListTile( + leading: Icon(Icons.info), + title: Text(NSSLStrings.of(context).about()), + onTap: () => Navigator.push( + context, MaterialPageRoute(builder: (c) => AboutPage(), fullscreenDialog: true)), + ), + ]), + ); + } +} diff --git a/lib/pages/shopping_item_search.dart b/lib/pages/shopping_item_search.dart index 91a6ad0..67b7cee 100644 --- a/lib/pages/shopping_item_search.dart +++ b/lib/pages/shopping_item_search.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:nssl/helper/iterable_extensions.dart'; import 'package:nssl/localization/nssl_strings.dart'; import 'package:nssl/models/model_export.dart'; import 'package:nssl/server_communication//s_c.dart'; @@ -6,8 +7,9 @@ import 'dart:async'; import 'dart:convert'; import 'package:http/http.dart'; import 'package:nssl/server_communication/return_classes.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class ProductAddPage extends StatefulWidget { +class ProductAddPage extends ConsumerStatefulWidget { ProductAddPage({Key? key, this.title}) : super(key: key); final String? title; @@ -17,7 +19,7 @@ class ProductAddPage extends StatefulWidget { static ProductResult fromJson(Map data) { var r = ProductResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; r.gtin = data["gtin"]; r.quantity = data["quantity"]; r.unit = data["unit"]; @@ -27,7 +29,7 @@ class ProductAddPage extends StatefulWidget { } } -class _ProductAddPageState extends State { +class _ProductAddPageState extends ConsumerState { final GlobalKey _mainScaffoldKey = GlobalKey(); GlobalKey _iff = GlobalKey(); GlobalKey _ib = GlobalKey(); @@ -36,52 +38,48 @@ class _ProductAddPageState extends State { int k = 1; Future _addProductToList(String? name, String? gtin) async { - var list = User.currentList; + var list = ref.read(currentListProvider); if (list != null) { - if (list.shoppingItems == null) list.shoppingItems = []; - - var item = list.shoppingItems! - .firstWhere((x) => x!.name == name, orElse: () => null); + var siState = ref.watch(shoppingItemsProvider.notifier); + var shoppingItems = siState.state.toList(); + var item = shoppingItems.firstOrNull((x) => x.name == name); ShoppingItem? afterAdd; if (item != null) { - var answer = await ShoppingListSync.changeProductAmount( - list.id!, item.id!, 1, context); + var answer = await ShoppingListSync.changeProductAmount(list.id, item.id, 1, context); var p = ChangeListItemResult.fromJson((answer).body); - setState(() { - item.amount = p.amount; - item.changed = p.changed; - }); + shoppingItems.remove(item); + afterAdd = item.cloneWith(newAmount: p.amount, newChanged: p.changed); } else { - var p = AddListItemResult.fromJson((await ShoppingListSync.addProduct( - list.id!, name!, gtin ?? '-', 1, context)) - .body); - afterAdd = ShoppingItem(p.name) - ..amount = 1 - ..id = p.productId; - setState(() => list.shoppingItems!.add(afterAdd)); + var p = AddListItemResult.fromJson( + (await ShoppingListSync.addProduct(list.id, name!, gtin ?? '-', 1, context)).body); + int sortOrder = 0; + if (shoppingItems.length > 0) sortOrder = shoppingItems.last.sortOrder + 1; + afterAdd = ShoppingItem(p.name, list.id, sortOrder, amount: 1, id: p.productId); } + shoppingItems.add(afterAdd); + siState.state = shoppingItems; + showInSnackBar( - item == null - ? NSSLStrings.of(context)!.addedProduct() + "$name" - : "$name" + NSSLStrings.of(context)!.productWasAlreadyInList(), - duration: Duration(seconds: item == null ? 2 : 4), - action: SnackBarAction( - label: NSSLStrings.of(context)!.undo(), - onPressed: () async { - var res = item == null - ? await ShoppingListSync.deleteProduct( - list.id!, afterAdd!.id!, context) - : await ShoppingListSync.changeProductAmount( - list.id!, item.id!, -1, context); - if (Result.fromJson(res.body).success!) { - if (item == null) - list.shoppingItems!.remove(afterAdd); - else - item.amount = item.amount - 1; - } - })); - list.save(); + item == null + ? NSSLStrings.of(context).addedProduct() + "$name" + : "$name" + NSSLStrings.of(context).productWasAlreadyInList(), + duration: Duration(seconds: item == null ? 2 : 4), + action: SnackBarAction( + label: NSSLStrings.of(context).undo(), + onPressed: () async { + var res = item == null + ? await ShoppingListSync.deleteProduct(list.id, afterAdd!.id, context) + : await ShoppingListSync.changeProductAmount(list.id, item.id, -1, context); + if (Result.fromJson(res.body).success) { + var newState = siState.state.toList(); + + newState.remove(afterAdd); + if (item != null) newState.add(item); + siState.state = newState; + } + }), + ); } } @@ -98,8 +96,7 @@ class _ProductAddPageState extends State { title: Form( child: TextField( key: _iff, - decoration: InputDecoration( - hintText: NSSLStrings.of(context)!.searchProductHint()), + decoration: InputDecoration(hintText: NSSLStrings.of(context).searchProductHint()), onSubmitted: (x) => _searchProducts(x, 1), autofocus: true, controller: tec, @@ -132,8 +129,7 @@ class _ProductAddPageState extends State { List? z = jsonDecode(o.body); // .decode(o.body); if (!noMoreProducts && z!.length <= 0) { noMoreProducts = true; - showInSnackBar(NSSLStrings.of(context)!.noMoreProductsMessage(), - duration: Duration(seconds: 3)); + showInSnackBar(NSSLStrings.of(context).noMoreProductsMessage(), duration: Duration(seconds: 3)); } else setState(() => prList.addAll(z! .map((f) => ProductResult() @@ -152,8 +148,7 @@ class _ProductAddPageState extends State { lastLength = prList.length; } return ListTile( - title: Text(prList[i].name!), - onTap: () => _addProductToList(prList[i].name, prList[i].gtin)); + title: Text(prList[i].name!), onTap: () => _addProductToList(prList[i].name, prList[i].gtin)); }, itemCount: prList.length); return listView; @@ -161,12 +156,9 @@ class _ProductAddPageState extends State { return Text(""); } - void showInSnackBar(String value, - {Duration? duration, SnackBarAction? action}) { + void showInSnackBar(String value, {Duration? duration, SnackBarAction? action}) { ScaffoldMessenger.of(context).removeCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar(SnackBar( - content: Text(value), - duration: duration ?? Duration(seconds: 3), - action: action)); + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(value), duration: duration ?? Duration(seconds: 3), action: action)); } } diff --git a/lib/server_communication/helper_methods.dart b/lib/server_communication/helper_methods.dart index 3087054..4638603 100644 --- a/lib/server_communication/helper_methods.dart +++ b/lib/server_communication/helper_methods.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:nssl/localization/nssl_strings.dart'; +import 'package:nssl/manager/database_manager.dart'; import 'dart:convert'; import 'dart:async'; import 'package:nssl/models/model_export.dart'; @@ -10,21 +11,26 @@ import 'user_sync.dart'; class HelperMethods { static const String scheme = "https"; static const String host = "nssl.susch.eu"; + static const int port = 443; + // static const String scheme = "http"; + // static const String host = "192.168.49.22"; // static const String url = "http://192.168.49.22:4344"; - static Future post(String path, BuildContext? context, [Object? body, skipTokenRefresh = false]) async { + static Future post(String path, BuildContext? context, + [Object? body, skipTokenRefresh = false, Map? query]) async { if (!skipTokenRefresh) await handleTokenRefresh(context); - var res = await http.post(Uri(host: host, scheme: scheme, path: path), + var res = await http.post( + Uri(host: host, scheme: scheme, path: path, port: port, queryParameters: query /*, port: 4344*/), body: jsonEncode(body), - headers: {"Content-Type": "application/json", User.token == null ? "X-foo" : "X-Token": User.token ?? ""}); + headers: {"Content-Type": "application/json", User.token == "" ? "X-foo" : "X-Token": User.token}); reactToRespone(res, context); return res; } static Future get(String path, BuildContext? context, [String query = ""]) async { await handleTokenRefresh(context); - var res = await http.get(Uri(host: host, scheme: scheme, path: path, query: query), - headers: {"Content-Type": "application/json", User.token == null ? "X-foo" : "X-Token":User.token ?? ""}); + var res = await http.get(Uri(host: host, scheme: scheme, path: path, query: query, port: port /*, port: 4344*/), + headers: {"Content-Type": "application/json", User.token == "" ? "X-foo" : "X-Token": User.token}); reactToRespone(res, context); return res; } @@ -32,17 +38,17 @@ class HelperMethods { static Future put(String path, BuildContext? context, [Object? body, bool skipTokenRefresh = false]) async { if (!skipTokenRefresh) await handleTokenRefresh(context); - var res = await http.put(Uri(host: host, scheme: scheme, path: path), + var res = await http.put(Uri(host: host, scheme: scheme, path: path, port: port /*, port: 4344*/), body: jsonEncode(body), - headers: {"Content-Type": "application/json", User.token == null ? "X-foo" : "X-Token": User.token ?? ""}); + headers: {"Content-Type": "application/json", User.token == "" ? "X-foo" : "X-Token": User.token}); reactToRespone(res, context); return res; } static Future delete(String path, BuildContext? context) async { await handleTokenRefresh(context); - var res = await http.delete(Uri(host: host, scheme: scheme, path: path), - headers: {"Content-Type": "application/json", User.token == null ? "X-foo" : "X-Token": User.token ?? ""}); + var res = await http.delete(Uri(host: host, scheme: scheme, path: path, port: port /*, port: 4344*/), + headers: {"Content-Type": "application/json", User.token == "" ? "X-foo" : "X-Token": User.token}); reactToRespone(res, context); return res; @@ -53,7 +59,6 @@ class HelperMethods { print(jsonString); } - static bool reactToRespone(http.Response respone, BuildContext? context, {ScaffoldState? scaffoldState}) { if (context == null) return false; if (respone.statusCode == 500) { @@ -62,8 +67,8 @@ class HelperMethods { showDialog( builder: (BuildContext context) { return AlertDialog( - title: Text(NSSLStrings.of(context)!.tokenExpired()), - content: Text(NSSLStrings.of(context)!.tokenExpiredExplanation()), + title: Text(NSSLStrings.of(context).tokenExpired()), + content: Text(NSSLStrings.of(context).tokenExpiredExplanation()), actions: [ MaterialButton( onPressed: () async { @@ -86,7 +91,7 @@ class HelperMethods { var m = jsonDecode(t.body); var to = m["token"]; User.token = to; - User.save(); + DatabaseManager.database.execute("Update User set token = ?", [to]); } } } diff --git a/lib/server_communication/jwt.dart b/lib/server_communication/jwt.dart index 1d1e4d7..8b41398 100644 --- a/lib/server_communication/jwt.dart +++ b/lib/server_communication/jwt.dart @@ -5,26 +5,22 @@ import 'package:nssl/models/user.dart'; class JWT { static Future newToken() async { //JsonWebToken jwt = JsonWebToken.decode(User.token); - var jwt = jsonDecode(tokenToJson()); + var jwt = jsonDecode(tokenToJson(User.token)); - // var expiresEnd = jwt.indexOf('Z\",\n'); // if(expiresEnd == -1) // expiresEnd = jwt.indexOf('Z\",\r\n'); // var exp = jwt.substring(jwt.indexOf("expires"), jwt.indexOf('Z\",\n')); DateTime expires = DateTime.parse(jwt["expires"]); - if ((DateTime.now()).add(Duration(days: 29)).isAfter(expires)) - return true; + if ((DateTime.now()).add(Duration(days: 29)).isAfter(expires)) return true; return false; } - static String tokenToJson() { - var s = User.token!; - var temps = s - .substring(s.indexOf(".")+1, s.lastIndexOf(".")); - if(temps.length % 4 != 0) - temps = temps.padRight(temps.length+(4-temps.length%4), "="); + static String tokenToJson(String? token) { + if (token == null || token == "") return ""; + var temps = token.substring(token.indexOf(".") + 1, token.lastIndexOf(".")); + if (temps.length % 4 != 0) temps = temps.padRight(temps.length + (4 - temps.length % 4), "="); return Utf8Decoder().convert(Base64Codec().decode(temps)); } @@ -32,16 +28,13 @@ class JWT { //JsonWebToken jwt = JsonWebToken.decode(token); //var map = jwt.payload.toJson(); //return int.parse(map["id"]); - String jwt = tokenToJson(); - var exp = jwt.substring(jwt.indexOf("id"), jwt.indexOf(',\n', jwt.indexOf("id"))); - return int.parse(exp.substring(5)); + var jwt = jsonDecode(tokenToJson(token)); + return jwt["id"]; // DateTime.parse("2018-07-07T10:49:56.9479953Z"); // var codec = Base64Codec.urlSafe(); // var s = codec.decode(User.token // .substring(User.token.indexOf("."), User.token.lastIndexOf("."))); // return 10; - - } } diff --git a/lib/server_communication/request_classes.dart b/lib/server_communication/request_classes.dart index df7369d..52d8b33 100644 --- a/lib/server_communication/request_classes.dart +++ b/lib/server_communication/request_classes.dart @@ -13,6 +13,12 @@ class ChangePasswordArgs { toJson() => {"oldPWHash": "$oldPWHash", "newPWHash": "$newPWHash"}; } +class ResetPasswordArgs { + ResetPasswordArgs(this.email); + String email; + toJson() => {"email": "$email"}; +} + class AddContributorArgs { AddContributorArgs(this.name); String name; @@ -29,7 +35,7 @@ class ChangeProductArgs { ChangeProductArgs({this.change, this.newName}); int? change; String? newName; - toJson() => {"change": "$change", "newName" : "$newName"}; + toJson() => {"change": "$change", "newName": "$newName"}; } class ChangeProductsArgs { @@ -51,6 +57,12 @@ class AddListArgs { toJson() => {"name": "$name"}; } +class AddRecipeArgs { + AddRecipeArgs(this.idOrUrl); + String idOrUrl; + toJson() => {"idOrUrl": "$idOrUrl"}; +} + class DeleteProductArgs { DeleteProductArgs(this.listId, this.productId); int listId; @@ -62,8 +74,7 @@ class AddProductArgs { String? productName; String? gtin; int? amount; - toJson() => - {"productName": "$productName", "gtin": "$gtin", "amount": "$amount"}; + toJson() => {"productName": "$productName", "gtin": "$gtin", "amount": "$amount"}; } class AddNewProductArgs { @@ -72,12 +83,7 @@ class AddNewProductArgs { String? gtin; String? unit; double quantity; - toJson() => { - "name": "$name", - "gtin": "$gtin", - "unit": "$unit", - "quantity": "$quantity" - }; + toJson() => {"name": "$name", "gtin": "$gtin", "unit": "$unit", "quantity": "$quantity"}; } class GetProductsArgs { diff --git a/lib/server_communication/return_classes.dart b/lib/server_communication/return_classes.dart index 732983a..b4665a8 100644 --- a/lib/server_communication/return_classes.dart +++ b/lib/server_communication/return_classes.dart @@ -3,21 +3,28 @@ import 'dart:convert'; import 'package:nssl/models_json.dart'; class BaseResult { - bool? success; - String? error; + late bool success; + late String error; + static BaseResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); + + static BaseResult _fromJson(Map data) { + var r = BaseResult(); + r.success = data["success"]; + r.error = data["error"] as String? ?? ""; + return r; + } } class CreateResult extends BaseResult { int? id; String? username; String? eMail; - static CreateResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static CreateResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static CreateResult _fromJson(Map data) { var r = CreateResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; r.id = data["id"]; r.username = data["username"]; r.eMail = data["eMail"]; @@ -27,20 +34,19 @@ class CreateResult extends BaseResult { } class LoginResult extends BaseResult { - int? id; - String? username; - String? eMail; - String? token; - static LoginResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + late int id; + late String username; + late String eMail; + late String token; + static LoginResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static LoginResult _fromJson(Map data) { var r = LoginResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; r.id = data["id"]; - r.username = data["username"]; - r.eMail = data["eMail"]; - r.token = data["token"]; + r.username = data["username"] as String? ?? ""; + r.eMail = data["eMail"] as String? ?? ""; + r.token = data["token"] as String? ?? ""; return r; } } @@ -48,13 +54,12 @@ class LoginResult extends BaseResult { class AddContributorResult extends BaseResult { String? name; int? id; - static AddContributorResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static AddContributorResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static AddContributorResult _fromJson(Map data) { var r = AddContributorResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; r.id = data["id"]; r.name = data["name"]; return r; @@ -70,13 +75,12 @@ class ContributorResult { class GetContributorsResult extends BaseResult { late List contributors; - static GetContributorsResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static GetContributorsResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static GetContributorsResult _fromJson(Map data) { var r = GetContributorsResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; List unMaped = data["contributors"] ?? []; r.contributors = unMaped .map((x) => ContributorResult() @@ -93,13 +97,12 @@ class ProductResult extends BaseResult { String? gtin; double? quantity; String? unit; - static ProductResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static ProductResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static ProductResult _fromJson(Map data) { var r = ProductResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; r.gtin = data["gtin"]; r.quantity = data["quantitity"]; r.unit = data["unit"]; @@ -109,16 +112,15 @@ class ProductResult extends BaseResult { } class AddListItemResult extends BaseResult { - int? productId; - String? name; + late int productId; + late String name; String? gtin; - static AddListItemResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static AddListItemResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static AddListItemResult _fromJson(Map data) { var r = AddListItemResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; r.productId = data["productId"]; r.gtin = data["gtin"]; r.name = data["name"]; @@ -127,18 +129,17 @@ class AddListItemResult extends BaseResult { } class ChangeListItemResult extends BaseResult { - String? name; - int? id; + late String name; + late int id; late int amount; - int? listId; - DateTime? changed; - static ChangeListItemResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + late int listId; + late DateTime? changed; + static ChangeListItemResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static ChangeListItemResult _fromJson(Map data) { var r = ChangeListItemResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; r.id = data["id"]; r.amount = data["amount"]; r.listId = data["listId"]; @@ -149,17 +150,16 @@ class ChangeListItemResult extends BaseResult { } class AddListResult extends BaseResult { - int? id; - String? name; - static AddListResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + int id; + String name; + AddListResult(this.id, this.name); + + static AddListResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static AddListResult _fromJson(Map data) { - var r = AddListResult(); + var r = AddListResult(data["id"], data["name"]); r.success = data["success"]; - r.error = data["error"]; - r.id = data["id"]; - r.name = data["name"]; + r.error = data["error"] as String? ?? ""; return r; } } @@ -174,8 +174,7 @@ class GetListResult { Iterable? products; String? contributors; - static GetListResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static GetListResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static GetListResult _fromJson(Map data) { var r = GetListResult(); @@ -184,13 +183,7 @@ class GetListResult { r.userId = data["userId"]; r.owner = data["owner"]; var unMaped = data["products"] ?? []; - r.products = unMaped.map((x) => ShoppingItem( - x["id"], - x["amount"], - x["name"], - DateTime.tryParse(x["changed"]), - DateTime.tryParse(x["created"]), - x["sortOrder"])); + r.products = unMaped.map((x) => ShoppingItem.fromJson(x)); r.contributors = data["contributors"]; @@ -201,26 +194,14 @@ class GetListResult { class GetListsResult { late Iterable shoppingLists; - static GetListsResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static GetListsResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static GetListsResult _fromJson(Map data) { var r = GetListsResult(); List unmappedShoppingLists = data["lists"]; - r.shoppingLists = unmappedShoppingLists.map((s) => ShoppingList() - ..products = s["products"] - .map((x) => ShoppingItem( - x["id"], - x["amount"], - x["name"], - DateTime.tryParse(x["changed"]), - DateTime.tryParse(x["created"]), - x["sortOrder"])) - .toList() - .cast() - ..id = s["id"] - ..name = s["name"]); + r.shoppingLists = unmappedShoppingLists.map((s) => ShoppingList( + s["id"], s["name"], s["products"].map((x) => ShoppingItem.fromJson(x)).toList().cast())); return r; } @@ -231,21 +212,14 @@ class GetBoughtListResult { String? name; late Iterable products; - static GetBoughtListResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static GetBoughtListResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static GetBoughtListResult _fromJson(Map data) { var r = GetBoughtListResult(); r.id = data["id"]; r.name = data["name"]; List unMaped = data["products"] ?? []; - r.products = unMaped.map((x) => ShoppingItem( - x["id"], - x["boughtAmount"], - x["name"], - DateTime.tryParse(x["changed"]), - DateTime.tryParse(x["created"]), - x["sortOrder"])); + r.products = unMaped.map((x) => ShoppingItem.fromJson(x)); return r; } } @@ -255,8 +229,7 @@ class InfoResult { String? username; String? eMail; List? listIds; - static InfoResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static InfoResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static InfoResult _fromJson(Map data) { var r = InfoResult(); @@ -271,33 +244,30 @@ class InfoResult { class HashResult extends Result { int? hash; - static HashResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static HashResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static HashResult _fromJson(Map data) { var r = HashResult(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; r.hash = data["hash"]; return r; } } class Result extends BaseResult { - static Result fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static Result fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static Result _fromJson(Map data) { var r = Result(); r.success = data["success"]; - r.error = data["error"]; + r.error = data["error"] as String? ?? ""; return r; } } class SessionRefreshResult { String? token; - static SessionRefreshResult fromJson(String dataString) => - _fromJson(jsonDecode(dataString)); + static SessionRefreshResult fromJson(String dataString) => _fromJson(jsonDecode(dataString)); static SessionRefreshResult _fromJson(Map data) { var r = SessionRefreshResult(); diff --git a/lib/server_communication/shopping_list_sync.dart b/lib/server_communication/shopping_list_sync.dart index 832a9f0..0221c6d 100644 --- a/lib/server_communication/shopping_list_sync.dart +++ b/lib/server_communication/shopping_list_sync.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 's_c.dart'; @@ -8,67 +7,75 @@ import 'package:http/http.dart'; final String listpath = "shoppinglists"; class ShoppingListSync { - static Future getList(int? listId,BuildContext? context, {bool bought = false}) => + static Future getList(int? listId, BuildContext? context, {bool bought = false}) => HelperMethods.get("$listpath/$listId/$bought", context); - static Future getLists(BuildContext? context) => - HelperMethods.get("$listpath/batchaction/", context); + static Future getLists(BuildContext? context) => HelperMethods.get("$listpath/batchaction/", context); - static Future deleteList(int listId,BuildContext? context) => + static Future deleteList(int listId, BuildContext? context) => HelperMethods.delete("$listpath/$listId", context); - static Future changeLName(int listId, String newName,BuildContext? context) => + static Future changeLName(int listId, String newName, BuildContext? context) => HelperMethods.put("$listpath/$listId", context, ChangeListNameArgs(newName)); - static Future addList(String listName,BuildContext? context) => + static Future addList(String listName, BuildContext? context) => HelperMethods.post("$listpath", context, AddListArgs(listName)); - static Future deleteProduct(int? listId, int? productId,BuildContext context) => + static Future addRecipe(String idOrUrl, BuildContext? context) => + HelperMethods.post("recipe/CreateShoppingListForRecipe", context, AddRecipeArgs(idOrUrl)); + + static Future importRecipe(String idOrUrl, int listId, BuildContext? context) => HelperMethods.post( + "recipe/AddRecipeToList", + context, + AddRecipeArgs(idOrUrl), + false, + {"amountOfPeople": "4", "listId": listId.toString()}); + + static Future checkRecipeInput(String idOrUrl, BuildContext? context) => + HelperMethods.post("recipe/IsValidIdOrUrl", context, AddRecipeArgs(idOrUrl)); + + static Future deleteProduct(int? listId, int? productId, BuildContext context) => HelperMethods.delete("$listpath/$listId/products/$productId", context); - static Future deleteProducts(int? listId, List productIds,BuildContext? context) => + static Future deleteProducts(int? listId, List productIds, BuildContext? context) => HelperMethods.post("$listpath/$listId/products/batchaction/delete", context, DeleteProductsArgs(productIds)); static Future addProduct( - int? listId, String? productName, String? gtin, int amount,BuildContext? context) => + int? listId, String? productName, String? gtin, int amount, BuildContext? context) => HelperMethods.post( - "$listpath/$listId/products/", context, + "$listpath/$listId/products/", + context, AddProductArgs() ..amount = amount ..gtin = gtin ..productName = productName); - static Future changeProductAmount( - int? listId, int? productId, int? change,BuildContext? context) => - HelperMethods.put("$listpath/$listId/products/$productId", context, - ChangeProductArgs(change: change, newName: "")); + static Future changeProductAmount(int? listId, int? productId, int? change, BuildContext? context) => + HelperMethods.put( + "$listpath/$listId/products/$productId", context, ChangeProductArgs(change: change, newName: "")); - static Future changeProductName( - int? listId, int? productId, String newName,BuildContext? context) => - HelperMethods.put("$listpath/$listId/products/$productId", context, - ChangeProductArgs(change: 0, newName: newName)); + static Future changeProductName(int? listId, int? productId, String newName, BuildContext? context) => + HelperMethods.put( + "$listpath/$listId/products/$productId", context, ChangeProductArgs(change: 0, newName: newName)); static Future changeProducts( - int? listId, List productIds, List amount,BuildContext? context) => - HelperMethods.post("$listpath/$listId/products/batchaction/change", context, - ChangeProductsArgs(productIds, amount)); + int? listId, List productIds, List amount, BuildContext? context) => + HelperMethods.post( + "$listpath/$listId/products/batchaction/change", context, ChangeProductsArgs(productIds, amount)); - static Future reorderProducts(int? listId, List productIds,BuildContext context) => + static Future reorderProducts(int? listId, List productIds, BuildContext context) => HelperMethods.post("$listpath/$listId/products/batchaction/order", context, DeleteProductsArgs(productIds)); - - static Future deleteContributor( - int listId, int userId,BuildContext context) //Wenn lokal gespeichert + static Future deleteContributor(int listId, int userId, BuildContext context) //Wenn lokal gespeichert => HelperMethods.delete("$listpath/$listId/contributors/$userId", context); - static Future addContributor(int listId, String contributorName,BuildContext context) => - HelperMethods.post("$listpath/$listId/contributors/", context, - AddContributorArgs(contributorName)); + static Future addContributor(int listId, String contributorName, BuildContext context) => + HelperMethods.post("$listpath/$listId/contributors/", context, AddContributorArgs(contributorName)); - static Future getContributors(int listId,BuildContext context) => + static Future getContributors(int listId, BuildContext context) => HelperMethods.get("$listpath/$listId/contributors/", context); - static Future changeRight(int listId, int changedUser,BuildContext context) - => HelperMethods.put("$listpath/$listId/contributors/$changedUser", context); + static Future changeRight(int listId, int changedUser, BuildContext context) => + HelperMethods.put("$listpath/$listId/contributors/$changedUser", context); } diff --git a/lib/server_communication/user_sync.dart b/lib/server_communication/user_sync.dart index 301173e..91e0bc0 100644 --- a/lib/server_communication/user_sync.dart +++ b/lib/server_communication/user_sync.dart @@ -5,27 +5,37 @@ import 'dart:async'; import 'helper_methods.dart'; import 'package:http/http.dart'; -final String path = "users"; -final String path2 = "session"; +final String usersPath = "users"; +final String sessionPath = "session"; +final String passwortPath = "password"; class UserSync { - static Future create( - String username, String email, String password, BuildContext context) => + static Future create(String username, String email, String password, + BuildContext context) => HelperMethods.post("registration", context, LoginArgs(username: username, pwHash: password, eMail: email), true); - static Future login(String username, String password, BuildContext context) => - HelperMethods.post( - path2, context, LoginArgs(username: username, pwHash: password), true); + static Future login( + String username, String password, BuildContext context) => + HelperMethods.post(sessionPath, context, + LoginArgs(username: username, pwHash: password), true); - static Future loginEmail(String email, String password, BuildContext context) => - HelperMethods.post(path2, context, LoginArgs(eMail: email, pwHash: password), true); + static Future loginEmail( + String email, String password, BuildContext context) => + HelperMethods.post(sessionPath, context, + LoginArgs(eMail: email, pwHash: password), true); - static Future info(BuildContext context) => HelperMethods.get(path, context); + static Future info(BuildContext context) => + HelperMethods.get(usersPath, context); static Future refreshToken(BuildContext? context) => - HelperMethods.put(path2, context, null, true); + HelperMethods.put(sessionPath, context, null, true); - static Future changePassword(String oldPassword, String newPassword, String? token, BuildContext context) => - HelperMethods.put(path, context, ChangePasswordArgs(oldPassword, newPassword)); + static Future changePassword(String oldPassword, String newPassword, + String? token, BuildContext context) => + HelperMethods.put( + usersPath, context, ChangePasswordArgs(oldPassword, newPassword)); + + static Future resetPassword(String email, BuildContext context) => + HelperMethods.post(passwortPath, context, ResetPasswordArgs(email), true); } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index e71a16d..0000000 --- a/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void fl_register_plugins(FlPluginRegistry* registry) { -} diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 51436ae..0000000 --- a/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b247c0f..f0bed44 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,6 +11,7 @@ import flutter_local_notifications import path_provider_macos import shared_preferences_macos import sqlite3_flutter_libs +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) @@ -19,4 +20,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/macos/Runner/GoogleService-Info.plist b/macos/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..1d04856 --- /dev/null +++ b/macos/Runner/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 714311873087-ch95nc16kbiuckgaogfg388vbhi3ka5n.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.714311873087-ch95nc16kbiuckgaogfg388vbhi3ka5n + API_KEY + AIzaSyCaNflc7mTb67r81IZP4Xpn6vkU8kto9Fc + GCM_SENDER_ID + 714311873087 + PLIST_VERSION + 1 + BUNDLE_ID + de.susch19.nssl + PROJECT_ID + nonsuckingshoppinglist + STORAGE_BUCKET + nonsuckingshoppinglist.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:714311873087:ios:293f27fa016b99dc4dbe8a + DATABASE_URL + https://nonsuckingshoppinglist.firebaseio.com + + \ No newline at end of file diff --git a/macos/firebase_app_id_file.json b/macos/firebase_app_id_file.json new file mode 100644 index 0000000..c674159 --- /dev/null +++ b/macos/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:714311873087:ios:293f27fa016b99dc4dbe8a", + "FIREBASE_PROJECT_ID": "nonsuckingshoppinglist", + "GCM_SENDER_ID": "714311873087" +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 4be792b..947e625 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,40 +1,54 @@ name: nssl description: An app for handling shopping lists +version: 0.40.0+0 + environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=2.12.0' dependencies: flutter: sdk: flutter flutter_localizations: sdk: flutter - path_provider: - intl: - file: - # intl_translation: - shared_preferences: - # jwt: - # git: git://github.com/susch19/dart-json_web_token.git - #jaguar_jwt: "^1.1.5" + adaptive_theme: crypto: + file: firebase_core: - firebase_messaging: + firebase_messaging: + flutter_colorpicker: flutter_local_notifications: + flutter_svg: http: + intl: + path_provider: path: - sqlite3_flutter_libs: - sqflite_common_ffi: - process_run: + pdf: permission_handler: - scandit_flutter_datacapture_barcode: - flutter_platform_widgets: + process_run: + scandit_flutter_datacapture_barcode: 6.14.1 + shared_preferences: + sqlite3_flutter_libs: # scandit_flutter_datacapture_barcode: ^6.7.0 # scandit_flutter_datacapture_core: ^6.7.0 # flutter_scandit: ^0.1.0 - pdf: - flutter_colorpicker: - adaptive_theme: + # intl_translation: + # jwt: + # git: git://github.com/susch19/dart-json_web_token.git + #jaguar_jwt: "^1.1.5" + url_launcher: + share_handler: ^0.0.8 + +dev_dependencies: + build_runner: ^2.0.6 + flutter_riverpod: 2.0.0-dev.8 + flutter_lints: ^2.0.1 + json_serializable: ^6.1.3 + sqflite_common_ffi: + git: + url: https://github.com/muhleder/sqflite.git + path: sqflite_common_ffi + ref: web #git: https://github.com/tekartik/sqflite.git @@ -43,7 +57,9 @@ dependencies: # The following section is specific to Flutter. flutter: - + assets: + - assets/images/ + - assets/vectors/ # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the Icons class. diff --git a/web/firebase-messaging-sw.js b/web/firebase-messaging-sw.js new file mode 100644 index 0000000..339e505 --- /dev/null +++ b/web/firebase-messaging-sw.js @@ -0,0 +1,21 @@ +importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"); +importScripts("https://www.gstatic.com/firebasejs/8.10.0/firebase-messaging.js"); + +firebase.initializeApp({ + apiKey: 'AIzaSyD2AWdZaGet1YvVgck5gZ3mww9-6NDozek', + appId: '1:714311873087:web:7ff97d63debd5b364dbe8a', + messagingSenderId: '714311873087', + projectId: 'nonsuckingshoppinglist', + authDomain: 'nonsuckingshoppinglist.firebaseapp.com', + databaseURL: 'https://nonsuckingshoppinglist.firebaseio.com', + storageBucket: 'nonsuckingshoppinglist.appspot.com', + measurementId: 'G-55C6JSXQK6', +}); + +// Necessary to receive background messages: +const messaging = firebase.messaging(); + +// Optional: +messaging.onBackgroundMessage((m) => { + console.log("onBackgroundMessage", m); +}); diff --git a/web/index.html b/web/index.html index 7e35a75..9b9c5d0 100644 --- a/web/index.html +++ b/web/index.html @@ -1,33 +1,80 @@ + - - - - - - - - - - - - - nssl - + example + - + - + diff --git a/web/sqlite3.wasm b/web/sqlite3.wasm new file mode 100644 index 0000000..faed541 Binary files /dev/null and b/web/sqlite3.wasm differ diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8b6d468..0000000 --- a/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void RegisterPlugins(flutter::PluginRegistry* registry) { -} diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake deleted file mode 100644 index 4d10c25..0000000 --- a/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc index 2d372d6..82af8a6 100644 --- a/windows/runner/Runner.rc +++ b/windows/runner/Runner.rc @@ -90,7 +90,7 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "de.susch19" "\0" - VALUE "FileDescription", "A new Flutter project." "\0" + VALUE "FileDescription", "NSSL" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" VALUE "InternalName", "nssl" "\0" VALUE "LegalCopyright", "Copyright (C) 2020 de.susch19. All rights reserved." "\0" diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico index c04e20c..dc94d2e 100644 Binary files a/windows/runner/resources/app_icon.ico and b/windows/runner/resources/app_icon.ico differ