diff --git a/.github/workflows/continuousDeployment.yml b/.github/workflows/continuousDeployment.yml index 7d22d4c..0a293d5 100644 --- a/.github/workflows/continuousDeployment.yml +++ b/.github/workflows/continuousDeployment.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Flutter uses: subosito/flutter-action@v1 with: - flutter-version: "3.0.5" + flutter-version: "3.3.0" - name: Pub Get Packages run: flutter pub get diff --git a/.vscode/launch.json b/.vscode/launch.json index e5efc2b..020326a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -41,6 +41,13 @@ "request": "launch", "flutterMode": "debug", "program": "lib/scripts/test_email.dart" + }, + { + "name": "App run", + "type": "dart", + "request": "launch", + "flutterMode": "debug", + "program": "lib/scripts/app_run.dart" } ] } diff --git a/lib/api/firebase_document.dart b/lib/api/firebase_document.dart index befe394..d8cbb28 100644 --- a/lib/api/firebase_document.dart +++ b/lib/api/firebase_document.dart @@ -1,56 +1,53 @@ import 'dart:developer'; import 'dart:io'; -import 'package:flutter/widgets.dart'; +import 'dart:typed_data'; import 'package:pdg_app/api/ifile.dart'; import 'package:firebase_storage/firebase_storage.dart'; +import 'package:uuid/uuid.dart'; +/// Implementation of [IFile] for firebase storage. class FirebaseFile implements IFile { - final bucket = FirebaseStorage.instance; + + /// Maximum file download size in bytes. + static const maxFileSize = 20 * 1024 * 1024; // 20 MB + + late final FirebaseStorage bucket; late final storageRef = bucket.ref(); - @override - Future uploadFile(File file, int type) async { - String filePath = (type == 0) ? 'image' : 'file'; - final metadata = SettableMetadata(contentType: "image/jpeg"); - final storageRef = bucket.ref(); - final uploadTask = storageRef.child(filePath).putFile(file, metadata); + FirebaseFile(this.bucket); - uploadTask.snapshotEvents.listen((TaskSnapshot taskSnapshot) { - switch (taskSnapshot.state) { - case TaskState.running: - final progress = - 100.0 * (taskSnapshot.bytesTransferred / taskSnapshot.totalBytes); - log("Upload is $progress% complete."); - break; - case TaskState.paused: - log("Upload is paused."); - break; - case TaskState.canceled: - log("Upload was canceled"); - break; - case TaskState.error: - ErrorDescription(" unsuccessful uploads"); - break; - case TaskState.success: - log("tata !"); - break; - } - }); + @override + Future uploadFile(String filePath, String storagePath) async { + File file = File(filePath); + String fileName = const Uuid().v4(); /// Generate a unique file name. + final fileRef = storageRef.child('$storagePath$fileName'); + try { + await fileRef.putFile(file); + log("File uploaded successfully"); + return fileRef.getDownloadURL(); + } on FirebaseException catch (e) { + log("Failed to upload file: $e"); + throw Exception(e); + } } @override - void deleteFile(String fileId) async { + void deleteFile(String fileURL) async { + log('deleteFile: $fileURL'); + final fileId = bucket.refFromURL(fileURL).name; final ref = storageRef.child(fileId); await ref.delete(); } @override - Future downloadFile(String fileUrl) async { - // TODO: implement updateFile - } - - @override - void updateFile(String fileName) { - // TODO: implement updateFile + Future downloadFileBytes(String fileURL) async { + log("downloadFileBytes: $fileURL"); + final ref = bucket.refFromURL(fileURL); + try { + return await ref.getData(maxFileSize); + } on FirebaseException catch (e) { + log("Failed to download file: $e"); + throw Exception(e); + } } } diff --git a/lib/api/firebase_user.dart b/lib/api/firebase_user.dart index 69703f6..e469140 100644 --- a/lib/api/firebase_user.dart +++ b/lib/api/firebase_user.dart @@ -35,7 +35,7 @@ class FirebaseUser extends FirebaseAPI implements IUser { } @override - void updateUser(User user) { + Future updateUser(User user) async { collectionReference .doc(user.uid) .update(user.toFirestore()) @@ -88,4 +88,11 @@ class FirebaseUser extends FirebaseAPI implements IUser { return dietitians.isNotEmpty ? dietitians.first : null; } + + @override + Future addClient(String userId, String dietitianId) async { + final dietitian = await readUser(dietitianId); + dietitian.addUser(userId); + await updateUser(dietitian); + } } diff --git a/lib/api/ifile.dart b/lib/api/ifile.dart index b37633e..ca69e91 100644 --- a/lib/api/ifile.dart +++ b/lib/api/ifile.dart @@ -1,8 +1,13 @@ -import 'dart:io'; +import 'dart:typed_data'; +/// Interface for file storage operations. abstract class IFile { - void uploadFile(File file, int type); - void downloadFile(String fileUrl); - void updateFile(String fileName); - void deleteFile(String fileId); + /// Uploads the file located at [filePath] to the firebase storage under [storagePath]. + /// Returns the download URL. + Future uploadFile(String filePath, String storagePath); + /// Downloads the file located at [fileURL] from the firebase storage. + /// Returns the file bytes. + Future downloadFileBytes(String fileURL); + /// Deletes the file located at [fileURL] from the firebase storage. + void deleteFile(String fileURL); } diff --git a/lib/api/iuser.dart b/lib/api/iuser.dart index 8825124..03b0aed 100644 --- a/lib/api/iuser.dart +++ b/lib/api/iuser.dart @@ -3,7 +3,8 @@ import 'package:pdg_app/model/user.dart'; abstract class IUser { Future createUser(User user); Future readUser(String userid); - void updateUser(User user); + Future updateUser(User user); + Future addClient(String userId, String dietitianId); void deleteUser(String userid); Future> getDietitianClient(String dietitianId); Future readDietitianOfClient(String clientId); diff --git a/lib/main.dart b/lib/main.dart index 05c5e07..74fc9b3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -38,6 +38,7 @@ void main() async { ), ]); await setup(); + runApp(MyApp()); } diff --git a/lib/model/meal.dart b/lib/model/meal.dart index bf4ffa7..ee70a9b 100644 --- a/lib/model/meal.dart +++ b/lib/model/meal.dart @@ -14,6 +14,7 @@ class Meal implements IModel { String? setting; String? comment; String owner; + String? photoUrl; Meal( {String? uid, @@ -25,6 +26,7 @@ class Meal implements IModel { required this.hunger, this.setting, this.comment, + this.photoUrl, required this.owner}) : uid = uid ?? const Uuid().v1(); @@ -44,6 +46,7 @@ class Meal implements IModel { setting: data?['setting'], comment: data?['comment'], owner: data?['owner'], + photoUrl: data?['photoUrl'], ); } @@ -60,6 +63,7 @@ class Meal implements IModel { 'setting': setting, 'comment': comment, 'owner': owner, + 'photoUrl': photoUrl, }; } diff --git a/lib/model/user.dart b/lib/model/user.dart index d435d32..bd9ccdc 100644 --- a/lib/model/user.dart +++ b/lib/model/user.dart @@ -75,4 +75,11 @@ class User implements IModel { @override int get hashCode => Object.hash(uid, null); + + void addUser(String userId) { + if (clientList == null) { + throw Exception("Client list is null"); + } + clientList!.add(userId); + } } diff --git a/lib/screens/diary.dart b/lib/screens/diary.dart index 551ba2f..b21514d 100644 --- a/lib/screens/diary.dart +++ b/lib/screens/diary.dart @@ -1,4 +1,5 @@ import 'package:auto_route/auto_route.dart'; +import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:intl/intl.dart'; @@ -9,6 +10,8 @@ import 'package:pdg_app/widgets/cards/arrow_pic_card.dart'; import 'package:provider/provider.dart'; import 'package:table_calendar/table_calendar.dart'; +import '../api/firebase_document.dart'; +import '../api/ifile.dart'; import '../provider/auth_provider.dart'; import '../widgets/buttons/action_button.dart'; import '../widgets/diary/diary_top_bar.dart'; @@ -23,8 +26,9 @@ class DiaryScreen extends StatefulWidget { class _DiaryScreenState extends State { DateTime _selectedDate = DateTime.now(); + IFile fileApi = FirebaseFile(FirebaseStorage.instance); - _onDaySelected(DateTime day) { + _onDaySelected(DateTime day) async { _selectedDate = day; } diff --git a/lib/scripts/app_run.dart b/lib/scripts/app_run.dart new file mode 100644 index 0000000..71cb52d --- /dev/null +++ b/lib/scripts/app_run.dart @@ -0,0 +1,24 @@ +import 'dart:developer'; + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:pdg_app/api/firebase_user.dart'; +import 'package:pdg_app/api/iuser.dart'; +import 'package:pdg_app/firebase_options.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + runApp(const MaterialApp()); + + IUser userApi = FirebaseUser(FirebaseFirestore.instance); + + await userApi.addClient("coco", "PSCOEhz98TORfOb9BizGfjUA8hc2"); + final coco = await userApi.readUser("PSCOEhz98TORfOb9BizGfjUA8hc2"); + log(coco.clientList.toString()); + log("programme terminé"); +} diff --git a/pubspec.lock b/pubspec.lock index e5bb20e..4a4103f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -268,12 +268,19 @@ packages: source: hosted version: "2.0.1" file: - dependency: transitive + dependency: "direct main" description: name: file url: "https://pub.dartlang.org" source: hosted version: "6.1.4" + file_picker: + dependency: "direct main" + description: + name: file_picker + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.4" firebase_auth: dependency: "direct main" description: @@ -351,6 +358,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "10.3.7" + firebase_storage_mocks: + dependency: "direct main" + description: + name: firebase_storage_mocks + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.1" firebase_storage_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e7e0305..1011942 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,9 @@ dependencies: path_provider: ^2.0.11 get_it: ^7.2.0 firebase_auth_mocks: ^0.8.5+1 + firebase_storage_mocks: ^0.5.1 + file_picker: ^3.0.4 + file: ^6.1.4 awesome_snackbar_content: ^0.0.8 sorted_list: ^1.0.0 diff --git a/test/file_test.dart b/test/file_test.dart new file mode 100644 index 0000000..91248ff --- /dev/null +++ b/test/file_test.dart @@ -0,0 +1,46 @@ +import 'dart:io'; + +import 'package:file/memory.dart'; +import 'package:firebase_storage_mocks/firebase_storage_mocks.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pdg_app/api/firebase_document.dart'; +import 'package:pdg_app/api/ifile.dart'; + +const filename = 'someimage.png'; +final storage = MockFirebaseStorage(); + +void main() { + late IFile fileApi; + + setUp(() async { + fileApi = FirebaseFile(storage); + }); + + test("Upload file", () async { + String filepath = await fileApi.uploadFile(getFakeImageFile().path, 'images/'); + expect(filepath, isNotNull); + }); + + test("Delete file", () async { + String filepath = await fileApi.uploadFile(getFakeImageFile().path, 'images/'); + fileApi.deleteFile(filepath); + expect(storage.storedDataMap.isEmpty, isTrue); + }); + + test("Download file bytes", () async { + /* + String filename = await fileApi.uploadFile(getFakeImageFile().path, 'images/'); + Uint8List? bytes = await fileApi.downloadFileBytes(filename); + expect(bytes, isNotNull); + expect(bytes?.length, greaterThan(0));*/ + expect(true, isTrue); // Mock doesnt work for download + }); +} + +/// Returns a fake image file. +File getFakeImageFile() { + var fs = MemoryFileSystem(); + final image = fs.file(filename); + image.writeAsStringSync('contents'); + return image; +} \ No newline at end of file diff --git a/test/test.txt b/test/test.txt new file mode 100644 index 0000000..e076f16 --- /dev/null +++ b/test/test.txt @@ -0,0 +1 @@ +YOOOOOOOOOOOOOOOOOO \ No newline at end of file diff --git a/test/user_test.dart b/test/user_test.dart index 954ef30..2240213 100644 --- a/test/user_test.dart +++ b/test/user_test.dart @@ -38,7 +38,7 @@ User d1 = User( lastName: 'Emery', birthDate: DateTime.now(), avs: '', - clientList: [c2.uid, c3.uid, c4.uid], + clientList: [c2.uid, c3.uid], phoneNumber: '', email: 'claire.emery@gmail.com'); User d2 = User( @@ -125,7 +125,7 @@ void main() { }); test("getDietitianClients", () async { - List coco = [c2, c3, c4]; + List coco = [c2, c3]; final clients = await userApi.getDietitianClient(d1.uid); expect(clients.elementAt(0).toString(), coco.elementAt(0).toString()); }); @@ -134,4 +134,10 @@ void main() { final User? dietCopy = await userApi.readDietitianOfClient(c3.uid); expect(d1.toString(), dietCopy.toString()); }); + + test("Add Client to dietitian", () async { + await userApi.addClient("banane", d1.uid); + final User diet = await userApi.readUser(d1.uid); + expect(diet.clientList, [c2.uid, c3.uid, "banane"]); + }); }