diff --git a/.drone.yml b/.drone.yml index 5208750..bad0485 100644 --- a/.drone.yml +++ b/.drone.yml @@ -10,8 +10,14 @@ trigger: steps: - name: build-apk - image: ghcr.io/cirruslabs/flutter:3.13.9 + image: ghcr.io/cirruslabs/flutter:3.16.3 commands: + - flutter clean + - flutter pub cache repair + - flutter pub get + - ls /drone/src/.dart_tool/ + - dart run build_runner clean + - dart run build_runner build --delete-conflicting-outputs - flutter build apk - sfm_apk=sfm_$(date +"%Y_%m_%d_%H_%M_%S").apk - cp ./build/app/outputs/flutter-apk/app-release.apk $sfm_apk @@ -19,7 +25,7 @@ steps: - cat upload.json | cut -d '"' -f 12 - name: build-web - image: ghcr.io/cirruslabs/flutter:3.13.9 + image: ghcr.io/cirruslabs/flutter:3.16.3 environment: FIREBASE_TOKEN: from_secret: firebase_token @@ -41,4 +47,4 @@ steps: - export PATH=$SONAR_SCANNER_HOME/bin:$PATH - export SONAR_SCANNER_OPTS="-server" - sonar-scanner -D sonar.projectKey=SmartFit_Mobile -D sonar.sources=./lib -D sonar.host.url=https://codefirst.iut.uca.fr/sonar -D sonar.login=$${SONAR_TOKEN} - depends_on: [ build-apk, build-web ] + depends_on: [ build-apk,build-web ] diff --git a/.gitignore b/.gitignore index db7fc69..08b1475 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ # Firebase .firebase/ +# ObjectBox +objectbox-model.json +objectbox.g.dart + # IntelliJ related *.iml *.ipr diff --git a/.metadata b/.metadata index 3408c2c..b060b6a 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "d211f42860350d914a5ad8102f9ec32764dc6d06" + revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 - base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - platform: linux - create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 - base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a # User provided section diff --git a/assets/img/bpm2-icon.svg b/assets/img/bpm2-icon.svg new file mode 100644 index 0000000..5f0f5be --- /dev/null +++ b/assets/img/bpm2-icon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/img/distance2-icon.svg b/assets/img/distance2-icon.svg new file mode 100644 index 0000000..8791f12 --- /dev/null +++ b/assets/img/distance2-icon.svg @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/img/local_save.png b/assets/img/local_save.png new file mode 100644 index 0000000..aec5b70 Binary files /dev/null and b/assets/img/local_save.png differ diff --git a/assets/img/prediction.svg b/assets/img/prediction.svg new file mode 100644 index 0000000..0b78190 --- /dev/null +++ b/assets/img/prediction.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/prediction_selected.svg b/assets/img/prediction_selected.svg new file mode 100644 index 0000000..e8b8e77 --- /dev/null +++ b/assets/img/prediction_selected.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/img/time-icon2.svg b/assets/img/time-icon2.svg new file mode 100644 index 0000000..1d793f3 --- /dev/null +++ b/assets/img/time-icon2.svg @@ -0,0 +1,703 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/img/vitesse2-icon.svg b/assets/img/vitesse2-icon.svg new file mode 100644 index 0000000..ec7f96c --- /dev/null +++ b/assets/img/vitesse2-icon.svg @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/img/volumes.svg b/assets/img/volumes.svg new file mode 100644 index 0000000..031522a --- /dev/null +++ b/assets/img/volumes.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/img/volumes_selected.svg b/assets/img/volumes_selected.svg new file mode 100644 index 0000000..452dd85 --- /dev/null +++ b/assets/img/volumes_selected.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lib/common_widget/button/tab_button.dart b/lib/common_widget/button/tab_button.dart index de8939d..b1b45e6 100644 --- a/lib/common_widget/button/tab_button.dart +++ b/lib/common_widget/button/tab_button.dart @@ -20,19 +20,19 @@ class TabButton extends StatelessWidget { onTap: onTap, child: Column(mainAxisSize: MainAxisSize.min, children: [ SvgPicture.asset(isActive ? selectIcon : icon, - width: 28, height: 28, fit: BoxFit.fitWidth), + width: 15, height: 25, fit: BoxFit.fitWidth), SizedBox( - height: isActive ? 12: 8, + height: isActive ? 10: 6, ), if(isActive) Container( - width: 4, - height: 4, + width: 3, + height: 3, decoration: BoxDecoration( gradient: LinearGradient( colors: TColor.secondaryG, ), - borderRadius: BorderRadius.circular(2)), + borderRadius: BorderRadius.circular(1.5)), ) ]), ); diff --git a/lib/common_widget/container/list/list_activity_widget.dart b/lib/common_widget/container/list/list_activity_widget.dart index 8d85253..e815b60 100644 --- a/lib/common_widget/container/list/list_activity_widget.dart +++ b/lib/common_widget/container/list/list_activity_widget.dart @@ -1,10 +1,15 @@ +import 'package:smartfit_app_mobile/modele/activity_saver.dart'; +import 'package:smartfit_app_mobile/modele/helper.dart'; +import 'package:smartfit_app_mobile/main.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:smartfit_app_mobile/modele/api/api_wrapper.dart'; import 'package:smartfit_app_mobile/common_widget/container/workout_row/workout_row_generic.dart'; import 'package:smartfit_app_mobile/common_widget/container/workout_row/workout_row_walking.dart'; import 'package:smartfit_app_mobile/modele/activity.dart'; import 'package:smartfit_app_mobile/modele/manager_file.dart'; import 'package:smartfit_app_mobile/modele/user.dart'; +import 'package:smartfit_app_mobile/modele/utile/info_message.dart'; import 'package:smartfit_app_mobile/modele/utile/list_activity/list_activity_utile.dart'; import 'package:tuple/tuple.dart'; @@ -16,12 +21,14 @@ class ListActivityWidget extends StatefulWidget { } class _ListActivityWidget extends State { + final ApiWrapper api = ApiWrapper(); + final InfoMessage infoManager = InfoMessage(); final ListActivityUtile _utile = ListActivityUtile(); final ManagerFile managerFile = ManagerFile(); @override Widget build(BuildContext context) { - void onClick(ActivityOfUser activityObj) async { + Future onClick(ActivityOfUser activityObj) async { if (!Provider.of(context, listen: false) .managerSelectedActivity .fileNotSelected(activityObj.fileUuid)) { @@ -33,19 +40,20 @@ class _ListActivityWidget extends State { } Tuple2 result = - await _utile.getContentActivity(context, activityObj); + await _utile.getContentActivity(context, activityObj, infoManager); if (!result.item1) { return; } + // TODO: Hein? Provider.of(context, listen: false).removeActivity(activityObj); Provider.of(context, listen: false).insertActivity(0, activityObj); } - void onDelete(ActivityOfUser activityObj) async { - if (await _utile.deleteFileOnBDD( - Provider.of(context, listen: false).token, - activityObj.fileUuid)) { + // TODO: Understand :( + Future onDelete(ActivityOfUser activityObj) async { + if (await api.deleteFile(Provider.of(context, listen: false).token, + activityObj.fileUuid, infoManager)) { if (!Provider.of(context, listen: false) .managerSelectedActivity .fileNotSelected(activityObj.fileUuid)) { @@ -53,6 +61,11 @@ class _ListActivityWidget extends State { .managerSelectedActivity .removeSelectedActivity(activityObj.fileUuid); } + if (!Helper.isPlatformWeb()) { + ActivitySaver actSaver = await ActivitySaver.create(); + actSaver.deleteActivity(activityObj.fileUuid); + localDB.removeActivity(activityObj.fileUuid); + } Provider.of(context, listen: false).removeActivity(activityObj); } } @@ -65,41 +78,62 @@ class _ListActivityWidget extends State { return Material( color: Colors.transparent, - child: ListView.builder( - padding: EdgeInsets.zero, - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: Provider.of(context, listen: true).listActivity.length, - itemBuilder: (context, index) { - ActivityOfUser activityObj = - Provider.of(context, listen: true).listActivity[index]; - Map activityMap; - // -- Si categorie == marche - if (activityObj.category == managerFile.marche) { - activityMap = activityObj.toMapWalking(); - return InkWell( - onTap: () {}, - child: WorkoutRowWalking( - wObj: activityMap, - onDelete: () => onDelete(activityObj), - onClick: () => onClick(activityObj), - isSelected: isSelected(activityObj), - ), - ); - } else { - // -- Default -- // - activityMap = activityObj.toMapGeneric(); - return InkWell( - onTap: () {}, - child: WorkoutRowGeneric( - wObj: activityMap, - onDelete: () => onDelete(activityObj), - onClick: () => onClick(activityObj), - isSelected: isSelected(activityObj), - ), - ); - } - }, + child: Column( + children: [ + Visibility( + visible: infoManager.isVisible, + child: Text(infoManager.message, + style: TextStyle(color: infoManager.messageColor))), + ListView.builder( + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: + Provider.of(context, listen: true).listActivity.length, + itemBuilder: (context, index) { + ActivityOfUser activityObj = + Provider.of(context, listen: true).listActivity[index]; + Map activityMap; + // -- Si categorie == marche + if (activityObj.category == managerFile.marche) { + activityMap = activityObj.toMapWalking(); + return InkWell( + onTap: () {}, + child: WorkoutRowWalking( + wObj: activityMap, + onDelete: () async { + await onDelete(activityObj); + setState(() {}); + }, + onClick: () async { + await onClick(activityObj); + setState(() {}); + }, + isSelected: isSelected(activityObj), + ), + ); + } else { + // -- Default -- // + activityMap = activityObj.toMapGeneric(); + return InkWell( + onTap: () {}, + child: WorkoutRowGeneric( + wObj: activityMap, + onDelete: () async { + await onDelete(activityObj); + setState(() {}); + }, + onClick: () async { + await onClick(activityObj); + setState(() {}); + }, + isSelected: isSelected(activityObj), + ), + ); + } + }, + ), + ], ), ); } diff --git a/lib/common_widget/container/list/volumes_list.dart b/lib/common_widget/container/list/volumes_list.dart index 9c61566..b228b9a 100644 --- a/lib/common_widget/container/list/volumes_list.dart +++ b/lib/common_widget/container/list/volumes_list.dart @@ -10,6 +10,7 @@ class VolumesList extends StatelessWidget { Widget build(BuildContext context) { var media = MediaQuery.of(context).size; + // TODO: True message with variables and context aware if (volume["nbActivity"] == 0) { return const Text("Aucune activité ces x jours/mois/années"); } diff --git a/lib/common_widget/container/profile/profile_switch.dart b/lib/common_widget/container/profile/profile_switch.dart new file mode 100644 index 0000000..ccdbdff --- /dev/null +++ b/lib/common_widget/container/profile/profile_switch.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:smartfit_app_mobile/common/colo_extension.dart'; +import 'package:smartfit_app_mobile/main.dart'; + +class ProfileSwitch extends StatefulWidget { + final String title; + final String description; + final String iconFilename; + + const ProfileSwitch(this.title, this.description, this.iconFilename, + {super.key}); + + @override + State createState() => _ProfileSwitchState(); +} + +class _ProfileSwitchState extends State { + bool switchValue = localDB.getSaveLocally(); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + decoration: BoxDecoration( + color: TColor.white, + borderRadius: BorderRadius.circular(15), + boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 2)]), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + widget.title, + style: TextStyle( + color: TColor.black, + fontSize: 16, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox( + height: 8, + ), + SizedBox( + height: 30, + child: + Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ + Image.asset("assets/img/${widget.iconFilename}", + height: 28, width: 28, fit: BoxFit.contain), + const SizedBox( + width: 15, + ), + Expanded( + child: Text( + widget.description, + style: TextStyle( + color: TColor.black, + fontSize: 12, + ), + ), + ), + Switch( + value: switchValue, + activeColor: Colors.orange, + onChanged: (bool value) { + setState(() { + switchValue = value; + localDB.setSaveLocally(switchValue); + }); + }), + ])) + ])); + } +} diff --git a/lib/common_widget/container/workout_row/workout_row.dart b/lib/common_widget/container/workout_row/workout_row.dart new file mode 100644 index 0000000..491809d --- /dev/null +++ b/lib/common_widget/container/workout_row/workout_row.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:simple_animation_progress_bar/simple_animation_progress_bar.dart'; +import 'package:smartfit_app_mobile/common/colo_extension.dart'; + +class WorkoutRow extends StatelessWidget { + final Map wObj; + const WorkoutRow({super.key, required this.wObj}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 2), + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 15), + decoration: BoxDecoration( + color: TColor.white, + borderRadius: BorderRadius.circular(20), + boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 2)]), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(30), + child: SvgPicture.asset( + wObj["image"].toString(), + width: 60, + height: 60, + fit: BoxFit.cover, + ), + ), + + const SizedBox(width: 15,), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + wObj["name"].toString(), + style: TextStyle( + color: TColor.black, + fontSize: 15, + fontWeight: FontWeight.bold), + ), + + Text( + "${ wObj["value"].toString()}", + style: TextStyle( + color: TColor.gray, + fontSize: 12,), + ), + + const SizedBox(height: 4,), + ], + )), + + ], + )); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index f2f81e1..d14e769 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,27 @@ +import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:smartfit_app_mobile/modele/local_db/db_impl.dart'; +import 'package:smartfit_app_mobile/modele/local_db/get_web_db.dart' + if (dart.library.io) 'package:smartfit_app_mobile/modele/local_db/get_native_db.dart'; import 'package:provider/provider.dart'; import 'package:smartfit_app_mobile/modele/user.dart'; import 'package:smartfit_app_mobile/common/colo_extension.dart'; import 'package:smartfit_app_mobile/view/login/signup_view.dart'; +import 'package:smartfit_app_mobile/view/main_tab/main_tab_view.dart'; + +late DbImpl localDB; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + if (!kIsWeb) { + DbImpl tmp = getDbImpl(); + localDB = await tmp.create(); + await localDB.init(); + localDB.initConfig(); + } -void main() { runApp(ChangeNotifierProvider( create: (context) => User(), child: const MyApp())); } @@ -14,6 +31,26 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { + Widget viewToDisplay = const SignUpView(); + + // Skip sign-up + fill provider if user already connected + if (!kIsWeb && localDB.hasUser()) { + final User user = localDB.getUser(); + final userActivities = localDB.getAllActivities(); + + context.watch().username = user.username; + context.watch().email = user.email; + context.watch().token = user.token; + context.watch().listActivity = userActivities; + + stdout.write("===== USER =====\n"); + stdout.write("Username: ${user.username}\n"); + stdout.write("Email: ${user.email}\n"); + stdout.write("Token: ${user.token}\n"); + + viewToDisplay = const MainTabView(); + } + return MaterialApp( title: 'SmartFit', debugShowCheckedModeBanner: false, @@ -35,9 +72,7 @@ class MyApp extends StatelessWidget { // tested with just a hot reload. primaryColor: TColor.primaryColor1, fontFamily: "Poppins"), - home: const SignUpView(), - //home: const TestPage(), - //home: const ProfileView(), + home: viewToDisplay, ); } } diff --git a/lib/modele/activity_saver.dart b/lib/modele/activity_saver.dart new file mode 100644 index 0000000..529e476 --- /dev/null +++ b/lib/modele/activity_saver.dart @@ -0,0 +1,43 @@ +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:path/path.dart' as p; +import "package:path_provider/path_provider.dart"; +import 'package:smartfit_app_mobile/main.dart'; + +class ActivitySaver { + String saveDirectory = "activities"; + late final Directory applicationDocumentsDir; + + ActivitySaver._create(this.applicationDocumentsDir); + + Uint8List getActivity(String uuid) { + String filename = localDB.getActivityFilenameByUuid(uuid); + + final file = + File(p.join(applicationDocumentsDir.path, saveDirectory, filename)); + return file.readAsBytesSync(); + } + + Future saveActivity(Uint8List activityFile, String filename) async { + stdout.write("Creating activity file...\n"); + final file = await File( + p.join(applicationDocumentsDir.path, saveDirectory, filename)) + .create(recursive: true); // To create dir if not exists + file.writeAsBytesSync(activityFile); + stdout.write("Activity file created\n"); + } + + void deleteActivity(String uuid) { + String filename = localDB.getActivityFilenameByUuid(uuid); + final file = + File(p.join(applicationDocumentsDir.path, saveDirectory, filename)); + file.deleteSync(); + } + + static Future create() async { + stdout.write("Activity Saver: Created\n"); + final appDir = await getApplicationDocumentsDirectory(); + return ActivitySaver._create(appDir); + } +} diff --git a/lib/modele/api/api_wrapper.dart b/lib/modele/api/api_wrapper.dart index 67174a1..48ee173 100644 --- a/lib/modele/api/api_wrapper.dart +++ b/lib/modele/api/api_wrapper.dart @@ -1,22 +1,107 @@ +import 'package:flutter/foundation.dart'; +import 'package:smartfit_app_mobile/modele/activity_info/activity_info.dart'; import 'package:smartfit_app_mobile/modele/api/i_data_strategy.dart'; import 'package:smartfit_app_mobile/modele/api/request_api.dart'; +import 'package:smartfit_app_mobile/modele/local_db/request_local.dart'; import 'package:smartfit_app_mobile/modele/utile/info_message.dart'; import 'package:email_validator/email_validator.dart'; import 'package:tuple/tuple.dart'; import 'dart:convert'; +import 'dart:io'; import 'package:crypto/crypto.dart'; +import 'package:smartfit_app_mobile/main.dart'; +import 'package:http/http.dart' as http; class ApiWrapper { - IDataStrategy api = RequestApi(); + late IDataStrategy api; + String noConnectionMessage = + "It seems like you are lost far away in the universe, no connection found :)"; - Future modifyUserInfo(String infoToModify, String value, String token, + // HELPERS + // TODO: Change check online for flutterWeb + Future isOnline() async { + try { + final http.Response res = await http + .head(Uri.https("example.com")) + .timeout(const Duration(seconds: 5)); + + if (res.statusCode == 200) { + return true; + } + } catch (_) { + return false; + } + return true; + } + + Future init() async { + // TODO: Fait à la pisse en despi (je voulais juste dormir) + if (kIsWeb) { + api = RequestApi(); + return; + } + + if (await isOnline()) { + api = RequestApi(); + } else if (!kIsWeb && localDB.getSaveLocally()) { + api = RequestLocal(); + } else { + api = RequestApi(); + } + } + + bool handleOffline(InfoMessage infoManager) { + if (api is RequestLocal) { + infoManager.displayMessage(noConnectionMessage, true); + return true; + } + return false; + } + + // BOTH (ONLINE + OFFLINE) + Future getUserInfo(String token) async { + await init(); + Tuple2 res = await api.getInfoUser(token); + + return res; + } + + Future getFile( + String token, String fileUuid, InfoMessage infoManager) async { + await init(); + Tuple2 res = await api.getFile(token, fileUuid); + + if (!res.item1) { + infoManager.displayMessage(noConnectionMessage, true); + } + + return res; + } + + Future getFiles(String token, InfoMessage infoManager) async { + await init(); + Tuple2 res = await api.getFiles(token); + + if (!res.item1) { + infoManager.displayMessage(noConnectionMessage, true); + } + + return res; + } + + // ONLINE + Future updateUserInfo(String infoToModify, String value, String token, InfoMessage infoManager) async { + await init(); + if (handleOffline(infoManager)) return false; + if (infoToModify == 'email' && EmailValidator.validate(value) || infoToModify == 'password' || infoToModify == 'username') { Tuple2 res = await api.modifAttribut(token, infoToModify, value); - if (res.item1 == true) { + + if (res.item1) { infoManager.displayMessage( "${infoToModify.capitalize()} modified succesfully !", false); return true; @@ -34,17 +119,79 @@ class ApiWrapper { Future> login( String password, String email, InfoMessage infoManager) async { + await init(); + if (handleOffline(infoManager)) return const Tuple2(false, "offline"); + String hash = sha256.convert(utf8.encode(password)).toString(); Tuple2 res = await api.connexion(email, hash); if (res.item1) { - return Tuple2(true, res.item2); // return token + return Tuple2(true, res.item2); } else { infoManager.displayMessage( "Authentification failed! Enter your actual password carefully.", true); return const Tuple2(false, "An error occured during connexion!"); - } // need to be better + } + } + + Future> deleteUser( + String token, InfoMessage infoManager) async { + await init(); + if (handleOffline(infoManager)) return const Tuple2(false, "offline"); + + Tuple2 res = await api.deleteUser(token); + + return res; + } + + Future> createUser(String email, String hash, + String username, InfoMessage infoManager) async { + await init(); + if (handleOffline(infoManager)) return const Tuple2(false, "offline"); + + Tuple2 res = await api.postUser(email, hash, username); + + return res; + } + + Future> uploadFile( + String token, File file, InfoMessage infoManager) async { + await init(); + if (handleOffline(infoManager)) return const Tuple2(false, "offline"); + + Tuple2 res = await api.uploadFile(token, file); + return res; + } + + Future> uploadFileByte( + String token, + Uint8List contentFile, + String filename, + String category, + DateTime date, + ActivityInfo activityInfo, + InfoMessage infoManager) async { + await init(); + if (handleOffline(infoManager)) return const Tuple2(false, "offline"); + + Tuple2 res = await api.uploadFileByte( + token, contentFile, filename, category, date, activityInfo); + if (!res.item1) infoManager.displayMessage(noConnectionMessage, true); + + //stdout.write("uploadFileByte: ${res.item1}\n"); + return res; + } + + Future deleteFile( + String token, String fileUuid, InfoMessage infoManager) async { + await init(); + if (handleOffline(infoManager)) return false; + + bool res = await api.deleteFile(token, fileUuid); + if (!res) infoManager.displayMessage(noConnectionMessage, true); + + return res; } } diff --git a/lib/modele/api/i_data_strategy.dart b/lib/modele/api/i_data_strategy.dart index e4f1f51..54ea65d 100644 --- a/lib/modele/api/i_data_strategy.dart +++ b/lib/modele/api/i_data_strategy.dart @@ -15,11 +15,13 @@ abstract class IDataStrategy { // Get Token validate Future> connexion(String email, String hash); - // Get all files for user + // Get all files for user (LOCAL OK) Future getFiles(String token); // Upload file on BDD Future> uploadFile(String token, File file); + + // Upload file as bytes Future> uploadFileByte( String token, Uint8List contentFile, @@ -28,21 +30,16 @@ abstract class IDataStrategy { DateTime date, ActivityInfo activityInfo); - // Get one file by id + // Get one file by id (LOCAL OK) Future getFile(String token, String fileUuid); // Delete one file on BDD - Future> deleteFile(String token, String fileUuid); + Future deleteFile(String token, String fileUuid); + // Get info on user (LOCAL OK) Future getInfoUser(String token); - /* -> Modification attribut - // Update email - Future updateEmail(String token, String email); - - // Update username - Future updateUsername(String token, String username); - */ + // Update email, password, username Future> modifAttribut( String token, String nameAttribut, String newValue); } diff --git a/lib/modele/api/request_api.dart b/lib/modele/api/request_api.dart index eb5b9fc..bf525af 100644 --- a/lib/modele/api/request_api.dart +++ b/lib/modele/api/request_api.dart @@ -19,85 +19,105 @@ class RequestApi implements IDataStrategy { var request = http.Request('GET', url); request.headers.addAll({'Authorization': token}); - var streamedResponse = await request.send(); - final response = await http.Response.fromStream(streamedResponse); - // !! Crée un fichier comme ca avec les bytes !! - //File("//").writeAsBytes(response.bodyBytes); + try { + var streamedResponse = await request.send(); + final response = await http.Response.fromStream(streamedResponse); - if (response.statusCode == 200) { - return Tuple2(true, response.bodyBytes); - } - if ((response.statusCode == 401)) { - return const Tuple2(false, "401 - UNAUTHORIZED"); - } - if ((response.statusCode == 404)) { - return const Tuple2(false, "404 - NOT FOUND"); + if (response.statusCode == 200) { + return Tuple2(true, response.bodyBytes); + } + if ((response.statusCode == 401)) { + return const Tuple2(false, "401 - UNAUTHORIZED"); + } + if ((response.statusCode == 404)) { + return const Tuple2(false, "404 - NOT FOUND"); + } + // When Network Off + } on SocketException { + return const Tuple2(false, "No connection"); } + return const Tuple2(false, "Fail"); } @override - Future> deleteFile(String token, String fileUuid) async { - final response = await http.delete( - Uri.parse('$urlApi/user/files/$fileUuid'), - headers: {'Authorization': token}); + Future deleteFile(String token, String fileUuid) async { + try { + final response = await http.delete( + Uri.parse('$urlApi/user/files/$fileUuid'), + headers: {'Authorization': token}); - if (response.statusCode == 200) { - return const Tuple2(true, "Successful"); - } - if (response.statusCode == 401) { - return const Tuple2(false, "401 - UNAUTHORIZED"); + if (response.statusCode == 200) { + return true; + } + if (response.statusCode == 401) { + return false; + } + if (response.statusCode == 404) { + return false; + } + } on SocketException catch (_) { + return false; } - if (response.statusCode == 404) { - return const Tuple2(false, "404 - NOT FOUND"); - } - return const Tuple2(false, "Fail"); + return false; } @override Future> deleteUser(String token) async { - final response = await http.delete(Uri.parse('$urlApi/user'), - headers: {'Authorization': token}); - if (response.statusCode == 200) { - return const Tuple2(true, "Successful"); - } else if (response.statusCode == 401) { - return const Tuple2( - false, "401 UNAUTHORIZED - Mauvais ou pas de token"); - } else if (response.statusCode == 404) { - return const Tuple2( - false, "404 NOT FOUND - Pas de compte lié"); + try { + final response = await http.delete(Uri.parse('$urlApi/user'), + headers: {'Authorization': token}); + if (response.statusCode == 200) { + return const Tuple2(true, "Successful"); + } else if (response.statusCode == 401) { + return const Tuple2( + false, "401 UNAUTHORIZED - Mauvais ou pas de token"); + } else if (response.statusCode == 404) { + return const Tuple2( + false, "404 NOT FOUND - Pas de compte lié"); + } + } on SocketException catch (_) { + return const Tuple2(false, "No connection"); } return const Tuple2(false, "Fail"); } @override Future getFiles(String token) async { - final response = await http.get(Uri.parse('$urlApi/user/files'), - headers: {'Authorization': token}); + try { + final response = await http.get(Uri.parse('$urlApi/user/files'), + headers: {'Authorization': token}); - if (response.statusCode == 200) { - return Tuple2(true, - (json.decode(response.body) as List).cast>()); - } - if (response.statusCode == 401) { - return const Tuple2(false, "401 - UNAUTHORIZED"); + if (response.statusCode == 200) { + return Tuple2(true, + (json.decode(response.body) as List).cast>()); + } + if (response.statusCode == 401) { + return const Tuple2(false, "401 - UNAUTHORIZED"); + } + } on SocketException catch (_) { + return const Tuple2(false, "No connection"); } return const Tuple2(false, "Fail"); } @override Future> connexion(String email, String hash) async { - final response = - await http.get(Uri.parse('$urlApi/user/login/$email/$hash')); - if (response.statusCode == 200) { - Map json = jsonDecode(response.body); - return Tuple2(true, json['token'].toString()); - } - if (response.statusCode == 401) { - return const Tuple2(false, "UNAUTHORIZED"); - } - if (response.statusCode == 404) { - return const Tuple2(false, "Not found the email"); + try { + final response = + await http.get(Uri.parse('$urlApi/user/login/$email/$hash')); + if (response.statusCode == 200) { + Map json = jsonDecode(response.body); + return Tuple2(true, json['token'].toString()); + } + if (response.statusCode == 401) { + return const Tuple2(false, "UNAUTHORIZED"); + } + if (response.statusCode == 404) { + return const Tuple2(false, "Not found the email"); + } + } on SocketException catch (_) { + return const Tuple2(false, "No connection"); } return const Tuple2(false, "Fail"); } @@ -107,19 +127,23 @@ class RequestApi implements IDataStrategy { String email, String hash, String username) async { var body = {"email": email, "hash": hash, "username": username}; var header = {"Content-Type": "application/json"}; - final response = await http.post(Uri.parse('$urlApi/user'), - headers: header, body: jsonEncode(body)); + try { + final response = await http.post(Uri.parse('$urlApi/user'), + headers: header, body: jsonEncode(body)); - if (response.statusCode == 200) { - Map json = jsonDecode(response.body); - return Tuple2(true, json['token'].toString()); - } - if (response.statusCode == 400) { - return const Tuple2(false, "400 BAD REQUEST - Json mal formaté"); - } - if (response.statusCode == 409) { - return const Tuple2( - false, "409 CONFLICT - Déja un compte avec cet email"); + if (response.statusCode == 200) { + Map json = jsonDecode(response.body); + return Tuple2(true, json['token'].toString()); + } + if (response.statusCode == 400) { + return const Tuple2(false, "400 BAD REQUEST - Json mal formaté"); + } + if (response.statusCode == 409) { + return const Tuple2( + false, "409 CONFLICT - Déja un compte avec cet email"); + } + } on SocketException catch (_) { + return const Tuple2(false, "No connection"); } return const Tuple2(false, "Fail"); } @@ -127,25 +151,28 @@ class RequestApi implements IDataStrategy { @override Future> modifAttribut( String token, String nameAttribut, String newValue) async { - final response = await http.put(Uri.parse('$urlApi/user/$nameAttribut'), - headers: { - 'Authorization': token, - "Content-Type": "application/json" - }, - body: jsonEncode({nameAttribut: newValue})); - - if (response.statusCode == 200) { - //Map json = jsonDecode(response.body); - return const Tuple2(true, "200 - OK"); - } - if (response.statusCode == 400) { - return const Tuple2(false, "400 - BAD REQUEST"); - } - if (response.statusCode == 401) { - return const Tuple2(false, "400 - UNAUTHORIZED"); - } else { - return const Tuple2(false, "Fail"); + try { + final response = await http.put(Uri.parse('$urlApi/user/$nameAttribut'), + headers: { + 'Authorization': token, + "Content-Type": "application/json" + }, + body: jsonEncode({nameAttribut: newValue})); + + if (response.statusCode == 200) { + //Map json = jsonDecode(response.body); + return const Tuple2(true, "200 - OK"); + } + if (response.statusCode == 400) { + return const Tuple2(false, "400 - BAD REQUEST"); + } + if (response.statusCode == 401) { + return const Tuple2(false, "400 - UNAUTHORIZED"); + } + } on SocketException catch (_) { + return const Tuple2(false, "No connection"); } + return const Tuple2(false, "Fail"); } // -- Priviligié uploadFileByte -- // @@ -169,19 +196,23 @@ class RequestApi implements IDataStrategy { request.fields["SmartFit_Category"] = categoryActivity; request.fields["SmartFit_Date"] = dateActivity; - final response = await request.send(); + try { + final response = await request.send(); - if (response.statusCode == 200) { - return const Tuple2(true, "Successful"); - } - if (response.statusCode == 400) { - return const Tuple2(false, "400 - BAD REQUEST"); - } - if (response.statusCode == 401) { - return const Tuple2(false, "401 - UNAUTHORIZED"); - } - if (response.statusCode == 409) { - return const Tuple2(false, "409 - CONFLICT"); + if (response.statusCode == 200) { + return const Tuple2(true, "Successful"); + } + if (response.statusCode == 400) { + return const Tuple2(false, "400 - BAD REQUEST"); + } + if (response.statusCode == 401) { + return const Tuple2(false, "401 - UNAUTHORIZED"); + } + if (response.statusCode == 409) { + return const Tuple2(false, "409 - CONFLICT"); + } + } on SocketException catch (_) { + return const Tuple2(false, "No connection"); } return const Tuple2(false, "Fail "); } @@ -209,38 +240,45 @@ class RequestApi implements IDataStrategy { request.fields["SmartFit_Date"] = date.toString(); request.fields["info"] = activityInfo.toJson(); - final response = await request.send(); + try { + final response = await request.send(); - if (response.statusCode == 200) { - return const Tuple2(true, "Successful"); - } - if (response.statusCode == 400) { - return const Tuple2(false, "400 - BAD REQUEST"); - } - if (response.statusCode == 401) { - return const Tuple2(false, "401 - UNAUTHORIZED"); - } - if (response.statusCode == 409) { - return const Tuple2(false, "409 - CONFLICT"); + if (response.statusCode == 200) { + return const Tuple2(true, "Successful"); + } + if (response.statusCode == 400) { + return const Tuple2(false, "400 - BAD REQUEST"); + } + if (response.statusCode == 401) { + return const Tuple2(false, "401 - UNAUTHORIZED"); + } + if (response.statusCode == 409) { + return const Tuple2(false, "409 - CONFLICT"); + } + } on SocketException catch (_) { + return const Tuple2(false, "No connection"); } return const Tuple2(false, "Fail "); } @override Future getInfoUser(String token) async { - final response = await http.get(Uri.parse('$urlApi/user/info'), - headers: {'Authorization': token}); - - if (response.statusCode == 200) { - Map json = jsonDecode(response.body); - return Tuple2(true, json); - } - if (response.statusCode == 400) { - return const Tuple2(false, "400 - BAD REQUEST"); + try { + final response = await http.get(Uri.parse('$urlApi/user/info'), + headers: {'Authorization': token}); + if (response.statusCode == 200) { + Map json = jsonDecode(response.body); + return Tuple2(true, json); + } + if (response.statusCode == 400) { + return const Tuple2(false, "400 - BAD REQUEST"); + } + if (response.statusCode == 401) { + return const Tuple2(false, "401 - UNAUTHORIZED"); + } + } on SocketException catch (_) { + return const Tuple2(false, "No connection"); } - if (response.statusCode == 401) { - return const Tuple2(false, "401 - UNAUTHORIZED"); - } - return const Tuple2(false, "Fail "); + return const Tuple2(false, "Fail"); } } diff --git a/lib/modele/helper.dart b/lib/modele/helper.dart new file mode 100644 index 0000000..b03f24a --- /dev/null +++ b/lib/modele/helper.dart @@ -0,0 +1,10 @@ +import 'package:flutter/foundation.dart'; + +class Helper { + static bool isPlatformWeb() { + if (kIsWeb) { + return true; + } + return false; + } +} diff --git a/lib/modele/local_db/db_dummy.dart b/lib/modele/local_db/db_dummy.dart new file mode 100644 index 0000000..d214cc2 --- /dev/null +++ b/lib/modele/local_db/db_dummy.dart @@ -0,0 +1,95 @@ +import 'package:smartfit_app_mobile/modele/activity.dart'; +import 'package:smartfit_app_mobile/modele/local_db/db_impl.dart'; +import 'package:smartfit_app_mobile/modele/user.dart'; + +class DbDummy implements DbImpl { + DbDummy._create(); + DbDummy(); + @override + Future create() async { + return DbDummy._create(); + } + + @override + Future init() { + throw Exception(); + } + + // ==== USER ==== + @override + void addUser(String username, String email, String token) { + throw Exception(); + } + + @override + User getUser() { + throw Exception(); + } + + @override + bool hasUser() { + throw Exception(); + } + + @override + void deleteUser() { + throw Exception(); + } + + @override + void setUserMail(String email) { + throw Exception(); + } + + @override + void setUserName(String username) { + throw Exception(); + } + + @override + void setUserToken(String token) { + throw Exception(); + } + + // ==== ACTIVITY ==== + @override + void addActivity(String uuid, String filename, String category, String info) { + throw Exception(); + } + + @override + void removeActivity(String uuid) { + throw Exception(); + } + + @override + void removeAllActivities() { + throw Exception(); + } + + @override + String getActivityFilenameByUuid(String uuid) { + throw Exception(); + } + + @override + List getAllActivities() { + throw Exception(); + } + + // ==== CONFIG ==== + @override + void initConfig() { + throw Exception(); + } + + @override + void setSaveLocally(bool saveLocally) { + throw Exception(); + } + + @override + bool getSaveLocally() { + throw Exception(); + } +} diff --git a/lib/modele/local_db/db_impl.dart b/lib/modele/local_db/db_impl.dart new file mode 100644 index 0000000..cc15d1f --- /dev/null +++ b/lib/modele/local_db/db_impl.dart @@ -0,0 +1,31 @@ +import 'package:smartfit_app_mobile/modele/user.dart'; +import 'package:smartfit_app_mobile/modele/activity.dart'; + +abstract class DbImpl { + DbImpl._create(); + + Future create(); + + Future init(); + + // ==== USER ==== + void addUser(String username, String email, String token); + User getUser(); + bool hasUser(); + void deleteUser(); + void setUserMail(String email); + void setUserName(String username); + void setUserToken(String token); + + // ==== ACTIVITY ==== + void addActivity(String uuid, String filename, String category, String info); + void removeActivity(String uuid); + void removeAllActivities(); + String getActivityFilenameByUuid(String uuid); + List getAllActivities(); + + // ==== CONFIG ==== + void initConfig(); + void setSaveLocally(bool saveLocally); + bool getSaveLocally(); +} diff --git a/lib/modele/local_db/get_native_db.dart b/lib/modele/local_db/get_native_db.dart new file mode 100644 index 0000000..62a2750 --- /dev/null +++ b/lib/modele/local_db/get_native_db.dart @@ -0,0 +1,7 @@ +import 'package:smartfit_app_mobile/modele/local_db/db_impl.dart'; +import 'package:smartfit_app_mobile/modele/local_db/objectbox.dart'; + +DbImpl getDbImpl() { + DbImpl db = ObjectBox(); + return db; +} diff --git a/lib/modele/local_db/get_web_db.dart b/lib/modele/local_db/get_web_db.dart new file mode 100644 index 0000000..85cb326 --- /dev/null +++ b/lib/modele/local_db/get_web_db.dart @@ -0,0 +1,7 @@ +import 'package:smartfit_app_mobile/modele/local_db/db_impl.dart'; +import 'package:smartfit_app_mobile/modele/local_db/db_dummy.dart'; + +DbImpl getDbImpl() { + DbImpl db = DbDummy(); + return db; +} diff --git a/lib/modele/local_db/model.dart b/lib/modele/local_db/model.dart new file mode 100644 index 0000000..38dc72d --- /dev/null +++ b/lib/modele/local_db/model.dart @@ -0,0 +1,33 @@ +import 'package:objectbox/objectbox.dart'; + +@Entity() +class User { + @Id() + int id = 0; + String username; + String email; + String token; + + User(this.id, this.username, this.email, this.token); +} + +@Entity() +class Activity { + int id; + @Unique() + String uuid; + String filename; + String category; + String info; + + Activity(this.id, this.uuid, this.filename, this.category, this.info); +} + +@Entity() +class Config { + @Id() + int id = 0; + bool saveLocally; + + Config(this.id, this.saveLocally); +} diff --git a/lib/modele/local_db/objectbox.dart b/lib/modele/local_db/objectbox.dart new file mode 100644 index 0000000..cbaceca --- /dev/null +++ b/lib/modele/local_db/objectbox.dart @@ -0,0 +1,154 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:objectbox/objectbox.dart'; +import 'package:smartfit_app_mobile/modele/user.dart'; +import 'package:smartfit_app_mobile/modele/activity.dart'; +import 'package:smartfit_app_mobile/modele/activity_info/activity_info.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:smartfit_app_mobile/modele/local_db/model.dart' as db; +import 'package:smartfit_app_mobile/modele/local_db/db_impl.dart'; + +class ObjectBox implements DbImpl { + late final Store store; + late final Box userBox; + late final Box activityBox; + late final Box configBox; + late final Directory applicationDocumentDir; + + ObjectBox._create(this.store); + + ObjectBox(); + + @override + Future create() async { + final docsDir = await getApplicationDocumentsDirectory(); + final store = await openStore(directory: p.join(docsDir.path, "database")); + return ObjectBox._create(store); + } + + @override + Future init() async { + applicationDocumentDir = await getApplicationDocumentsDirectory(); + userBox = store.box(); + activityBox = store.box(); + configBox = store.box(); + } + + // ===== USER ===== + @override + bool hasUser() { + return !userBox.isEmpty(); + } + + @override + User getUser() { + db.User userRes = userBox.get(1); + return User.create(userRes.username, userRes.email, userRes.token); + } + + @override + void setUserMail(String email) { + db.User user = userBox.get(1); + user.email = email; + userBox.put(user); + } + + @override + void setUserName(String username) { + db.User user = userBox.get(1); + user.username = username; + userBox.put(user); + } + + @override + void setUserToken(String token) { + db.User user = userBox.get(1); + user.token = token; + userBox.put(user); + } + + @override + void deleteUser() { + userBox.removeAll(); + } + + @override + void addUser(String username, String email, String token) { + userBox.put(db.User(0, username, email, token)); + } + + // ===== Activity ===== + @override + void addActivity(String uuid, String filename, String category, String info) { + db.Activity act = db.Activity(0, uuid, filename, category, info); + + try { + activityBox.put(act); + } on ObjectBoxException { + print("Activity already exists"); + } catch (e) { + print("Unknown exception"); + } + } + + // TODO: try catch + @override + void removeActivity(String uuid) { + final Query query = activityBox.query(Activity_.uuid.equals(uuid)).build(); + final db.Activity act = query.findFirst(); + + activityBox.remove(act.id); + } + + @override + String getActivityFilenameByUuid(String uuid) { + final Query query = activityBox.query(Activity_.uuid.equals(uuid)).build(); + final db.Activity act = query.findFirst(); + + return act.filename; + } + + @override + void removeAllActivities() { + activityBox.removeAll(); + } + + // ===== FIT Files ===== + @override + List getAllActivities() { + List activityDBList = activityBox.getAll(); + List userActivityList = List.empty(growable: true); + + for (db.Activity act in activityDBList) { + ActivityInfo actInfo = ActivityInfo.fromJson(jsonDecode(act.info)); + + userActivityList + .add(ActivityOfUser(actInfo, act.category, act.uuid, act.filename)); + } + + return userActivityList; + } + + // ===== Config ===== + @override + void initConfig() { + db.Config config = db.Config(0, true); + configBox.put(config); + } + + @override + void setSaveLocally(bool saveLocally) { + db.Config config = configBox.get(1); + config.saveLocally = saveLocally; + configBox.put(config); + stdout.write("(Config) setSaveLocally: $saveLocally\n"); + } + + @override + bool getSaveLocally() { + db.Config config = configBox.get(1); + stdout.write("(Config) getSaveLocally: ${config.saveLocally}\n"); + return config.saveLocally; + } +} diff --git a/lib/modele/local_db/request_local.dart b/lib/modele/local_db/request_local.dart new file mode 100644 index 0000000..a8fcb26 --- /dev/null +++ b/lib/modele/local_db/request_local.dart @@ -0,0 +1,89 @@ +import 'dart:convert'; +import 'package:smartfit_app_mobile/modele/activity.dart'; +import 'package:smartfit_app_mobile/modele/activity_info/activity_info.dart'; +import 'package:smartfit_app_mobile/modele/activity_saver.dart'; +import 'package:smartfit_app_mobile/modele/api/i_data_strategy.dart'; +import 'package:tuple/tuple.dart'; +import 'package:smartfit_app_mobile/modele/user.dart'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:smartfit_app_mobile/main.dart'; + +class RequestLocal implements IDataStrategy { + @override + Future getInfoUser(String token) async { + final User user = localDB.getUser(); + Map json = {"email": user.email, "username": user.username}; + return Tuple2(true, jsonEncode(json)); + } + + // need to save file on request_api.upload() beforehand. + @override + Future getFile(String token, String fileUuid) async { + ActivitySaver actSaver = await ActivitySaver.create(); + Uint8List fileBytes = actSaver.getActivity(fileUuid); + + return Tuple2(true, fileBytes); + } + + @override + Future getFiles(String token) async { + final List activities = localDB.getAllActivities(); + List> jsonList = List.empty(growable: true); + + for (ActivityOfUser act in activities) { + Map json = { + "uuid": act.fileUuid, + "filename": act.nameFile, + "category": act.category, + "info": act.activityInfo + }; + jsonList.add(json); + } + + return Tuple2(true, jsonList); + } + + @override + Future> modifAttribut( + String token, String nameAttribut, String newValue) async { + return const Tuple2(false, "not implemented"); + } + + @override + Future> postUser( + String email, String hash, String username) async { + return const Tuple2(false, "not implemented"); + } + + @override + Future> deleteUser(String token) async { + return const Tuple2(false, "not implemented"); + } + + @override + Future> connexion(String email, String hash) async { + return const Tuple2(false, "not implemented"); + } + + @override + Future> uploadFile(String token, File file) async { + return const Tuple2(false, "not implemented"); + } + + @override + Future> uploadFileByte( + String token, + Uint8List contentFile, + String nameFile, + String category, + DateTime date, + ActivityInfo activityInfo) async { + return const Tuple2(false, "not implemented"); + } + + @override + Future deleteFile(String token, String fileUuid) async { + throw Exception("Not Implemented"); + } +} diff --git a/lib/modele/user.dart b/lib/modele/user.dart index 3869a9a..3a6f428 100644 --- a/lib/modele/user.dart +++ b/lib/modele/user.dart @@ -12,6 +12,10 @@ class User extends ChangeNotifier { List listActivity = List.empty(growable: true); ManagerSelectedActivity managerSelectedActivity = ManagerSelectedActivity(); + User(); + + User.create(this.username, this.email, this.token); + void addActivity(ActivityOfUser activity) { listActivity.add(activity); notifyListeners(); diff --git a/lib/modele/utile/list_activity/list_activity_utile.dart b/lib/modele/utile/list_activity/list_activity_utile.dart index 19c43c8..dd79aa0 100644 --- a/lib/modele/utile/list_activity/list_activity_utile.dart +++ b/lib/modele/utile/list_activity/list_activity_utile.dart @@ -1,3 +1,8 @@ +import 'package:flutter/foundation.dart'; +import 'package:smartfit_app_mobile/main.dart'; +import 'package:smartfit_app_mobile/modele/api/api_wrapper.dart'; +import 'package:smartfit_app_mobile/modele/activity_saver.dart'; +import 'package:smartfit_app_mobile/modele/helper.dart'; import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; @@ -6,21 +11,21 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smartfit_app_mobile/modele/activity.dart'; import 'package:smartfit_app_mobile/modele/activity_info/activity_info.dart'; -import 'package:smartfit_app_mobile/modele/api/i_data_strategy.dart'; -import 'package:smartfit_app_mobile/modele/api/request_api.dart'; import 'package:smartfit_app_mobile/modele/manager_file.dart'; import 'package:smartfit_app_mobile/modele/user.dart'; +import 'package:smartfit_app_mobile/modele/utile/info_message.dart'; import 'package:tuple/tuple.dart'; class ListActivityUtile { - final IDataStrategy _strategy = RequestApi(); + final ApiWrapper api = ApiWrapper(); final ManagerFile _managerFile = ManagerFile(); - Future> getContentActivity( - BuildContext context, ActivityOfUser activityOfUser) async { - Tuple2 result = await _strategy.getFile( + Future> getContentActivity(BuildContext context, + ActivityOfUser activityOfUser, InfoMessage infoManager) async { + Tuple2 result = await api.getFile( Provider.of(context, listen: false).token, - activityOfUser.fileUuid); + activityOfUser.fileUuid, + infoManager); if (result.item1 == false) { return Tuple2(result.item1, result.item2); } @@ -28,6 +33,17 @@ class ListActivityUtile { activityOfUser.contentActivity = List.from(_managerFile.convertByteIntoCSV(result.item2)); + // TODO: Not sure this line as an utility + // localDB.saveActivityFile(activityOfUser.contentActivity); + + // TODO: Check if file exists, right now it overwrites each time + // TODO: Make ActivitySaver member of the class + if (!Helper.isPlatformWeb() && localDB.getSaveLocally()) { + ActivitySaver actSaver = await ActivitySaver.create(); + actSaver.saveActivity(result.item2, + localDB.getActivityFilenameByUuid(activityOfUser.fileUuid)); + } + if (!Provider.of(context, listen: false) .managerSelectedActivity .addSelectedActivity(activityOfUser)) { @@ -37,15 +53,15 @@ class ListActivityUtile { } Future> getFiles( - String token, BuildContext context) async { + String token, BuildContext context, InfoMessage infoManager) async { bool notZero = false; - Tuple2 result = await _strategy - .getFiles(Provider.of(context, listen: false).token); + Tuple2 result = await api.getFiles( + Provider.of(context, listen: false).token, infoManager); if (result.item1 == false) { return Tuple2(result.item1, result.item2); } - for (Map element in result.item2) { + for (var element in result.item2) { if (!notZero) { Provider.of(context, listen: false).listActivity.clear(); notZero = true; @@ -56,75 +72,77 @@ class ListActivityUtile { element["category"].toString(), element["uuid"].toString(), element["filename"].toString())); + + // Save to local db + if (!kIsWeb) { + localDB.addActivity(element["uuid"], element["filename"], + element["category"], jsonEncode(element["info"])); + } } return const Tuple2(true, "Yeah"); } - Future> _addFile( - Uint8List bytes, String filename, String token) async { + Future> addFile(Uint8List bytes, String filename, + String token, InfoMessage infoManager) async { // -- Transormer le fit en CSV Tuple4>, ActivityInfo, String> resultData = _managerFile.convertBytesFitFileIntoCSVListAndGetInfo(bytes); String csvString = const ListToCsvConverter().convert(resultData.item2); Uint8List byteCSV = Uint8List.fromList(utf8.encode(csvString)); - // --- Save Local - // --- Api - /* - ManagerFile x = ManagerFile(); - await File("${await x.localPath}\\test.csv").writeAsString(csvString); - print("${await x.localPath}\\test.csv");*/ - - Tuple2 result = await _strategy.uploadFileByte( + + Tuple2 result = await api.uploadFileByte( token, byteCSV, filename, - resultData.item4, - resultData.item3.startTime, - resultData.item3); - // resultData.item4 == category - // resultData.item3 == ActivityInfo + resultData.item4, // category + resultData.item3.startTime, // activityInfo + resultData.item3, + infoManager); if (result.item1 == false) { return Tuple2(false, result.item2); } - return const Tuple2(true, "Yeah"); - } - Future deleteFileOnBDD(String token, String fileUuid) async { - Tuple2 result = await _strategy.deleteFile(token, fileUuid); - if (!result.item1) { - return false; + // Save on local storage if plateform not browser + if (!Helper.isPlatformWeb() && localDB.getSaveLocally()) { + ActivitySaver actSaver = await ActivitySaver.create(); + actSaver.saveActivity(byteCSV, filename); } - return true; + + return const Tuple2(true, "Yeah"); } - // --- Ne marche pas sous window !! Jsp linux (mettre en format mobile) -- // - void addFileWeb(Uint8List? bytes, String token, String filename, - BuildContext context) async { + // --- Ne marche pas sous windows !! Jsp linux (mettre en format mobile) -- // + Future addFileWeb(Uint8List? bytes, String token, String filename, + BuildContext context, InfoMessage infoManager) async { if (bytes == null) { return; } - Tuple2 resultAdd = await _addFile(bytes, filename, token); + Tuple2 resultAdd = + await addFile(bytes, filename, token, infoManager); if (!resultAdd.item1) { //print("Message error"); return; } - Tuple2 resultGet = await getFiles(token, context); + Tuple2 resultGet = + await getFiles(token, context, infoManager); if (!resultGet.item1) { //print("Message error"); return; } } - Future addFileMobile( - String path, String token, String filename, BuildContext context) async { - Tuple2 resultAdd = - await _addFile(await File(path).readAsBytes(), filename, token); + Future addFileMobile(String path, String token, String filename, + BuildContext context, InfoMessage infoManager) async { + Tuple2 resultAdd = await addFile( + await File(path).readAsBytes(), filename, token, infoManager); if (!resultAdd.item1) { //print("Message error"); return; } - Tuple2 resultGet = await getFiles(token, context); + // TODO: What is that ? + Tuple2 resultGet = + await getFiles(token, context, infoManager); if (!resultGet.item1) { //print("Message error"); return; diff --git a/lib/modele/utile/profile_view/profile_utile.dart b/lib/modele/utile/profile_view/profile_utile.dart deleted file mode 100644 index e1ee3fa..0000000 --- a/lib/modele/utile/profile_view/profile_utile.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:smartfit_app_mobile/modele/api/i_data_strategy.dart'; -import 'package:smartfit_app_mobile/modele/api/request_api.dart'; - -class ProfileUtil { - final IDataStrategy _dataStrategy = RequestApi(); - - void modifyDataUser(String token, String attribut, String newUsername) { - _dataStrategy.modifAttribut(token, attribut, newUsername); - } -} diff --git a/lib/view/activity/mobile/mobile_list_activity.dart b/lib/view/activity/mobile/mobile_list_activity.dart index e67c7eb..bea9c2d 100644 --- a/lib/view/activity/mobile/mobile_list_activity.dart +++ b/lib/view/activity/mobile/mobile_list_activity.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import 'package:smartfit_app_mobile/common/colo_extension.dart'; import 'package:smartfit_app_mobile/common_widget/container/list/list_activity_widget.dart'; import 'package:smartfit_app_mobile/modele/user.dart'; +import 'package:smartfit_app_mobile/modele/utile/info_message.dart'; import 'package:smartfit_app_mobile/modele/utile/list_activity/list_activity_utile.dart'; class MobileListActivity extends StatefulWidget { @@ -15,6 +16,7 @@ class MobileListActivity extends StatefulWidget { class _MobileListActivity extends State { final ListActivityUtile _utile = ListActivityUtile(); + final InfoMessage infoManager = InfoMessage(); @override Widget build(BuildContext context) { @@ -27,7 +29,7 @@ class _MobileListActivity extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 20), Row( @@ -41,9 +43,13 @@ class _MobileListActivity extends State { fontWeight: FontWeight.w700), ), TextButton( - onPressed: () => _utile.getFiles( - Provider.of(context, listen: false).token, - context), + onPressed: () async { + await _utile.getFiles( + Provider.of(context, listen: false).token, + context, + infoManager); + setState(() {}); + }, child: Text("Get activity", style: TextStyle( color: TColor.gray, @@ -54,16 +60,18 @@ class _MobileListActivity extends State { FilePickerResult? result = await FilePicker.platform.pickFiles(); if (result != null && result.files.isNotEmpty) { - // ignore: use_build_context_synchronously - _utile.addFileMobile( + await _utile.addFileMobile( result.files.single.path!, Provider.of(context, listen: false).token, result.files.first.name, - context); + context, + infoManager); + setState(() {}); } else { // msg d'erreur // User canceled the picker } + setState(() {}); }, child: Text( "Ajouter", @@ -75,6 +83,10 @@ class _MobileListActivity extends State { ) ], ), + Visibility( + visible: infoManager.isVisible, + child: Text(infoManager.message, + style: TextStyle(color: infoManager.messageColor))), Provider.of(context, listen: true).listActivity.isEmpty ? Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/view/activity/web/web_list_activity.dart b/lib/view/activity/web/web_list_activity.dart index 1296842..d5a333d 100644 --- a/lib/view/activity/web/web_list_activity.dart +++ b/lib/view/activity/web/web_list_activity.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:smartfit_app_mobile/common_widget/container/list/list_activity_widget.dart'; +import 'package:smartfit_app_mobile/modele/utile/info_message.dart'; import 'package:smartfit_app_mobile/modele/utile/list_activity/list_activity_utile.dart'; - +import 'package:smartfit_app_mobile/common_widget/container/list/list_activity_widget.dart'; import 'package:file_picker/file_picker.dart'; import 'package:provider/provider.dart'; import 'package:smartfit_app_mobile/common/colo_extension.dart'; -import 'package:smartfit_app_mobile/modele/api/i_data_strategy.dart'; -import 'package:smartfit_app_mobile/modele/api/request_api.dart'; import 'package:smartfit_app_mobile/modele/user.dart'; class WebListActivity extends StatefulWidget { @@ -18,8 +16,8 @@ class WebListActivity extends StatefulWidget { class _WebListActivityState extends State { FilePickerResult? result; - IDataStrategy strategy = RequestApi(); final ListActivityUtile _utile = ListActivityUtile(); + final InfoMessage infoManager = InfoMessage(); @override Widget build(BuildContext context) { @@ -31,7 +29,7 @@ class _WebListActivityState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 15), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ const SizedBox(height: 20), Row( @@ -45,9 +43,13 @@ class _WebListActivityState extends State { fontWeight: FontWeight.w700), ), TextButton( - onPressed: () => _utile.getFiles( - Provider.of(context, listen: false).token, - context), + onPressed: () async { + await _utile.getFiles( + Provider.of(context, listen: false).token, + context, + infoManager); + setState(() {}); + }, child: Text("Get activity", style: TextStyle( color: TColor.gray, @@ -58,31 +60,17 @@ class _WebListActivityState extends State { FilePickerResult? result = await FilePicker.platform.pickFiles(); if (result != null && result.files.isNotEmpty) { - _utile.addFileWeb( + await _utile.addFileWeb( result.files.first.bytes, Provider.of(context, listen: false).token, result.files.first.name, - context); + context, + infoManager); } else { print("Picker"); // msg d'erreur // User canceled the picker } - - /* - html.FileUploadInputElement uploadInput = - html.FileUploadInputElement(); - uploadInput.click(); - - uploadInput.onChange.listen((e) { - final files = uploadInput.files; - if (files != null && files.isNotEmpty) { - addFileWeb( - files[0], - Provider.of(context, listen: false) - .token); // Lecture du fichier sélectionné - } - });*/ }, child: Text( "Ajouter", @@ -94,6 +82,10 @@ class _WebListActivityState extends State { ) ], ), + Visibility( + visible: infoManager.isVisible, + child: Text(infoManager.message, + style: TextStyle(color: infoManager.messageColor))), Provider.of(context, listen: true).listActivity.isEmpty ? Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/view/home/prediction_view.dart b/lib/view/home/prediction_view.dart new file mode 100644 index 0000000..71c0641 --- /dev/null +++ b/lib/view/home/prediction_view.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:smartfit_app_mobile/common/colo_extension.dart'; +import 'package:smartfit_app_mobile/common_widget/button/round_button.dart'; +import 'package:smartfit_app_mobile/common_widget/container/workout_row/workout_row.dart'; + +class Prediction extends StatefulWidget { + const Prediction({Key? key}) : super(key: key); + + @override + State createState() => _PredictionState(); +} + +class _PredictionState extends State { + List> lastWorkoutArr = [ + { + "name": "Temps", + "image": "assets/img/time-icon2.svg", + "value": "200 s", + }, + { + "name": "Rythme cardiaque", + "image": "assets/img/bpm2-icon.svg", + "value": "120 BPM", + }, + { + "name": "Vitesse", + "image": "assets/img/vitesse2-icon.svg", + "value": "3 m/s", + }, + { + "name": "Distance", + "image": "assets/img/distance2-icon.svg", + "value": "300 m", + } + ]; + + @override + Widget build(BuildContext context) { + var media = MediaQuery.of(context).size; + + return Scaffold( + body: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 40), + Text( + "Prédiction", + style: TextStyle( + color: TColor.black, + fontSize: 22, + fontWeight: FontWeight.w700, + ), + ), + SizedBox(height: 20), + Row( + children: [ + + ], + ), + Container( + decoration: BoxDecoration( + color: TColor.lightGray, + boxShadow: [ + BoxShadow( + color: Color.fromARGB(255, 234, 234, 234).withOpacity(0.9), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 2), + ), + ], + borderRadius: BorderRadius.circular(15), + ), + child: Row( + children: [ + Container( + alignment: Alignment.center, + width: 50, + height: 50, + padding: const EdgeInsets.symmetric(horizontal: 15), + child: SvgPicture.asset( + "assets/img/Profile_tab.svg", + width: 20, + height: 20, + fit: BoxFit.contain, + ), + ), + Expanded( + child: DropdownButtonHideUnderline( + child: DropdownButton( + items: ["Walking", "Cycling"] + .map((name) => DropdownMenuItem( + value: name, + child: Text( + name, + style: TextStyle( + color: TColor.gray, + fontSize: 14, + ), + ), + )) + .toList(), + onChanged: (value) {}, + isExpanded: true, + hint: Text( + "Choisir type d'activité", + style: TextStyle( + color: TColor.gray, + fontSize: 12, + ), + ), + ), + ), + ), + // Bouton "Valider" prenant 30% de la largeur du parent + + ], + ), + ), + SizedBox(height: 20), + RoundButton( + title: "Valider", + onPressed: () async { + setState(() {}); + }), + SizedBox(height: 20), + ListView.builder( + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: lastWorkoutArr.length, + itemBuilder: (context, index) { + var wObj = + lastWorkoutArr[index] as Map ?? {}; + return InkWell( + child: WorkoutRow(wObj: wObj), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/view/home/web/web_homeview.dart b/lib/view/home/web/web_homeview.dart index 2498fd0..a44b92d 100644 --- a/lib/view/home/web/web_homeview.dart +++ b/lib/view/home/web/web_homeview.dart @@ -63,11 +63,10 @@ class _WebHomeView extends State { return Scaffold( backgroundColor: TColor.white, body: SingleChildScrollView( - child: SafeArea( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 50), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( height: media.width * 0.03, @@ -76,9 +75,9 @@ class _WebHomeView extends State { SizedBox( height: media.width * 0.03, ), - Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Column(mainAxisAlignment: MainAxisAlignment.center, children: [ Column( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Status d'activité", @@ -90,7 +89,8 @@ class _WebHomeView extends State { SizedBox( height: media.width * 0.02, ), - Row(children: [ + Row(mainAxisAlignment: MainAxisAlignment.center, + children: [ BpmByTime(media, data), SizedBox( width: media.width * 0.01, @@ -111,7 +111,7 @@ class _WebHomeView extends State { height: media.width * 0.05, ), Column( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Rythme cardique et vitesse", @@ -124,7 +124,7 @@ class _WebHomeView extends State { height: media.width * 0.03, ), Row( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ GraphBpmAndSpeedByTime(media, data), SizedBox( @@ -156,7 +156,7 @@ class _WebHomeView extends State { fontSize: 16, fontWeight: FontWeight.w700), ), - Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ GraphAltitudeByTime(media, data), LigneContainerStats( "${minAltitude.toInt()} m", @@ -172,8 +172,8 @@ class _WebHomeView extends State { ], ), ), - ), ), + ); } } diff --git a/lib/view/login/mobile/android_login_view.dart b/lib/view/login/mobile/android_login_view.dart index 39b5da0..e3d5dd3 100644 --- a/lib/view/login/mobile/android_login_view.dart +++ b/lib/view/login/mobile/android_login_view.dart @@ -1,10 +1,12 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:smartfit_app_mobile/modele/utile/login_user.dart'; -import 'package:smartfit_app_mobile/view/main_tab/main_tab_view.dart'; import 'package:smartfit_app_mobile/common/colo_extension.dart'; import 'package:smartfit_app_mobile/common_widget/button/round_button.dart'; import 'package:smartfit_app_mobile/common_widget/text_field/round_text_field.dart'; +import 'package:smartfit_app_mobile/main.dart'; +import 'package:smartfit_app_mobile/modele/utile/login_user.dart'; +import 'package:smartfit_app_mobile/view/main_tab/main_tab_view.dart'; import 'package:tuple/tuple.dart'; class MobileLoginView extends StatefulWidget { @@ -164,7 +166,9 @@ class _MobileLoginView extends State { "Impossible de récupéré les données de l'utilisateur - {$infoUser.item2}"); } else { util.fillUser(context, infoUser.item2, result.item2); - + if (!kIsWeb) + localDB.addUser(infoUser.item2["username"], + infoUser.item2["email"], result.item2); Navigator.push( context, MaterialPageRoute( diff --git a/lib/view/login/mobile/android_signup_view.dart b/lib/view/login/mobile/android_signup_view.dart index a62a9db..7cffe61 100644 --- a/lib/view/login/mobile/android_signup_view.dart +++ b/lib/view/login/mobile/android_signup_view.dart @@ -228,59 +228,6 @@ class _MobileSignUpView extends State { SizedBox( height: media.width * 0.04, ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () {}, - child: Container( - width: 50, - height: 50, - alignment: Alignment.center, - decoration: BoxDecoration( - color: TColor.white, - border: Border.all( - width: 1, - color: TColor.gray.withOpacity(0.4), - ), - borderRadius: BorderRadius.circular(15), - ), - child: Image.asset( - "assets/img/google.png", - width: 20, - height: 20, - ), - ), - ), - SizedBox( - width: media.width * 0.04, - ), - GestureDetector( - onTap: () {}, - child: Container( - width: 50, - height: 50, - alignment: Alignment.center, - decoration: BoxDecoration( - color: TColor.white, - border: Border.all( - width: 1, - color: TColor.gray.withOpacity(0.4), - ), - borderRadius: BorderRadius.circular(15), - ), - child: Image.asset( - "assets/img/suunto.png", - width: 35, - height: 35, - ), - ), - ) - ], - ), - SizedBox( - height: media.width * 0.04, - ), TextButton( onPressed: () { Navigator.push( diff --git a/lib/view/login/web/web_login_view.dart b/lib/view/login/web/web_login_view.dart index 0c4833e..899bdad 100644 --- a/lib/view/login/web/web_login_view.dart +++ b/lib/view/login/web/web_login_view.dart @@ -1,10 +1,12 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:smartfit_app_mobile/modele/utile/login_user.dart'; -import 'package:smartfit_app_mobile/view/main_tab/main_tab_view.dart'; import 'package:smartfit_app_mobile/common/colo_extension.dart'; import 'package:smartfit_app_mobile/common_widget/button/round_button.dart'; import 'package:smartfit_app_mobile/common_widget/text_field/round_text_field.dart'; +import 'package:smartfit_app_mobile/main.dart'; +import 'package:smartfit_app_mobile/modele/utile/login_user.dart'; +import 'package:smartfit_app_mobile/view/main_tab/main_tab_view.dart'; import 'package:tuple/tuple.dart'; class WebLoginView extends StatefulWidget { @@ -150,6 +152,7 @@ class _WebLoginView extends State { RoundButton( title: "Se connecter", onPressed: () async { + // TODO: utiliser la vrai validation if (!emailValidate || !passwordValidate) { _printMsgError("Les champs renseigné ne sont pas valide"); return; @@ -168,6 +171,9 @@ class _WebLoginView extends State { "Impossible de récupéré les données de l'utilisateur - {$infoUser.item2}"); } else { util.fillUser(context, infoUser.item2, result.item2); + if (!kIsWeb) + localDB.addUser(infoUser.item2["username"], + infoUser.item2["email"], result.item2); Navigator.push( context, MaterialPageRoute( diff --git a/lib/view/main_tab/mobile/mobile_main_tab_view.dart b/lib/view/main_tab/mobile/mobile_main_tab_view.dart index e8ebcef..11b381e 100644 --- a/lib/view/main_tab/mobile/mobile_main_tab_view.dart +++ b/lib/view/main_tab/mobile/mobile_main_tab_view.dart @@ -4,6 +4,7 @@ import 'package:smartfit_app_mobile/common_widget/button/tab_button.dart'; import 'package:smartfit_app_mobile/view/activity/activity.dart'; import 'package:smartfit_app_mobile/view/home/home_view.dart'; import 'package:flutter/material.dart'; +import 'package:smartfit_app_mobile/view/home/prediction_view.dart'; import 'package:smartfit_app_mobile/view/map/my_map.dart'; import 'package:smartfit_app_mobile/view/profile/profile_view.dart'; import 'package:smartfit_app_mobile/view/volumes/volumes_view.dart'; @@ -30,7 +31,7 @@ class _MobileMainTabViewState extends State { height: 70, child: InkWell( onTap: () { - selectTab = 4; + selectTab = 10; currentTab = const ListActivity(); if (mounted) { setState(() {}); @@ -90,8 +91,8 @@ class _MobileMainTabViewState extends State { } }), TabButton( - icon: "assets/img/Activity_tab.svg", - selectIcon: "assets/img/Activity_tab_select.svg", + icon: "assets/img/volumes.svg", + selectIcon: "assets/img/volumes_selected.svg", isActive: selectTab == 2, onTap: () { selectTab = 2; @@ -104,11 +105,22 @@ class _MobileMainTabViewState extends State { width: 40, ), TabButton( - icon: "assets/img/mapIcon.svg", - selectIcon: "assets/img/mapIcon_selected.svg", + icon: "assets/img/prediction.svg", + selectIcon: "assets/img/prediction_selected.svg", isActive: selectTab == 3, onTap: () { selectTab = 3; + currentTab = const Prediction(); + if (mounted) { + setState(() {}); + } + }), + TabButton( + icon: "assets/img/mapIcon.svg", + selectIcon: "assets/img/mapIcon_selected.svg", + isActive: selectTab == 4, + onTap: () { + selectTab = 4; currentTab = const MyMap(); if (mounted) { setState(() {}); @@ -117,9 +129,9 @@ class _MobileMainTabViewState extends State { TabButton( icon: "assets/img/Profile_tab.svg", selectIcon: "assets/img/Profile_tab_select.svg", - isActive: selectTab == 4, + isActive: selectTab == 5, onTap: () { - selectTab = 4; + selectTab = 5; currentTab = const ProfileView(); if (mounted) { setState(() {}); diff --git a/lib/view/main_tab/web/web_main_tab_view.dart b/lib/view/main_tab/web/web_main_tab_view.dart index 759558e..133ab4f 100644 --- a/lib/view/main_tab/web/web_main_tab_view.dart +++ b/lib/view/main_tab/web/web_main_tab_view.dart @@ -4,8 +4,10 @@ import 'package:smartfit_app_mobile/common_widget/button/tab_button.dart'; import 'package:smartfit_app_mobile/view/activity/list_activity.dart'; import 'package:smartfit_app_mobile/view/activity/activity.dart'; import 'package:smartfit_app_mobile/view/home/home_view.dart'; +import 'package:smartfit_app_mobile/view/home/prediction_view.dart'; import 'package:smartfit_app_mobile/view/map/my_map.dart'; import 'package:smartfit_app_mobile/view/profile/profile_view.dart'; +import 'package:smartfit_app_mobile/view/volumes/volumes_view.dart'; class WebMainTabView extends StatefulWidget { const WebMainTabView({Key? key}) : super(key: key); @@ -15,7 +17,7 @@ class WebMainTabView extends StatefulWidget { } class _WebMainTabViewState extends State { - int selectTab = 4; // Définissez l'onglet initial ici + int selectTab = 10; // Définissez l'onglet initial ici late Widget currentTab; @override @@ -49,9 +51,15 @@ class _WebMainTabViewState extends State { index: 1, onTap: () => updateTab(1, const Activity()), ), + sideBarButton( + icon: "assets/img/volumes.svg", + selectIcon: "assets/img/volumes_selected.svg", + index: 2, + onTap: () => updateTab(2, const VolumesView()), + ), InkWell( onTap: () { - updateTab(4, const ListActivity()); + updateTab(10, const ListActivity()); }, child: Container( width: 65, @@ -75,17 +83,23 @@ class _WebMainTabViewState extends State { ), ), ), + sideBarButton( + icon: "assets/img/prediction.svg", + selectIcon: "assets/img/prediction_selected.svg", + index: 3, + onTap: () => updateTab(3, const Prediction()), + ), sideBarButton( icon: "assets/img/mapIcon.svg", selectIcon: "assets/img/mapIcon_selected.svg", - index: 2, - onTap: () => updateTab(2, const MyMap()), + index: 4, + onTap: () => updateTab(4, const MyMap()), ), sideBarButton( icon: "assets/img/Profile_tab.svg", selectIcon: "assets/img/Profile_tab_select.svg", - index: 3, - onTap: () => updateTab(3, const ProfileView()), + index: 5, + onTap: () => updateTab(5, const ProfileView()), ), ], ), diff --git a/lib/view/profile/all_platforme/profile_view_allplatforme.dart b/lib/view/profile/all_platforme/profile_view_allplatforme.dart index 790ca58..4406961 100644 --- a/lib/view/profile/all_platforme/profile_view_allplatforme.dart +++ b/lib/view/profile/all_platforme/profile_view_allplatforme.dart @@ -1,3 +1,5 @@ +import 'package:flutter/foundation.dart'; +import 'package:smartfit_app_mobile/common_widget/container/profile/profile_switch.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smartfit_app_mobile/common/colo_extension.dart'; @@ -8,17 +10,20 @@ import 'package:smartfit_app_mobile/common_widget/container/profile/profile_othe import 'package:smartfit_app_mobile/modele/user.dart'; class ProfileViewAllPlatforme extends StatefulWidget { - const ProfileViewAllPlatforme(this.positive, this.accountArr, this.otherArr, - {super.key}); - final bool positive; + final bool offlineSave; final List accountArr; final List otherArr; + const ProfileViewAllPlatforme( + this.offlineSave, this.accountArr, this.otherArr, + {super.key}); @override State createState() => _ProfileViewAllPlatforme(); } class _ProfileViewAllPlatforme extends State { + bool isNative = !kIsWeb; + @override Widget build(BuildContext context) { String username = context.watch().username; @@ -54,6 +59,19 @@ class _ProfileViewAllPlatforme extends State { const SizedBox( height: 25, ), + // TODO: Download/Delete (local) all users files on toggle ? + // TODO: Display size of download in Mo + Visibility( + visible: isNative, + child: const Column( + children: [ + ProfileSwitch("Offline mode", "Save your files locally", + "local_save.png"), + SizedBox( + height: 25, + ) + ], + )), ProfileOther(widget.otherArr) ], ), diff --git a/lib/view/profile/mobile/mobile_change_email.dart b/lib/view/profile/mobile/mobile_change_email.dart index b887896..b82edf7 100644 --- a/lib/view/profile/mobile/mobile_change_email.dart +++ b/lib/view/profile/mobile/mobile_change_email.dart @@ -1,3 +1,5 @@ +import 'package:flutter/foundation.dart'; +import 'package:smartfit_app_mobile/main.dart'; import 'package:flutter/material.dart'; import 'package:smartfit_app_mobile/modele/api/api_wrapper.dart'; import 'package:smartfit_app_mobile/modele/user.dart'; @@ -110,7 +112,7 @@ class _MobileChangeEmailViewState extends State { RoundButton( title: "Confirmer", onPressed: () async { - bool res = await api.modifyUserInfo( + bool res = await api.updateUserInfo( 'email', controllerTextEmail.text, Provider.of(context, listen: false).token, @@ -118,6 +120,8 @@ class _MobileChangeEmailViewState extends State { if (res) { Provider.of(context, listen: false).email = controllerTextEmail.text; + if (!kIsWeb) + localDB.setUserMail(controllerTextEmail.text); } setState(() {}); }), diff --git a/lib/view/profile/mobile/mobile_change_password.dart b/lib/view/profile/mobile/mobile_change_password.dart index a170eb2..66a9cfd 100644 --- a/lib/view/profile/mobile/mobile_change_password.dart +++ b/lib/view/profile/mobile/mobile_change_password.dart @@ -111,7 +111,7 @@ class _MobileChangePasswordViewState extends State { if (res.item1) { if (controllerNewPasswd.text == controllerNewPasswd2.text) { - await api.modifyUserInfo( + await api.updateUserInfo( 'password', sha256 .convert(utf8 diff --git a/lib/view/profile/mobile/mobile_change_username.dart b/lib/view/profile/mobile/mobile_change_username.dart index 3aa1367..c494cf9 100644 --- a/lib/view/profile/mobile/mobile_change_username.dart +++ b/lib/view/profile/mobile/mobile_change_username.dart @@ -1,3 +1,5 @@ +import 'package:flutter/foundation.dart'; +import 'package:smartfit_app_mobile/main.dart'; import 'package:provider/provider.dart'; import 'package:flutter/material.dart'; import 'package:smartfit_app_mobile/modele/api/api_wrapper.dart'; @@ -109,7 +111,7 @@ class _MobileChangeUsernameViewState extends State { RoundButton( title: "Confirmer", onPressed: () async { - bool res = await api.modifyUserInfo( + bool res = await api.updateUserInfo( 'username', controllerTextUsername.text, Provider.of(context, listen: false).token, @@ -117,6 +119,9 @@ class _MobileChangeUsernameViewState extends State { if (res) { Provider.of(context, listen: false) .username = controllerTextUsername.text; + if (!kIsWeb) + localDB + .setUserName(controllerTextUsername.text); } setState(() {}); }), diff --git a/lib/view/profile/web/web_change_email.dart b/lib/view/profile/web/web_change_email.dart index 6a7fe3c..1818559 100644 --- a/lib/view/profile/web/web_change_email.dart +++ b/lib/view/profile/web/web_change_email.dart @@ -1,3 +1,5 @@ +import 'package:flutter/foundation.dart'; +import 'package:smartfit_app_mobile/main.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:smartfit_app_mobile/modele/api/api_wrapper.dart'; @@ -108,7 +110,7 @@ class _WebChangeEmailViewState extends State { RoundButton( title: "Confirmer", onPressed: () async { - bool res = await apiWrapper.modifyUserInfo( + bool res = await apiWrapper.updateUserInfo( 'email', controllerTextEmail.text, Provider.of(context, listen: false).token, @@ -116,6 +118,8 @@ class _WebChangeEmailViewState extends State { if (res) { Provider.of(context, listen: false).email = controllerTextEmail.text; + if (!kIsWeb) + localDB.setUserMail(controllerTextEmail.text); } setState(() {}); }), diff --git a/lib/view/profile/web/web_change_password.dart b/lib/view/profile/web/web_change_password.dart index 00ff54e..718468e 100644 --- a/lib/view/profile/web/web_change_password.dart +++ b/lib/view/profile/web/web_change_password.dart @@ -114,7 +114,7 @@ class _WebChangePasswordViewState extends State { if (res.item1) { if (controllerNewPasswd.text == controllerNewPasswd2.text) { - await api.modifyUserInfo( + await api.updateUserInfo( 'password', sha256 .convert(utf8 diff --git a/lib/view/profile/web/web_change_username.dart b/lib/view/profile/web/web_change_username.dart index f00c301..25ee3be 100644 --- a/lib/view/profile/web/web_change_username.dart +++ b/lib/view/profile/web/web_change_username.dart @@ -1,3 +1,5 @@ +import 'package:flutter/foundation.dart'; +import 'package:smartfit_app_mobile/main.dart'; import 'package:flutter/material.dart'; import 'package:smartfit_app_mobile/modele/user.dart'; import 'package:provider/provider.dart'; @@ -110,7 +112,7 @@ class _WebChangeUsernameViewState extends State { RoundButton( title: "Confirmer", onPressed: () async { - bool res = await api.modifyUserInfo( + bool res = await api.updateUserInfo( 'username', controllerTextUsername.text, Provider.of(context, listen: false).token, @@ -118,6 +120,9 @@ class _WebChangeUsernameViewState extends State { if (res) { Provider.of(context, listen: false) .username = controllerTextUsername.text; + if (!kIsWeb) + localDB + .setUserName(controllerTextUsername.text); } setState(() {}); }), diff --git a/pubspec.yaml b/pubspec.yaml index 0b436fa..ed415a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,10 @@ dependencies: tuple: ^2.0.2 crypto: ^3.0.3 responsive_builder: ^0.7.0 + universal_html: ^2.2.4 + objectbox: ^2.3.1 + objectbox_flutter_libs: any + path: ^1.8.3 flutter_map: ^5.0.0 latlong2: ^0.9.0 units_converter: ^2.1.1 @@ -70,6 +74,8 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 + build_runner: ^2.4.7 + objectbox_generator: any # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec