diff --git a/assets/images/default_user_pic.jpeg b/assets/images/default_user_pic.jpeg new file mode 100644 index 0000000..d7b09fd Binary files /dev/null and b/assets/images/default_user_pic.jpeg differ diff --git a/lib/api/firebase_user.dart b/lib/api/firebase_user.dart index e469140..bab260f 100644 --- a/lib/api/firebase_user.dart +++ b/lib/api/firebase_user.dart @@ -69,6 +69,7 @@ class FirebaseUser extends FirebaseAPI implements IUser { lastName: doc['lastName'], phoneNumber: doc['phoneNumber'], email: doc['email'], + photoUrl: doc['photoUrl'], uid: doc['uid'])) .toList(); return clients; diff --git a/lib/main.dart b/lib/main.dart index 74fc9b3..4b8b1e9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,8 +1,10 @@ import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/services.dart'; import 'package:get_it/get_it.dart'; +import 'package:pdg_app/api/firebase_file.dart'; import 'package:pdg_app/provider/auth_provider.dart'; import 'package:pdg_app/router/auth_gard.dart'; import 'package:pdg_app/router/chat_guard.dart'; @@ -21,6 +23,7 @@ Future setup() async { AuthProvider( auth: FirebaseConnection(), clientApi: FirebaseUser(FirebaseFirestore.instance), + fileApi: FirebaseFile(FirebaseStorage.instance), ), ); await GetIt.I.get().init(); diff --git a/lib/provider/auth_provider.dart b/lib/provider/auth_provider.dart index 30c1000..8a564c1 100644 --- a/lib/provider/auth_provider.dart +++ b/lib/provider/auth_provider.dart @@ -1,7 +1,9 @@ import 'dart:developer'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:pdg_app/api/iauth.dart'; +import 'package:pdg_app/api/ifile.dart'; import 'package:pdg_app/api/iuser.dart'; import '../model/user.dart'; @@ -9,14 +11,17 @@ import '../model/user.dart'; class AuthProvider extends ChangeNotifier { final Auth _auth; final IUser _userApi; + final IFile _fileApi; bool _isAdmin = false; User? _client; User? _clientDietitian; - AuthProvider({required Auth auth, required IUser clientApi}) + AuthProvider( + {required Auth auth, required IUser clientApi, required IFile fileApi}) : _auth = auth, - _userApi = clientApi; + _userApi = clientApi, + _fileApi = fileApi; Future init() async { if (isConnected()) { @@ -70,10 +75,29 @@ class AuthProvider extends ChangeNotifier { notifyListeners(); } +// Return the profile pic URL + Future uploadProfilePic(XFile? pic, String userUid) async { + String? picUrl; + if (pic != null) { + String path = "images/profile/$userUid.jpg"; + picUrl = await _fileApi.uploadFile(pic.path, path); + log(picUrl); + log(pic.path); + } + + return picUrl; + } + /// register a new user using [email] and [password] as credentials. - Future register(String email, String password, User user) async { + Future register( + String email, String password, User user, XFile? pic) async { + String? picUrl = await uploadProfilePic(pic, user.uid); + await _auth.register(email: email, password: password); user.uid = _auth.uid; + + user.photoUrl = picUrl; + await _userApi.createUser(user); _client = user; notifyListeners(); diff --git a/lib/provider/meal_provider.dart b/lib/provider/meal_provider.dart index 14e6e54..2d3697f 100644 --- a/lib/provider/meal_provider.dart +++ b/lib/provider/meal_provider.dart @@ -1,14 +1,21 @@ +import 'dart:developer'; + import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/foundation.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:pdg_app/api/firebase_file.dart'; import 'package:pdg_app/api/firebase_meal.dart'; import 'package:pdg_app/api/imeal.dart'; +import '../api/ifile.dart'; import '../model/meal.dart'; class MealProvider extends ChangeNotifier { final IMeal _mealApi = FirebaseMeal(FirebaseFirestore.instance); final String _uid; bool _isFetching = false; + final IFile _fileApi = FirebaseFile(FirebaseStorage.instance); MealProvider(this._uid) { fetchMeals(); @@ -37,13 +44,31 @@ class MealProvider extends ChangeNotifier { }).toList(); } - Future addMeal(Meal meal) async { + Future addMeal(Meal meal, XFile? pic) async { + String? picUrl = await uploadMealPic(pic, meal.uid); + meal.photo = picUrl; await _mealApi.createMeal(meal); notifyListeners(); } - Future updateMeal(Meal meal) async { + Future updateMeal(Meal meal, XFile? pic) async { + // Not really optimized to reload each time but is working + String? picUrl = await uploadMealPic(pic, meal.uid); + meal.photo = picUrl; await _mealApi.updateMeal(meal); notifyListeners(); } + + // Return the meal pic URL + Future uploadMealPic(XFile? pic, String mealUid) async { + String? picUrl; + if (pic != null) { + String path = "images/diary/$mealUid.jpg"; + picUrl = await _fileApi.uploadFile(pic.path, path); + log(picUrl); + log(pic.path); + } + + return picUrl; + } } diff --git a/lib/router/router.dart b/lib/router/router.dart index 2d48003..9c90a64 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -1,5 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:auto_route/empty_router_widgets.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:pdg_app/model/aftercare.dart'; import 'package:pdg_app/router/chat_guard.dart'; import 'package:pdg_app/screens/add_meal.dart'; @@ -14,6 +15,7 @@ import 'package:pdg_app/screens/profile.dart'; import 'package:pdg_app/screens/register.dart'; import 'package:pdg_app/widgets/register/register_first_page.dart'; import 'package:pdg_app/widgets/register/register_second_page.dart'; +import 'package:tuple/tuple.dart'; import '../model/meal.dart'; import '../screens/home.dart'; @@ -89,7 +91,7 @@ import 'home_guard.dart'; path: '', page: DiaryScreen, ), - AutoRoute( + AutoRoute>( path: 'add', page: AddMealScreen, ), diff --git a/lib/router/router.gr.dart b/lib/router/router.gr.dart index 767b5a7..e01ba4b 100644 --- a/lib/router/router.gr.dart +++ b/lib/router/router.gr.dart @@ -13,11 +13,14 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_route/auto_route.dart' as _i18; import 'package:auto_route/empty_router_widgets.dart' as _i5; +import 'package:flutter/foundation.dart' as _i27; import 'package:flutter/material.dart' as _i19; +import 'package:image_picker/image_picker.dart' as _i26; +import 'package:tuple/tuple.dart' as _i24; import '../model/aftercare.dart' as _i23; -import '../model/meal.dart' as _i24; -import '../model/user.dart' as _i25; +import '../model/meal.dart' as _i25; +import '../model/user.dart' as _i28; import '../screens/add_meal.dart' as _i14; import '../screens/chat.dart' as _i7; import '../screens/client_list.dart' as _i10; @@ -129,7 +132,7 @@ class AppRouter extends _i18.RootStackRouter { }, AddMealScreenRoute.name: (routeData) { final args = routeData.argsAs(); - return _i18.MaterialPageX<_i24.Meal?>( + return _i18.MaterialPageX<_i24.Tuple2<_i25.Meal?, _i26.XFile?>>( routeData: routeData, child: _i14.AddMealScreen( day: args.day, meal: args.meal, key: args.key)); @@ -289,7 +292,7 @@ class ProfileScreenRoute extends _i18.PageRouteInfo { /// generated route for /// [_i7.ChatScreen] class ChatScreenRoute extends _i18.PageRouteInfo { - ChatScreenRoute({_i19.Key? key, _i25.User? otherUser}) + ChatScreenRoute({_i27.Key? key, _i28.User? otherUser}) : super(ChatScreenRoute.name, path: 'onechat', args: ChatScreenRouteArgs(key: key, otherUser: otherUser)); @@ -300,9 +303,9 @@ class ChatScreenRoute extends _i18.PageRouteInfo { class ChatScreenRouteArgs { const ChatScreenRouteArgs({this.key, this.otherUser}); - final _i19.Key? key; + final _i27.Key? key; - final _i25.User? otherUser; + final _i28.User? otherUser; @override String toString() { @@ -323,7 +326,7 @@ class DiscussionListScreenRoute extends _i18.PageRouteInfo { /// [_i9.DocumentListScreen] class DocumentListScreenRoute extends _i18.PageRouteInfo { - DocumentListScreenRoute({_i19.Key? key, required _i25.User user}) + DocumentListScreenRoute({_i27.Key? key, required _i28.User user}) : super(DocumentListScreenRoute.name, path: 'documents', args: DocumentListScreenRouteArgs(key: key, user: user)); @@ -334,9 +337,9 @@ class DocumentListScreenRoute class DocumentListScreenRouteArgs { const DocumentListScreenRouteArgs({this.key, required this.user}); - final _i19.Key? key; + final _i27.Key? key; - final _i25.User user; + final _i28.User user; @override String toString() { @@ -375,7 +378,7 @@ class ClientListScreenRoute extends _i18.PageRouteInfo { /// [_i11.ClientRecordScreen] class ClientRecordScreenRoute extends _i18.PageRouteInfo { - ClientRecordScreenRoute({required _i25.User user, _i19.Key? key}) + ClientRecordScreenRoute({required _i28.User user, _i27.Key? key}) : super(ClientRecordScreenRoute.name, path: 'record', args: ClientRecordScreenRouteArgs(user: user, key: key)); @@ -386,9 +389,9 @@ class ClientRecordScreenRoute class ClientRecordScreenRouteArgs { const ClientRecordScreenRouteArgs({required this.user, this.key}); - final _i25.User user; + final _i28.User user; - final _i19.Key? key; + final _i27.Key? key; @override String toString() { @@ -401,7 +404,7 @@ class ClientRecordScreenRouteArgs { class UpdateClientRecordScreenRoute extends _i18.PageRouteInfo { UpdateClientRecordScreenRoute( - {required dynamic user, _i23.Aftercare? aftercare, _i19.Key? key}) + {required dynamic user, _i23.Aftercare? aftercare, _i27.Key? key}) : super(UpdateClientRecordScreenRoute.name, path: 'update', args: UpdateClientRecordScreenRouteArgs( @@ -418,7 +421,7 @@ class UpdateClientRecordScreenRouteArgs { final _i23.Aftercare? aftercare; - final _i19.Key? key; + final _i27.Key? key; @override String toString() { @@ -429,7 +432,7 @@ class UpdateClientRecordScreenRouteArgs { /// generated route for /// [_i13.DiaryScreen] class DiaryScreenRoute extends _i18.PageRouteInfo { - DiaryScreenRoute({_i25.User? client, _i19.Key? key}) + DiaryScreenRoute({_i28.User? client, _i27.Key? key}) : super(DiaryScreenRoute.name, path: '', args: DiaryScreenRouteArgs(client: client, key: key)); @@ -439,9 +442,9 @@ class DiaryScreenRoute extends _i18.PageRouteInfo { class DiaryScreenRouteArgs { const DiaryScreenRouteArgs({this.client, this.key}); - final _i25.User? client; + final _i28.User? client; - final _i19.Key? key; + final _i27.Key? key; @override String toString() { @@ -452,7 +455,7 @@ class DiaryScreenRouteArgs { /// generated route for /// [_i14.AddMealScreen] class AddMealScreenRoute extends _i18.PageRouteInfo { - AddMealScreenRoute({required DateTime day, _i24.Meal? meal, _i19.Key? key}) + AddMealScreenRoute({required DateTime day, _i25.Meal? meal, _i27.Key? key}) : super(AddMealScreenRoute.name, path: 'add', args: AddMealScreenRouteArgs(day: day, meal: meal, key: key)); @@ -465,9 +468,9 @@ class AddMealScreenRouteArgs { final DateTime day; - final _i24.Meal? meal; + final _i25.Meal? meal; - final _i19.Key? key; + final _i27.Key? key; @override String toString() { @@ -487,7 +490,7 @@ class RegisterFirstPageRoute extends _i18.PageRouteInfo { /// [_i16.RegisterSecondPage] class RegisterSecondPageRoute extends _i18.PageRouteInfo { - RegisterSecondPageRoute({_i19.Key? key}) + RegisterSecondPageRoute({_i27.Key? key}) : super(RegisterSecondPageRoute.name, path: '1', args: RegisterSecondPageRouteArgs(key: key)); @@ -497,7 +500,7 @@ class RegisterSecondPageRoute class RegisterSecondPageRouteArgs { const RegisterSecondPageRouteArgs({this.key}); - final _i19.Key? key; + final _i27.Key? key; @override String toString() { diff --git a/lib/screens/add_meal.dart b/lib/screens/add_meal.dart index 79825ca..0ddd843 100644 --- a/lib/screens/add_meal.dart +++ b/lib/screens/add_meal.dart @@ -9,6 +9,7 @@ import 'package:pdg_app/widgets/cards/main_card.dart'; import 'package:pdg_app/widgets/forms/main_text_field.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; import '../model/meal.dart'; import '../widgets/buttons/custom_icon_button.dart'; @@ -45,6 +46,7 @@ class _AddMealScreenState extends State { final TextEditingController _nameTextController = TextEditingController(); final TextEditingController _settingsController = TextEditingController(); final TextEditingController _commentController = TextEditingController(); + bool updatePic = false; @override void initState() { @@ -81,6 +83,18 @@ class _AddMealScreenState extends State { @override Widget build(BuildContext context) { + ImageProvider image = const AssetImage("assets/images/placeholderfood.png"); + + if (widget._meal != null && widget._meal!.photo != null) { + if (_image != null) { + image = XFileImage(_image!); + } else { + image = NetworkImage(widget._meal!.photo!); + } + } else if (_image != null) { + image = XFileImage(_image!); + } + return AddMeal( nameTextController: _nameTextController, settingsController: _settingsController, @@ -94,7 +108,7 @@ class _AddMealScreenState extends State { onHungerBeforeChanged: (value) => setState(() { _hungerBeforeValue = value; }), - image: _image, + image: image, onCameraPressed: () async { _image = await _takePicture(); setState(() {}); @@ -158,29 +172,33 @@ class _AddMealScreenState extends State { _endTime?.minute ?? 0, ); if (widget._meal == null) { - AutoRouter.of(context).pop(Meal( - title: _nameTextController.text, - startTime: selectedStartDate, - endTime: selectedEndDate, - hunger: _hungerBeforeValue.toInt(), - satiety: _hungerAfterValue.toInt(), - setting: _settingsController.text, - comment: _commentController.text, - owner: context.read().userUid, - )); + AutoRouter.of(context).pop(Tuple2( + Meal( + title: _nameTextController.text, + startTime: selectedStartDate, + endTime: selectedEndDate, + hunger: _hungerBeforeValue.toInt(), + satiety: _hungerAfterValue.toInt(), + setting: _settingsController.text, + comment: _commentController.text, + owner: context.read().userUid, + ), + _image)); return; } - AutoRouter.of(context).pop(Meal( - uid: widget._meal!.uid, - title: _nameTextController.text, - startTime: selectedStartDate, - endTime: selectedEndDate, - hunger: _hungerBeforeValue.toInt(), - satiety: _hungerAfterValue.toInt(), - setting: _settingsController.text, - comment: _commentController.text, - owner: context.read().userUid, - )); + AutoRouter.of(context).pop(Tuple2( + Meal( + uid: widget._meal!.uid, + title: _nameTextController.text, + startTime: selectedStartDate, + endTime: selectedEndDate, + hunger: _hungerBeforeValue.toInt(), + satiety: _hungerAfterValue.toInt(), + setting: _settingsController.text, + comment: _commentController.text, + owner: context.read().userUid, + ), + _image)); }, ); } @@ -193,7 +211,7 @@ class AddMeal extends StatelessWidget { final void Function(double) _onHungerAfterChanged; final void Function() _onCameraPressed; final void Function() _onGalleryPressed; - final XFile? _image; + final ImageProvider? _image; final void Function(TimeOfDay) _onTimeSelected; final void Function()? _onTimeSelectCanceled; final void Function()? _onStartTimeSelected; @@ -223,7 +241,7 @@ class AddMeal extends StatelessWidget { bool showTimePicker = false, String? startTimeText, String? endTimeText, - XFile? image, + ImageProvider? image, TextEditingController? nameTextController, TextEditingController? settingsController, TextEditingController? commentController, @@ -305,7 +323,7 @@ class _Top extends StatelessWidget { const _Top({ Key? key, required this.height, - required XFile? image, + required ImageProvider? image, required void Function() onCameraPressed, required void Function() onGalleryPressed, required bool isAdmin, @@ -316,7 +334,7 @@ class _Top extends StatelessWidget { super(key: key); final double height; - final XFile? _image; + final ImageProvider? _image; final void Function() _onCameraPressed; final void Function() _onGalleryPressed; final bool _isAdmin; @@ -331,13 +349,7 @@ class _Top extends StatelessWidget { height: height, width: double.infinity, decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: _image == null - ? const AssetImage("assets/images/placeholderfood.png") - as ImageProvider - : XFileImage(_image!), - ), + image: DecorationImage(fit: BoxFit.cover, image: _image!), ), ), !_isAdmin diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index bd11041..b14cc6a 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -235,10 +235,17 @@ class TopBar extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(horizontal: 10.0), child: CircleAvatar( - backgroundColor: Colors.grey, - radius: 35, - foregroundImage: _image, - ), + backgroundColor: Colors.white, + radius: 35, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: _image!, + fit: BoxFit.cover, + ), + ), + )), ), Text( name, diff --git a/lib/screens/client_list.dart b/lib/screens/client_list.dart index 12a38f9..d891537 100644 --- a/lib/screens/client_list.dart +++ b/lib/screens/client_list.dart @@ -149,8 +149,11 @@ class ScrollableClientList extends StatelessWidget { child: ArrowPicCard( title: Text( "${_clients[index].firstName} ${_clients[index].lastName}"), - imagePath: - 'assets/images/default_user_pic.png', //TODO à changer quand on aura les photos + defaultUserPic: + const AssetImage("assets/images/default_user_pic.png"), + image: _clients[index].photoUrl != null + ? NetworkImage(_clients[index].photoUrl!) + : null, ), )), separatorBuilder: ((context, index) => const SizedBox(height: 15)), diff --git a/lib/screens/client_record.dart b/lib/screens/client_record.dart index 6e280e2..24755bb 100644 --- a/lib/screens/client_record.dart +++ b/lib/screens/client_record.dart @@ -30,6 +30,8 @@ class ClientRecordScreen extends StatelessWidget { log(afterCareProvider.aftercare.toString()); final aftercareProvider = context.read(); return ClientRecord( + defaultUserPic: 'assets/images/default_user_pic.png', + clientPicturePath: _user.photoUrl, clientFirstName: _user.firstName, clientLastName: _user.lastName, clientEmail: _user.email, @@ -86,6 +88,8 @@ class ClientRecord extends StatelessWidget { final DateTime? _startDate; final DateTime? _endDate; final void Function()? _onIconButtonPressed; + final String _defaultUserPic; + final String? _clientPicturePath; final void Function()? _onDiariesButtonPressed; final void Function()? _onChatButtonPressed; //TODO décommenter @@ -105,8 +109,10 @@ class ClientRecord extends StatelessWidget { clientStartDate, clientEndDate, onIconButtonPressed, + String? clientPicturePath, onDiariesButtonPressed, onChatButtonPressed, + required String defaultUserPic, Key? key}) : _clientFirstName = clientFirstName, _clientLastName = clientLastName, @@ -123,6 +129,8 @@ class ClientRecord extends StatelessWidget { _startDate = clientStartDate, _endDate = clientEndDate, _onIconButtonPressed = onIconButtonPressed, + _defaultUserPic = defaultUserPic, + _clientPicturePath = clientPicturePath, _onDiariesButtonPressed = onDiariesButtonPressed, _onChatButtonPressed = onChatButtonPressed, super(key: key); @@ -139,6 +147,8 @@ class ClientRecord extends StatelessWidget { clientBirthday: _clientBirthday, clientInsurance: _clientInsurance, buttonIcon: Icons.create_outlined, + defaultUserPic: _defaultUserPic, + clientPicturePath: _clientPicturePath, onIconButtonPressed: _onIconButtonPressed ?? () {}, firstBloc: Column( children: [ diff --git a/lib/screens/diary.dart b/lib/screens/diary.dart index 5ba0f98..cc181c1 100644 --- a/lib/screens/diary.dart +++ b/lib/screens/diary.dart @@ -2,15 +2,18 @@ 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:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; import 'package:pdg_app/model/meal.dart'; import 'package:pdg_app/provider/meal_provider.dart'; import 'package:pdg_app/router/router.gr.dart'; import 'package:pdg_app/widgets/cards/arrow_pic_card.dart'; +import 'package:pdg_app/widgets/loading_overlay.dart'; import 'package:provider/provider.dart'; import 'package:table_calendar/table_calendar.dart'; import '../model/user.dart'; import '../api/firebase_file.dart'; +import 'package:tuple/tuple.dart'; import '../api/ifile.dart'; import '../provider/auth_provider.dart'; import '../widgets/buttons/action_button.dart'; @@ -33,6 +36,8 @@ class DiaryScreen extends StatefulWidget { class _DiaryScreenState extends State { DateTime _selectedDate = DateTime.now(); IFile fileApi = FirebaseFile(FirebaseStorage.instance); + final LoadingOverlayController _loadingOverlayController = + LoadingOverlayController(); _onDaySelected(DateTime day) async { _selectedDate = day; @@ -53,31 +58,46 @@ class _DiaryScreenState extends State { context.watch().meals; MealProvider mealProvider = context.read(); - return Diary( - onDaySelected: _onDaySelected, - showActionButton: !isAdmin, - getDiariesForDay: (day) { - return _getEventsForDay(context, day); - }, - clientName: !isAdmin - ? GetIt.I.get().user!.firstName - : widget._client!.firstName, - onAddPressed: () async { - final addedMeal = await AutoRouter.of(context) - .push(AddMealScreenRoute(day: _selectedDate)); - if (addedMeal != null) { - mealProvider.addMeal(addedMeal); - mealProvider.fetchMeals(); - } - }, - onMealBlocPressed: (Meal meal) async { - final changedMeal = await AutoRouter.of(context).push( - AddMealScreenRoute(day: _selectedDate, meal: meal)); - if (changedMeal != null) { - mealProvider.updateMeal(changedMeal); - mealProvider.fetchMeals(); - } - }); + AuthProvider authProvider = GetIt.I.get(); + + return LoadingOverlay( + controller: _loadingOverlayController, + child: Diary( + onDaySelected: _onDaySelected, + getDiariesForDay: (day) { + return _getEventsForDay(context, day); + }, + clientName: !isAdmin + ? GetIt.I.get().user!.firstName + : widget._client!.firstName, + clientPicturePath: authProvider.user!.photoUrl, + defaultUserPic: "assets/images/default_user_pic.png", + showActionButton: !isAdmin, + defaultMealPic: "assets/images/breakfast.jpg", + onAddPressed: () async { + final addedMeal = await AutoRouter.of(context) + .push>( + AddMealScreenRoute(day: _selectedDate)); + _loadingOverlayController.showLoadingOverlay(); + if (addedMeal != null) { + await mealProvider.addMeal(addedMeal.item1!, addedMeal.item2); + mealProvider.fetchMeals(); + } + _loadingOverlayController.hideLoadingOverlay(); + }, + onMealBlocPressed: (Meal meal) async { + final changedMeal = await AutoRouter.of(context) + .push>( + AddMealScreenRoute(day: _selectedDate, meal: meal)); + _loadingOverlayController.showLoadingOverlay(); + if (changedMeal != null) { + await mealProvider.updateMeal( + changedMeal.item1!, changedMeal.item2); + mealProvider.fetchMeals(); + } + _loadingOverlayController.hideLoadingOverlay(); + }), + ); }, ); } @@ -88,24 +108,30 @@ class Diary extends StatefulWidget { final bool showActionButton; final List Function(DateTime) getDiariesForDay; final String clientName; - final String clientPicturePath; + final String? clientPicturePath; final void Function()? _onAddPressed; final void Function(DateTime)? _onDaySelected; final void Function(Meal)? _onMealBlocPressed; + final String _defaultUserPic; + final String _defaultMealPic; const Diary({ this.screenWidth = 0, this.showActionButton = true, required this.getDiariesForDay, required this.clientName, - this.clientPicturePath = "assets/images/default_user_pic.png", + this.clientPicturePath, void Function(DateTime)? onDaySelected, void Function()? onAddPressed, void Function(Meal)? onMealBlocPressed, + required String defaultUserPic, + required String defaultMealPic, Key? key, }) : _onAddPressed = onAddPressed, _onDaySelected = onDaySelected, _onMealBlocPressed = onMealBlocPressed, + _defaultUserPic = defaultUserPic, + _defaultMealPic = defaultMealPic, super(key: key); @override @@ -148,6 +174,7 @@ class _DiaryState extends State { height: height, clientName: widget.clientName, clientPicturePath: widget.clientPicturePath, + defaultUserPic: widget._defaultUserPic, ), TableCalendar( firstDay: DateTime.utc(2020, 1, 1), @@ -190,6 +217,7 @@ class _DiaryState extends State { const SizedBox(height: 13), Expanded( child: _CalendarBody( + defaultMealPic: widget._defaultMealPic, hourFormatter: hourFormatter, meals: context.read().getMealsByDay(_selectedDay), onMealBlocPressed: widget._onMealBlocPressed, @@ -209,11 +237,13 @@ class _DiaryState extends State { class _CalendarBody extends StatelessWidget { final List meals; final void Function(Meal)? onMealBlocPressed; + final String defaultMealPic; const _CalendarBody({ required this.meals, Key? key, required this.hourFormatter, + required this.defaultMealPic, this.onMealBlocPressed, }) : super(key: key); @@ -235,6 +265,10 @@ class _CalendarBody extends StatelessWidget { vertical: 4.0, ), child: ArrowPicCard( + image: meals[index].photo != null + ? NetworkImage(meals[index].photo!) + : null, + defaultUserPic: AssetImage(defaultMealPic), title: Text(meals[index].title, style: const TextStyle(fontWeight: FontWeight.w600)), subtitle: Text( diff --git a/lib/screens/discussion_list.dart b/lib/screens/discussion_list.dart index 84f8e2c..a383e99 100644 --- a/lib/screens/discussion_list.dart +++ b/lib/screens/discussion_list.dart @@ -21,6 +21,22 @@ class DiscussionListScreen extends StatelessWidget { title: '${e.key.firstName} ${e.key.lastName}', subtitle: e.value.content, date: e.value.time, + avatar: CircleAvatar( + backgroundColor: Colors.white, + radius: 35, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: (e.key.photoUrl != null + ? NetworkImage(e.key.photoUrl!) + : const AssetImage( + "assets/images/default_user_pic.png")) + as ImageProvider, + fit: BoxFit.cover, + ), + ), + )), onTap: () { AutoRouter.of(context) .push(ChatScreenRoute(otherUser: e.key)); diff --git a/lib/screens/profile.dart b/lib/screens/profile.dart index bfac8e3..17565e6 100644 --- a/lib/screens/profile.dart +++ b/lib/screens/profile.dart @@ -51,6 +51,8 @@ class ProfileScreen extends StatelessWidget { clientPhone: user.phoneNumber, clientBirthday: user.birthDate, clientInsurance: user.avs, + defaultUserPic: 'assets/images/default_user_pic.png', + clientPicturePath: user.photoUrl, onLogoutPressed: () { GetIt.I.get().signOut(); context.router.replaceAll([ @@ -63,7 +65,7 @@ class ProfileScreen extends StatelessWidget { class Profile extends StatelessWidget { final double _screenWidth; - final String _clientPicturePath; + final String? _clientPicturePath; final String _clientFirstName; final String _clientLastName; final bool _blockEnabled; @@ -73,10 +75,11 @@ class Profile extends StatelessWidget { final DateTime _clientBirthday; final String _clientInsurance; final void Function()? _onLogoutPressed; + final String _defaultUserPic; const Profile({ screenWidth = 0.0, - clientPicturePath = 'assets/images/default_user_pic.png', + String? clientPicturePath, required clientFirstName, required clienLastName, required clientEmail, @@ -86,6 +89,7 @@ class Profile extends StatelessWidget { blockEnabled = false, blockText = "", void Function()? onLogoutPressed, + required String defaultUserPic, Key? key, }) : _screenWidth = screenWidth, _clientPicturePath = clientPicturePath, @@ -98,6 +102,7 @@ class Profile extends StatelessWidget { _clientBirthday = clientBirthday, _clientInsurance = clientInsurance, _onLogoutPressed = onLogoutPressed, + _defaultUserPic = defaultUserPic, super(key: key); @override @@ -113,6 +118,7 @@ class Profile extends StatelessWidget { clientInsurance: _clientInsurance, onIconButtonPressed: _onLogoutPressed, buttonIcon: Icons.logout_outlined, + defaultUserPic: _defaultUserPic, firstBloc: Column(children: [ _blockEnabled ? Column( diff --git a/lib/widgets/cards/arrow_pic_card.dart b/lib/widgets/cards/arrow_pic_card.dart index 7cf4571..24171a8 100644 --- a/lib/widgets/cards/arrow_pic_card.dart +++ b/lib/widgets/cards/arrow_pic_card.dart @@ -2,14 +2,16 @@ import 'package:flutter/material.dart'; import 'package:pdg_app/widgets/cards/pic_card.dart'; class ArrowPicCard extends StatelessWidget { - final String imagePath; + final ImageProvider? image; final Widget title; final Widget? subtitle; + final ImageProvider defaultUserPic; const ArrowPicCard({ required this.title, this.subtitle, - this.imagePath = 'assets/images/breakfast.jpg', + this.image, + required this.defaultUserPic, Key? key, }) : super(key: key); @@ -18,7 +20,7 @@ class ArrowPicCard extends StatelessWidget { return PicCard( title: title, subtitle: subtitle, - imagePath: imagePath, + image: image ?? defaultUserPic, icon: const Icon(Icons.keyboard_arrow_right_sharp), ); } diff --git a/lib/widgets/cards/pic_card.dart b/lib/widgets/cards/pic_card.dart index 0b9b095..f611d45 100644 --- a/lib/widgets/cards/pic_card.dart +++ b/lib/widgets/cards/pic_card.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'left_element_card.dart'; class PicCard extends StatelessWidget { - final String imagePath; + final ImageProvider image; final Widget title; final Widget? subtitle; final Icon? icon; @@ -11,7 +11,7 @@ class PicCard extends StatelessWidget { const PicCard({ required this.title, this.subtitle, - this.imagePath = 'assets/images/breakfast.jpg', + required this.image, this.icon, Key? key, }) : super(key: key); @@ -20,8 +20,20 @@ class PicCard extends StatelessWidget { Widget build(BuildContext context) { return LeftElementCard( element: CircleAvatar( - backgroundImage: AssetImage(imagePath), + // backgroundImage: NetworkImage(imagePath), + backgroundColor: Colors.white, radius: 66, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: image, + fit: BoxFit.cover, + ), + ), + + // borderRadius: BorderRadius.circular(50.0), + ), ), title: title, subtitle: subtitle, diff --git a/lib/widgets/diary/diary_top_bar.dart b/lib/widgets/diary/diary_top_bar.dart index 67ca73f..f728078 100644 --- a/lib/widgets/diary/diary_top_bar.dart +++ b/lib/widgets/diary/diary_top_bar.dart @@ -11,12 +11,14 @@ class DiaryTopBar extends StatelessWidget { required this.height, required this.clientName, required this.clientPicturePath, + required this.defaultUserPic, }) : super(key: key); final CustomPaint background; final double height; final String clientName; - final String clientPicturePath; + final String? clientPicturePath; + final String defaultUserPic; @override Widget build(BuildContext context) { @@ -30,7 +32,9 @@ class DiaryTopBar extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - ProfileAvatar(image: AssetImage(clientPicturePath)), + clientPicturePath != null + ? ProfileAvatar(image: NetworkImage(clientPicturePath!)) + : ProfileAvatar(image: AssetImage(defaultUserPic)), const SizedBox(height: 15), Text( !isAdmin ? "Hello $clientName" : "$clientName's diary", diff --git a/lib/widgets/profile/profile_top_bar.dart b/lib/widgets/profile/profile_top_bar.dart index 5c8177e..78b52c3 100644 --- a/lib/widgets/profile/profile_top_bar.dart +++ b/lib/widgets/profile/profile_top_bar.dart @@ -7,20 +7,22 @@ import '../profile_avatar.dart'; class ProfileTopBar extends StatelessWidget { final double _width; final double _height; - final String _clientPicturePath; + final String? _clientPicturePath; final String _clientFirstName; final String _clientLastName; final void Function()? _onIconButtonPress; final IconData? _buttonIcon; + final String _defaultUserPic; const ProfileTopBar({ Key? key, required width, required height, - required String clientPicturePath, + required String? clientPicturePath, required String clientFirstName, required String clientLastName, void Function()? onIconButtonPress, + required String defaultUserPic, buttonIcon, }) : _width = width, _height = height, @@ -29,6 +31,7 @@ class ProfileTopBar extends StatelessWidget { _clientLastName = clientLastName, _onIconButtonPress = onIconButtonPress, _buttonIcon = buttonIcon, + _defaultUserPic = defaultUserPic, super(key: key); @override @@ -45,7 +48,9 @@ class ProfileTopBar extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - ProfileAvatar(image: AssetImage(_clientPicturePath)), + _clientPicturePath != null + ? ProfileAvatar(image: NetworkImage(_clientPicturePath!)) + : ProfileAvatar(image: AssetImage(_defaultUserPic)), const SizedBox(height: 8), Text( "$_clientFirstName $_clientLastName", diff --git a/lib/widgets/profile_avatar.dart b/lib/widgets/profile_avatar.dart index 2132fb0..7985d7c 100644 --- a/lib/widgets/profile_avatar.dart +++ b/lib/widgets/profile_avatar.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class ProfileAvatar extends StatelessWidget { - final AssetImage image; + final ImageProvider image; const ProfileAvatar({required this.image, Key? key}) : super(key: key); @@ -16,9 +16,16 @@ class ProfileAvatar extends StatelessWidget { ) ], shape: BoxShape.circle), child: CircleAvatar( - backgroundImage: image, backgroundColor: Colors.white, radius: 45, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + image: DecorationImage( + image: image, + fit: BoxFit.cover, + ), + )), )); } } diff --git a/lib/widgets/profile_template.dart b/lib/widgets/profile_template.dart index 449f6e9..ca0cf11 100644 --- a/lib/widgets/profile_template.dart +++ b/lib/widgets/profile_template.dart @@ -6,7 +6,7 @@ import 'package:intl/intl.dart'; class ProfileTemplate extends StatelessWidget { final double _screenWidth; - final String _clientPicturePath; + final String? _clientPicturePath; final String _clientFirstName; final String _clientLastName; final String _clientEmail; @@ -17,10 +17,11 @@ class ProfileTemplate extends StatelessWidget { final IconData? _buttonIcon; final Widget? _firstBloc; final Widget? _lastBloc; + final String _defaultUserPic; const ProfileTemplate( {double screenWidth = 0.0, - String clientPicturePath = 'assets/images/default_user_pic.png', + String? clientPicturePath, required String clientFirstName, required String clientLastName, required String clientEmail, @@ -31,6 +32,7 @@ class ProfileTemplate extends StatelessWidget { IconData? buttonIcon, Widget? firstBloc, Widget? lastBloc, + required String defaultUserPic, Key? key}) : _screenWidth = screenWidth, _clientPicturePath = clientPicturePath, @@ -44,6 +46,7 @@ class ProfileTemplate extends StatelessWidget { _buttonIcon = buttonIcon, _firstBloc = firstBloc, _lastBloc = lastBloc, + _defaultUserPic = defaultUserPic, super(key: key); @override @@ -65,6 +68,7 @@ class ProfileTemplate extends StatelessWidget { clientLastName: _clientLastName, onIconButtonPress: _onIconButtonPressed, buttonIcon: _buttonIcon, + defaultUserPic: _defaultUserPic, ), const SizedBox(height: 15), Expanded( diff --git a/lib/widgets/register/register_third_page.dart b/lib/widgets/register/register_third_page.dart index e42f4f2..fd97ada 100644 --- a/lib/widgets/register/register_third_page.dart +++ b/lib/widgets/register/register_third_page.dart @@ -46,6 +46,7 @@ class _RegisterThirdPageState extends State { registerProvider.emailController.text, registerProvider.passwordController.text, registerProvider.createUser(), + registerProvider.profilePicture, ); registerProvider.loadingController.hideLoadingOverlay(); router.replaceAll([const HomeScreenRoute()]); diff --git a/pubspec.lock b/pubspec.lock index 3793474..f75e735 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -910,6 +910,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + tuple: + dependency: "direct main" + description: + name: tuple + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 252af4c..4afcc15 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dependencies: sorted_list: ^1.0.0 async: ^2.9.0 url_launcher: ^6.1.5 + tuple: ^2.0.0 flutter_svg: ^1.1.4 dev_dependencies: diff --git a/widgetbook/widgetbook.dart b/widgetbook/widgetbook.dart index 7b45d26..ffb9e0d 100644 --- a/widgetbook/widgetbook.dart +++ b/widgetbook/widgetbook.dart @@ -83,6 +83,7 @@ class HotReload extends StatelessWidget { WidgetbookUseCase( name: 'Default', builder: (context) => PicCard( + image: const AssetImage("assets/images/breakfast.jpg"), title: Text(context.knobs.text( label: 'title', initialValue: "Déjeuner", @@ -102,6 +103,7 @@ class HotReload extends StatelessWidget { WidgetbookUseCase( name: 'Default', builder: (context) => ArrowPicCard( + defaultUserPic: const AssetImage("assets/images/breakfast.jpg"), title: Text(context.knobs.text( label: 'title', initialValue: "Déjeuner", @@ -189,6 +191,8 @@ class HotReload extends StatelessWidget { name: 'Default', builder: (context) { return Diary( + defaultUserPic: 'assets/images/default_user_pic.png', + defaultMealPic: "assets/images/breakfast.jpg", getDiariesForDay: (datetime) { return [ Meal( @@ -217,6 +221,7 @@ class HotReload extends StatelessWidget { name: 'Default', builder: (context) { return Profile( + defaultUserPic: 'assets/images/default_user_pic.png', clientFirstName: context.knobs.text( label: 'First name', initialValue: "Luca",