From 439e94eceda845ece3abe9359925782d0e96e9ed Mon Sep 17 00:00:00 2001 From: Jumpeee Date: Mon, 10 Jul 2023 20:26:27 +0200 Subject: [PATCH] Sending friend request, fixed sending image while creating party, code refactoring --- l10n.yaml | 1 + lib/controllers/party_controller.dart | 49 +++++++++++------- lib/controllers/user_controller.dart | 8 ++- lib/l10n/app_en.arb | 16 +++--- lib/l10n/app_pl.arb | 16 +++--- lib/routes/create_party_page.dart | 34 ++++--------- lib/routes/notifications_page.dart | 45 +++++++++-------- lib/routes/selected_party_page.dart | 17 ++++++- .../settings_routes/edit_profile_page.dart | 17 +++++-- ...ation_sheet.dart => invitation_sheet.dart} | 5 +- .../notificationspage/category_row.dart | 50 +++++++++++++++++++ lib/widgets/profilepage/user_info.dart | 40 +++++++++------ untranslated.txt | 1 + 13 files changed, 195 insertions(+), 104 deletions(-) rename lib/widgets/dialogs/{notification_sheet.dart => invitation_sheet.dart} (62%) create mode 100644 lib/widgets/notificationspage/category_row.dart create mode 100644 untranslated.txt diff --git a/l10n.yaml b/l10n.yaml index 15338f2..44897db 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,3 +1,4 @@ arb-dir: lib/l10n template-arb-file: app_en.arb output-localization-file: app_localizations.dart +untranslated-messages-file: untranslated.txt diff --git a/lib/controllers/party_controller.dart b/lib/controllers/party_controller.dart index 9c5613c..7d4e018 100644 --- a/lib/controllers/party_controller.dart +++ b/lib/controllers/party_controller.dart @@ -1,6 +1,8 @@ import 'dart:convert'; +import 'dart:io'; import 'package:http/http.dart' as http; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:http_parser/http_parser.dart'; import '../utils/consts.dart'; import '../models/party.dart'; @@ -68,25 +70,34 @@ class PartyController { const storage = FlutterSecureStorage(); final token = await storage.read(key: "access"); final url = "$mainUrl/parties/"; - //TODO send image in HTTP request - final res = await http.post( - Uri.parse(url), - body: jsonEncode({ - "owner": {}, - "owner_public_id": party.ownerPublicId, - "name": party.name, - "privacy_status": party.privacyStatus, - "description": party.description, - "location": party.location!.toPOINT(), - "start_time": party.startTime.toIso8601String(), - "stop_time": party.stopTime.toIso8601String(), - "participants": [for (final p in party.participants!) p.toMap()], - }), - headers: { - "Content-Type": "application/json", - "Authorization": "Bearer $token", - }, - ); + final req = http.MultipartRequest("POST", Uri.parse(url)); + req.fields.addAll({ + "owner_public_id": party.ownerPublicId!, + "name": party.name, + "privacy_status": party.privacyStatus.toString(), + "description": party.description, + "location": party.location!.toPOINT(), + "start_time": party.startTime.toIso8601String(), + "stop_time": party.stopTime.toIso8601String(), + "participants": + jsonEncode([for (final p in party.participants!) p.toMap()]), + }); + req.headers.addAll({ + "Content-Type": "multipart/form-data", + "Authorization": "Bearer $token", + }); + if (party.image != null) { + req.files.add( + http.MultipartFile( + "image", + File(party.image!).readAsBytes().asStream(), + File(party.image!).lengthSync(), + filename: party.image!, + contentType: MediaType("image", "jpeg"), + ), + ); + } + final res = await req.send(); return res.statusCode == 201; } } diff --git a/lib/controllers/user_controller.dart b/lib/controllers/user_controller.dart index 4a2ac2d..7c67fbc 100644 --- a/lib/controllers/user_controller.dart +++ b/lib/controllers/user_controller.dart @@ -91,14 +91,20 @@ class UserController { } static Future sendFriendInvitation(FriendInvitation inv) async { + const storage = FlutterSecureStorage(); + final token = await storage.read(key: "access"); final url = "$mainUrl/friends/invitations/"; final res = await http.post( Uri.parse(url), - body: { + body: jsonEncode({ "sender": {}, "receiver": {}, "receiver_public_id": inv.receiverPublicId, "sender_public_id": inv.senderPublicId, + }), + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer $token", }, ); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 16037a7..a16609c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -25,17 +25,17 @@ "partiesProfile2": "Parties", "notificationsNotifications": "Notifications", - "invitationFrom": "Invitation from", + "notificationFrom": "From", "secondsAgo": "seconds ago", "minutesAgo": "minutes ago", "hoursAgo": "hours ago", - "markAsRead": "Mark as read", + "friendInvitations": "Friend invitations", + "partyInvitations": "Party invitations", + "joinRequests": "Join requests", "settings": "Settings", "editProfile": "Edit profile", "organization": "Organization", - "language": "Language", - "privacy": "Privacy", "logOut": "Log out", "editProfilePage": "Edit profile", @@ -59,13 +59,11 @@ "sureDeleteAccount": "Delete your account?", "deleteAccount": "Delete my account", "doNotDeleteAccount": "Don't delete my account", + "updatedSuccess": "Account updated successfully", + "updatedFailure": "Could not update profile", "yourParties": "Your parties", - "chooseALanguage": "Choose a language", - "polish": "Polski", - "english": "English", - "privacyPolicy": "Privacy policy", "doYouWantToLogOut": "Do you want to log out?", @@ -78,6 +76,8 @@ "join": "Join", "error": "Error!", "mapError": "It seems like you don't have any maps installed on your device.", + "joinRequestSuccess": "Join request sent", + "joinRequestFailure": "Could not send join request", "logIn": "Log in", "fillFieldsBelow": "Fill the fields below to continue.", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b870f27..eb84cd9 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -25,17 +25,17 @@ "partiesProfile2": "Imprezy", "notificationsNotifications": "Powiadomienia", - "invitationFrom": "Zaproszenie od", + "notificationFrom": "Od", "secondsAgo": "sekund temu", "minutesAgo": "minut temu", "hoursAgo": "godzin temu", - "markAsRead": "Oznacz jako przeczytane", + "friendInvitations": "Zaproszenia do znajomych", + "partyInvitations": "Zaproszenia na imprezę", + "joinRequests": "Prośby o dołączenie", "settings": "Ustawienia", "editProfile": "Edytuj profil", "organization": "Organizacja", - "language": "Język", - "privacy": "Prywatność", "logOut": "Wyloguj się", "editProfilePage": "Edytuj profil", @@ -59,13 +59,11 @@ "sureDeleteAccount": "Napewno chcesz usunąć konto?", "deleteAccount": "Usuń moje konto", "doNotDeleteAccount": "Nie usuwaj konta", + "updatedSuccess": "Pomyślnie zaaktualizowano profil", + "updatedFailure": "Nie zaaktualizowano profilu ", "yourParties": "Twoje imprezy", - "chooseALanguage": "Wybierz język", - "polish": "Polski", - "english": "English", - "privacyPolicy": "Polityka prywatności", "doYouWantToLogOut": "Wylogować cię?", @@ -78,6 +76,8 @@ "join": "Dołącz", "error": "Błąd!", "mapError": "Wygląda na to, że nie masz zainstalowanych map na swoim urzadzeniu.", + "joinRequestSuccess": "Wysłano prośbę o dołączenie", + "joinRequestFailure": "Nie wysłano prośby", "logIn": "Zaloguj się", "fillFieldsBelow": "Wypełnij poniższe pola, aby kontynuować.", diff --git a/lib/routes/create_party_page.dart b/lib/routes/create_party_page.dart index 6345628..07bfd0a 100644 --- a/lib/routes/create_party_page.dart +++ b/lib/routes/create_party_page.dart @@ -191,29 +191,17 @@ class _CreatePartyRouteState extends State { image: thumbnail?.path, ), ); - if (isCreated && mounted) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Theming.bgColor, - builder: (_) => SuccessSheet( - success: true, - successMsg: AppLocalizations.of(context)!.createdSuccessfuly, - failureMsg: AppLocalizations.of(context)!.creationFailed, - ), - ); - } else { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Theming.bgColor, - builder: (_) => SuccessSheet( - success: false, - successMsg: AppLocalizations.of(context)!.createdSuccessfuly, - failureMsg: AppLocalizations.of(context)!.creationFailed, - ), - ); - } + if (!mounted) return; + showModalBottomSheet( + context: context, + isScrollControlled: isCreated, + backgroundColor: Theming.bgColor, + builder: (_) => SuccessSheet( + success: false, + successMsg: AppLocalizations.of(context)!.createdSuccessfuly, + failureMsg: AppLocalizations.of(context)!.creationFailed, + ), + ); }, ), ), diff --git a/lib/routes/notifications_page.dart b/lib/routes/notifications_page.dart index 4280b1f..595c44e 100644 --- a/lib/routes/notifications_page.dart +++ b/lib/routes/notifications_page.dart @@ -3,13 +3,13 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../utils/theming.dart'; -import '../widgets/dialogs/notification_sheet.dart'; -import '../widgets/custom_floating_button.dart'; +import '../widgets/dialogs/invitation_sheet.dart'; import '../controllers/party_creator_controller.dart'; import '../controllers/user_controller.dart'; import '../models/friend_invitiation.dart'; import '../models/party_invitation.dart'; import '../models/party_request.dart'; +import '../widgets/notificationspage/category_row.dart'; class NotificationsPage extends StatefulWidget { const NotificationsPage({super.key}); @@ -18,7 +18,10 @@ class NotificationsPage extends StatefulWidget { State createState() => _NotificationsPageState(); } -class _NotificationsPageState extends State { +class _NotificationsPageState extends State + with SingleTickerProviderStateMixin { + late final TabController _tabCtrl; + late List friendInvs; late List partyInvs; late List partyReqs; @@ -26,6 +29,11 @@ class _NotificationsPageState extends State { @override void initState() { super.initState(); + _tabCtrl = TabController( + length: 3, + vsync: this, + initialIndex: 0, + ); friendInvs = []; partyInvs = []; partyReqs = []; @@ -36,20 +44,22 @@ class _NotificationsPageState extends State { }); } + @override + void dispose() { + super.dispose(); + _tabCtrl.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Theming.bgColor, - floatingActionButton: CustomFloatingButton( - caption: AppLocalizations.of(context)!.markAsRead, - onTap: () {}, - ), body: CustomScrollView( slivers: [ SliverAppBar( backgroundColor: Theming.bgColor, surfaceTintColor: Theming.bgColor, - expandedHeight: 115, + expandedHeight: 150, pinned: true, centerTitle: true, shadowColor: Theming.bgColor, @@ -72,16 +82,9 @@ class _NotificationsPageState extends State { ), ), ), - flexibleSpace: const FlexibleSpaceBar( - centerTitle: true, - title: Text( - "5 notifications", - style: TextStyle( - color: Theming.primaryColor, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), + bottom: PreferredSize( + preferredSize: const Size.fromHeight(54), + child: CategoryRow(_tabCtrl), ), ), SliverList( @@ -100,12 +103,12 @@ class _NotificationsPageState extends State { ); } - Widget _notificationItem(BuildContext ctx) { + Widget _notificationItem(/*Friend friend, */ BuildContext ctx) { return InkWell( onTap: () { showModalBottomSheet( context: ctx, - builder: (_) => const NotificationSheet(), + builder: (_) => const InvitationSheet(), ); }, splashColor: Theming.whiteTone.withOpacity(0.05), @@ -162,7 +165,7 @@ class _NotificationsPageState extends State { text: TextSpan( children: [ TextSpan( - text: AppLocalizations.of(ctx)!.invitationFrom, + text: AppLocalizations.of(ctx)!.notificationFrom, style: const TextStyle( color: Theming.whiteTone, fontWeight: FontWeight.bold, diff --git a/lib/routes/selected_party_page.dart b/lib/routes/selected_party_page.dart index 43703e3..532246e 100644 --- a/lib/routes/selected_party_page.dart +++ b/lib/routes/selected_party_page.dart @@ -1,3 +1,5 @@ +import 'package:drinkify/controllers/party_controller.dart'; +import 'package:drinkify/widgets/dialogs/success_sheet.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; @@ -79,9 +81,20 @@ class _SelectedPartyPage extends State { floatingActionButton: Padding( padding: const EdgeInsets.only(right: 14, bottom: 25), child: GestureDetector( - onTap: () { + onTap: () async { if (!showMore) return; - //TODO implement joining party + final success = await PartyController.sendJoinRequest(widget.party); + if (!mounted) return; + showModalBottomSheet( + context: context, + builder: (ctx) { + return SuccessSheet( + success: success, + successMsg: AppLocalizations.of(context)!.joinRequestSuccess, + failureMsg: AppLocalizations.of(context)!.joinRequestFailure, + ); + }, + ); }, child: GlassMorphism( blur: showMore ? 10 : 0, diff --git a/lib/routes/settings_routes/edit_profile_page.dart b/lib/routes/settings_routes/edit_profile_page.dart index 6e1f9cf..40136a6 100644 --- a/lib/routes/settings_routes/edit_profile_page.dart +++ b/lib/routes/settings_routes/edit_profile_page.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:drinkify/widgets/dialogs/success_sheet.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -66,7 +67,7 @@ class _EditProfilePageState extends State { backgroundColor: Theming.bgColor, floatingActionButton: CustomFloatingButton( caption: AppLocalizations.of(context)!.save, - onTap: () { + onTap: () async { errorFields = []; final allFields = [ ".", @@ -80,7 +81,7 @@ class _EditProfilePageState extends State { } setState(() => errorFields = tempErrorList); if (errorFields.isNotEmpty) return; - UserController.updateMe( + final success = await UserController.updateMe( User( firstName: firstNameCtrl.text, lastName: lastNameCtrl.text, @@ -89,7 +90,17 @@ class _EditProfilePageState extends State { pfp: pfp?.path, ), ); - context.pop(); + if (!mounted) return; + showModalBottomSheet( + context: context, + builder: (ctx) { + return SuccessSheet( + success: success, + successMsg: AppLocalizations.of(context)!.updatedSuccess, + failureMsg: AppLocalizations.of(context)!.updatedFailure, + ); + }, + ); }, ), body: CustomScrollView( diff --git a/lib/widgets/dialogs/notification_sheet.dart b/lib/widgets/dialogs/invitation_sheet.dart similarity index 62% rename from lib/widgets/dialogs/notification_sheet.dart rename to lib/widgets/dialogs/invitation_sheet.dart index c22c5d3..f4452e5 100644 --- a/lib/widgets/dialogs/notification_sheet.dart +++ b/lib/widgets/dialogs/invitation_sheet.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; ///Used to display all information from the notification -class NotificationSheet extends StatelessWidget { - const NotificationSheet({super.key}); +class InvitationSheet extends StatelessWidget { + const InvitationSheet({super.key}); - //TODO implement it @override Widget build(BuildContext context) { return const SizedBox(); diff --git a/lib/widgets/notificationspage/category_row.dart b/lib/widgets/notificationspage/category_row.dart new file mode 100644 index 0000000..76d45d2 --- /dev/null +++ b/lib/widgets/notificationspage/category_row.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '/utils/theming.dart'; + +class CategoryRow extends StatelessWidget { + final TabController ctrl; + const CategoryRow( + this.ctrl, { + super.key, + }); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 30), + child: SizedBox( + height: 50, + width: double.infinity, + child: TabBar( + isScrollable: true, + controller: ctrl, + indicatorColor: Theming.primaryColor, + dividerColor: Theming.bgColor, + splashFactory: NoSplash.splashFactory, + tabs: [ + _category(0, AppLocalizations.of(context)!.friendInvitations), + _category(1, AppLocalizations.of(context)!.partyInvitations), + _category(2, AppLocalizations.of(context)!.joinRequests), + ], + ), + ), + ); + } + + Widget _category(int index, String caption) { + return Container( + alignment: Alignment.center, + margin: const EdgeInsets.only(right: 10), + padding: const EdgeInsets.all(15), + child: Text( + caption, + style: TextStyle( + color: Theming.whiteTone.withOpacity(0.7), + fontWeight: FontWeight.bold, + ), + ), + ); + } +} diff --git a/lib/widgets/profilepage/user_info.dart b/lib/widgets/profilepage/user_info.dart index a5477e8..fb57ecf 100644 --- a/lib/widgets/profilepage/user_info.dart +++ b/lib/widgets/profilepage/user_info.dart @@ -1,3 +1,4 @@ +import 'package:drinkify/models/friend_invitiation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -6,6 +7,7 @@ import '/utils/theming.dart'; import '/widgets/dialogs/success_sheet.dart'; import '/models/friend.dart'; import '/models/user.dart'; +import '/controllers/user_controller.dart'; class UserInfo extends StatefulWidget { final User? user; @@ -86,24 +88,30 @@ class _UserInfoState extends State { //Add friend GestureDetector( onTap: () async { - //sending a friend request const storage = FlutterSecureStorage(); final userId = await storage.read(key: "user_publicId"); - if (widget.user!.publicId! == userId && mounted) { - showModalBottomSheet( - context: context, - useRootNavigator: true, - backgroundColor: Theming.bgColor, - builder: (ctx) { - return SuccessSheet( - success: false, - successMsg: - AppLocalizations.of(context)!.successFriendInvite, - failureMsg: - AppLocalizations.of(context)!.failureFriendInvite, - ); - }); - } + if (widget.user!.publicId == userId) return; + final success = await UserController.sendFriendInvitation( + FriendInvitation( + receiverPublicId: widget.friend!.publicId!, + senderPublicId: userId!, + createdAt: DateTime.now(), + ), + ); + if (!mounted) return; + showModalBottomSheet( + context: context, + useRootNavigator: true, + backgroundColor: Theming.bgColor, + builder: (ctx) { + return SuccessSheet( + success: success, + successMsg: + AppLocalizations.of(context)!.successFriendInvite, + failureMsg: + AppLocalizations.of(context)!.failureFriendInvite, + ); + }); }, child: Container( height: 50, diff --git a/untranslated.txt b/untranslated.txt new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/untranslated.txt @@ -0,0 +1 @@ +{} \ No newline at end of file