diff --git a/android/app/build.gradle b/android/app/build.gradle index f8502be..c60a710 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -22,6 +22,9 @@ if (flutterVersionName == null) { } apply plugin: 'com.android.application' +// START: FlutterFire Configuration +apply plugin: 'com.google.gms.google-services' +// END: FlutterFire Configuration apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..5b2674c --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,46 @@ +{ + "project_info": { + "project_number": "655141330055", + "project_id": "gdev-annuaire", + "storage_bucket": "gdev-annuaire.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:655141330055:android:db10532c8a73cc9d1a2c22", + "android_client_info": { + "package_name": "com.example.gdev_annuaire" + } + }, + "oauth_client": [ + { + "client_id": "655141330055-sofj8l1em1q4ggn3eso91v1gnp128451.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAxFULgOFom_Dsx6AVmcZdHGetDvtjGzbk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "655141330055-sofj8l1em1q4ggn3eso91v1gnp128451.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "655141330055-e3uim74419qgvjlt7svqk763q758qfak.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.example.gdevAnnuaire" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 83ae220..9192654 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,6 +7,9 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.1.2' + // START: FlutterFire Configuration + classpath 'com.google.gms:google-services:4.3.10' + // END: FlutterFire Configuration classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/ios/firebase_app_id_file.json b/ios/firebase_app_id_file.json new file mode 100644 index 0000000..5e678d5 --- /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:655141330055:ios:82c8fec7ae27b2951a2c22", + "FIREBASE_PROJECT_ID": "gdev-annuaire", + "GCM_SENDER_ID": "655141330055" +} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..942320f --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,82 @@ +// 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: 'AIzaSyB6eFpKM-FX31cZPRPbNwlj6VCoc1G3SeU', + appId: '1:655141330055:web:b262dc3d240a32a81a2c22', + messagingSenderId: '655141330055', + projectId: 'gdev-annuaire', + authDomain: 'gdev-annuaire.firebaseapp.com', + storageBucket: 'gdev-annuaire.appspot.com', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyAxFULgOFom_Dsx6AVmcZdHGetDvtjGzbk', + appId: '1:655141330055:android:db10532c8a73cc9d1a2c22', + messagingSenderId: '655141330055', + projectId: 'gdev-annuaire', + storageBucket: 'gdev-annuaire.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyB6u7EpW4pvrR9gJGq4B7LrzesBV-sIq1I', + appId: '1:655141330055:ios:82c8fec7ae27b2951a2c22', + messagingSenderId: '655141330055', + projectId: 'gdev-annuaire', + storageBucket: 'gdev-annuaire.appspot.com', + iosClientId: '655141330055-e3uim74419qgvjlt7svqk763q758qfak.apps.googleusercontent.com', + iosBundleId: 'com.example.gdevAnnuaire', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyB6u7EpW4pvrR9gJGq4B7LrzesBV-sIq1I', + appId: '1:655141330055:ios:82c8fec7ae27b2951a2c22', + messagingSenderId: '655141330055', + projectId: 'gdev-annuaire', + storageBucket: 'gdev-annuaire.appspot.com', + iosClientId: '655141330055-e3uim74419qgvjlt7svqk763q758qfak.apps.googleusercontent.com', + iosBundleId: 'com.example.gdevAnnuaire', + ); +} diff --git a/lib/main.dart b/lib/main.dart index bcaf73b..cd0ac5b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,41 @@ +// ignore_for_file: avoid_print + +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:hive_flutter/hive_flutter.dart'; +// import 'package:hive_flutter/hive_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:truncate/truncate.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'firebase_options.dart'; +import 'package:firebase_auth/firebase_auth.dart'; -late Box addressBox; +// late Box addressBox; +late FirebaseFirestore db; +bool canAdd = false; List profiles = []; Future main() async { - await Hive.initFlutter(); - addressBox = await Hive.openBox("addressBox"); + WidgetsFlutterBinding.ensureInitialized(); + + // await Hive.initFlutter(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + db = FirebaseFirestore.instance; + // await eraseDB(); + await FirebaseAuth.instance.signInAnonymously(); + FirebaseAuth.instance.authStateChanges().listen((User? user) { + if (user == null) { + print('User is currently signed out!'); + } else { + print('User is signed in!'); + } + }); + + // addressBox = await Hive.openBox("addressBox"); // await addressBox.clear(); + runApp(const MyApp()); } @@ -45,19 +70,19 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { - profiles.clear(); - addressBox.toMap().forEach((key, value) { - profiles.add(value); - }); + // profiles.clear(); + // addressBox.toMap().forEach((key, value) { + // profiles.add(value); + // }); return Scaffold( backgroundColor: Colors.orange[200], - body: profileTiles(context, profiles, refresh), + body: profileTiles(context, refresh), ); } } -Widget profileTiles(BuildContext context, List profiles, refresh) { +Widget profileTiles(BuildContext context, refresh) { final double screenWidth = MediaQuery.of(context).size.width; int nbrColumn = 9; TextEditingController nameController = TextEditingController(); @@ -91,6 +116,10 @@ Widget profileTiles(BuildContext context, List profiles, refresh) { width: 150, child: TextField( controller: nameController, + onChanged: (a) { + canAddProfile(nameController.text, addressController.text); + // refresh(); + }, decoration: const InputDecoration( hintText: "Nom", ), @@ -101,6 +130,10 @@ Widget profileTiles(BuildContext context, List profiles, refresh) { width: 470, child: TextField( controller: addressController, + onChanged: (a) { + canAddProfile(nameController.text, addressController.text); + // refresh(); + }, decoration: const InputDecoration( hintText: "Adresse", ), @@ -110,39 +143,58 @@ Widget profileTiles(BuildContext context, List profiles, refresh) { IconButton( icon: const Icon(Icons.add_reaction_outlined), onPressed: (() async { - await addProfile(nameController.text, addressController.text); - refresh(); + if (canAdd) { + await addProfile(nameController.text, addressController.text); + refresh(); + canAdd = false; + } }), ), + const Spacer(), + const Text( + 'Annuaire ĞDev', + style: TextStyle(fontSize: 20), + ), + const SizedBox(width: 80), ], )), - SliverGrid.count( - key: const Key('listWallets'), - crossAxisCount: nbrColumn, - childAspectRatio: 1, - crossAxisSpacing: 0, - mainAxisSpacing: 0, - children: [ - for (String profile in profiles) - Container( - color: Colors.orange[200], - child: Padding( - padding: const EdgeInsets.all(10), - child: Container( - color: Colors.orange[100], + const SliverToBoxAdapter(child: SizedBox(height: 30)), + FutureBuilder( + future: getDbData(), + builder: (context, AsyncSnapshot? snapshot) { + List result = snapshot?.data ?? []; + result.sort(); + return SliverGrid.count( + key: const Key('listWallets'), + crossAxisCount: nbrColumn, + childAspectRatio: 1, + crossAxisSpacing: 0, + mainAxisSpacing: 0, + children: [ + for (String profile in result) + Container( + color: Colors.orange[200], child: Padding( padding: const EdgeInsets.all(10), - child: tile( - context, profile.split(':')[0], profile.split(':')[1]), + child: Container( + color: Colors.orange[100], + child: Padding( + padding: const EdgeInsets.all(10), + child: tile(context, profile.split(':')[0], + profile.split(':')[1], refresh), + ), + ), ), ), - ), - ), - ]), + ]); + }, + ), ]); } -Widget tile(BuildContext context, String name, String address) { +Widget tile(BuildContext context, String name, String address, refresh) { + int lastTap = DateTime.now().millisecondsSinceEpoch; + int consecutiveTaps = 0; return Container( color: Colors.orange[50], child: Padding( @@ -150,11 +202,34 @@ Widget tile(BuildContext context, String name, String address) { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text(name), + GestureDetector( + onTap: () { + int now = DateTime.now().millisecondsSinceEpoch; + if (now - lastTap < 400) { + consecutiveTaps++; + if (consecutiveTaps > 8) { + print("DELETE PROFILE"); + deleteAddress(address, refresh); + } + } else { + consecutiveTaps = 0; + } + lastTap = now; + }, + child: Text( + name, + style: + const TextStyle(fontWeight: FontWeight.w600, fontSize: 17), + ), + ), const SizedBox(height: 3), InkWell( onTap: () => snackCopyKey(context, address), - child: Text(getShortPubkey(address)), + child: Text( + getShortPubkey(address), + style: + const TextStyle(fontWeight: FontWeight.w500, fontSize: 17), + ), ), QrImageWidget( // gapless: false, @@ -188,5 +263,81 @@ snackCopyKey(BuildContext context, String address) { } Future addProfile(String name, String address) async { - await addressBox.add('$name:$address'); + final newProfile = {"name": name, "address": address}; + + db.collection("profiles").add(newProfile).then((DocumentReference doc) => + print('DocumentSnapshot added with ID: ${doc.id}')); +} + +Future getDbData() async { + await db.collection("profiles").get().then((event) { + profiles.clear(); + for (var doc in event.docs) { + final name = doc.data()['name']; + final address = doc.data()['address']; + profiles.add('$name:$address'); + print("${doc.id} => ${doc.data()}"); + } + }); + + return profiles; +} + +Future eraseDB() async { + var collection = FirebaseFirestore.instance.collection('profiles'); + var snapshots = await collection.get(); + for (var doc in snapshots.docs) { + await doc.reference.delete(); + } +} + +Future deleteAddress(String address, refresh) async { + await db.collection("profiles").get().then((event) async { + profiles.clear(); + for (var doc in event.docs) { + if (address == doc.data()['address']) { + await FirebaseFirestore.instance + .runTransaction((Transaction myTransaction) async { + myTransaction.delete(doc.reference); + }); + refresh(); + return; + } + } + // await doc.reference.delete(); + }); +} + +bool isAddress(address) { + final RegExp regExp = RegExp( + r'^[a-zA-Z0-9]+$', + caseSensitive: false, + multiLine: false, + ); + + if (regExp.hasMatch(address) == true && + address.length > 45 && + address.length < 52) { + // canAdd = true; + + return true; + } else { + // canAdd = false; + return false; + } +} + +bool canAddProfile(String name, String address) { + if (name == '' || !isAddress(address)) { + return false; + } + + for (var profile in profiles) { + final nameStored = profile.split(':')[0]; + final addressStored = profile.split(':')[1]; + if (address == addressStored || name == nameStored) return false; + } + + canAdd = true; + return true; } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 0d56f51..e27042d 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,14 @@ import FlutterMacOS import Foundation +import cloud_firestore +import firebase_auth +import firebase_core import path_provider_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/macos/firebase_app_id_file.json b/macos/firebase_app_id_file.json new file mode 100644 index 0000000..5e678d5 --- /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:655141330055:ios:82c8fec7ae27b2951a2c22", + "FIREBASE_PROJECT_ID": "gdev-annuaire", + "GCM_SENDER_ID": "655141330055" +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 9c1caad..4b567fa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -36,6 +36,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.17" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.5.7" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.16" collection: dependency: transitive description: @@ -78,6 +99,48 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.2" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.19" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "6.2.7" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.16" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "1.17.1" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.4" flutter: dependency: "direct main" description: flutter @@ -95,6 +158,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" hive: dependency: "direct main" description: @@ -109,6 +177,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" lints: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7914661..f60b6a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,6 +19,9 @@ dependencies: hive_flutter: ^1.1.0 cupertino_icons: ^1.0.2 + firebase_core: ^1.17.1 + cloud_firestore: ^3.1.17 + firebase_auth: ^3.3.19 dev_dependencies: flutter_test: