From 4f1fc054ade83dd28b45f5b837bd315dd4754503 Mon Sep 17 00:00:00 2001 From: oltimaloku Date: Sat, 1 Jun 2024 09:52:54 -0700 Subject: [PATCH] ref: use User objects everywhere instead of both User and OtherUser --- client/assets/map/style.json | 1 + .../friends/services/user_service.dart | 27 +++++++------- .../features/friends/view/friends_screen.dart | 2 +- .../friends/view/user_profile_screen.dart | 8 +++-- .../friends/view_model/friend_view_model.dart | 11 +++--- .../friends/widgets/profile_layout.dart | 12 +++++-- .../friends/widgets/user_search_widget.dart | 24 ++++++++----- .../profile/views/profile_photo_edit.dart | 4 +-- .../profile/views/profile_screen.dart | 1 + client/lib/models/user.dart | 19 +++++++--- georeal/models.py | 3 ++ georeal/routes/users.py | 35 ++++++++++--------- 12 files changed, 89 insertions(+), 58 deletions(-) diff --git a/client/assets/map/style.json b/client/assets/map/style.json index 317bb00..dc7177c 100644 --- a/client/assets/map/style.json +++ b/client/assets/map/style.json @@ -127,6 +127,7 @@ "elementType": "labels.text.fill", "stylers": [ { + "color": "#98a5be" } ] diff --git a/client/lib/features/friends/services/user_service.dart b/client/lib/features/friends/services/user_service.dart index 98bcc23..98f8d00 100644 --- a/client/lib/features/friends/services/user_service.dart +++ b/client/lib/features/friends/services/user_service.dart @@ -3,19 +3,21 @@ import 'dart:developer'; import 'package:georeal/constants/env_variables.dart'; import 'package:georeal/models/friend_request.dart'; -import 'package:georeal/models/other_user.dart'; +import 'package:georeal/models/user.dart'; import 'package:http/http.dart' as http; class UserService { - static Future> getAllUsers() async { + static Future> getAllUsers() async { try { final response = await http.get(Uri.parse('${EnvVariables.uri}/users')); if (response.statusCode == 200) { final List usersJson = json.decode(response.body); - log(usersJson.toString()); - List users = - usersJson.map((user) => OtherUser.fromMap(user)).toList(); + log(usersJson.toString(), name: 'getAllUsers'); + for (var user in usersJson) { + log(user.toString(), name: 'getAllUsers'); + } + List users = usersJson.map((user) => User.fromMap(user)).toList(); return users; } else { @@ -28,10 +30,8 @@ class UserService { } } - static Future getUserByUsername( - String username, int userId) async { + static Future getUserByUsername(String username, int userId) async { try { - log('Fetching user with username: $username'); final response = await http.get( Uri.parse( '${EnvVariables.uri}/user?username=${Uri.encodeComponent(username)}&user_id=${Uri.encodeComponent(userId.toString())}'), @@ -40,7 +40,7 @@ class UserService { if (response.statusCode == 200) { final userJson = json.decode(response.body); - return OtherUser.fromMap(userJson); + return User.fromMap(userJson); } else { throw Exception( 'Failed to load user with status code: ${response.statusCode}'); @@ -53,7 +53,6 @@ class UserService { static Future sendFriendRequest(int senderId, int receiverId) async { try { - log('Sending friend request from $senderId to $receiverId'); http.Response response = await http.post( Uri.parse( '${EnvVariables.uri}/users/friend_request?sender_id=${Uri.encodeComponent(senderId.toString())}&receiver_id=${Uri.encodeComponent(receiverId.toString())}'), @@ -134,16 +133,16 @@ class UserService { } } - static Future> searchUsers(String query) async { + static Future> searchUsers(String query) async { try { http.Response response = await http.get( Uri.parse( '${EnvVariables.uri}/users/search?query=${Uri.encodeComponent(query)}'), ); - List users = []; - log('Searching users: ${response.body}'); + List users = []; + for (var user in json.decode(response.body)) { - users.add(OtherUser.fromMap(user)); + users.add(User.fromMap(user)); } return users; } catch (e) { diff --git a/client/lib/features/friends/view/friends_screen.dart b/client/lib/features/friends/view/friends_screen.dart index fd27666..bbf8173 100644 --- a/client/lib/features/friends/view/friends_screen.dart +++ b/client/lib/features/friends/view/friends_screen.dart @@ -85,7 +85,7 @@ class FriendsScreen extends StatelessWidget { itemBuilder: (context, index) { final friend = model.searchedUsers[index]; return UserSearchWidget( - username: friend.username, + user: friend, ); }, ); diff --git a/client/lib/features/friends/view/user_profile_screen.dart b/client/lib/features/friends/view/user_profile_screen.dart index 32add28..512baf2 100644 --- a/client/lib/features/friends/view/user_profile_screen.dart +++ b/client/lib/features/friends/view/user_profile_screen.dart @@ -4,11 +4,11 @@ import 'package:georeal/features/friends/widgets/profile_layout.dart'; import 'package:georeal/features/geo_sphere/view_model/geo_sphere_view_model.dart'; import 'package:georeal/features/geo_sphere/widgets/geo_sphere_widget.dart'; import 'package:georeal/global_variables.dart'; -import 'package:georeal/models/other_user.dart'; +import 'package:georeal/models/user.dart'; import 'package:provider/provider.dart'; class UserProfileScreen extends StatefulWidget { - final OtherUser user; + final User user; const UserProfileScreen({ super.key, required this.user, @@ -44,7 +44,9 @@ class _UserProfileScreenState extends State { body: SafeArea( child: Column( children: [ - const ProfileLayout(), + ProfileLayout( + user: widget.user, + ), Expanded( child: Consumer( builder: (context, geoSphereViewModel, child) { diff --git a/client/lib/features/friends/view_model/friend_view_model.dart b/client/lib/features/friends/view_model/friend_view_model.dart index c6a28e0..e6c8271 100644 --- a/client/lib/features/friends/view_model/friend_view_model.dart +++ b/client/lib/features/friends/view_model/friend_view_model.dart @@ -2,15 +2,15 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:georeal/features/friends/services/user_service.dart'; -import 'package:georeal/models/other_user.dart'; +import 'package:georeal/models/user.dart'; class FriendViewModel extends ChangeNotifier { - List _searchedUsers = []; - OtherUser? _selectedUser; + List _searchedUsers = []; + User? _selectedUser; TextEditingController searchController = TextEditingController(); - List get searchedUsers => _searchedUsers; - OtherUser? get selectedUser => _selectedUser; + List get searchedUsers => _searchedUsers; + User? get selectedUser => _selectedUser; void fetchUsers() async { try { @@ -41,7 +41,6 @@ class FriendViewModel extends ChangeNotifier { Future searchUsers() async { try { _searchedUsers = await UserService.searchUsers(searchController.text); - log(_searchedUsers.toString(), name: 'Searched users'); notifyListeners(); } catch (e) { log(e.toString(), name: 'searchUsers'); diff --git a/client/lib/features/friends/widgets/profile_layout.dart b/client/lib/features/friends/widgets/profile_layout.dart index 5dbadfc..5f5f3bb 100644 --- a/client/lib/features/friends/widgets/profile_layout.dart +++ b/client/lib/features/friends/widgets/profile_layout.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; import 'package:georeal/common/profile_photo.dart'; import 'package:georeal/features/friends/view_model/friend_view_model.dart'; +import 'package:georeal/models/user.dart'; import 'package:georeal/providers/user_provider'; import 'package:provider/provider.dart'; class ProfileLayout extends StatefulWidget { - const ProfileLayout({super.key}); + final User user; + const ProfileLayout({super.key, required this.user}); @override State createState() => _ProfileLayoutState(); @@ -26,7 +28,7 @@ class _ProfileLayoutState extends State { senderUsername, viewModel.selectedUser!.id); setState(() => _isRequested = true); } else { - // Handle error or invalid state + // TODO: Handle error or invalid state } } finally { setState(() => _isProcessing = false); @@ -45,7 +47,11 @@ class _ProfileLayoutState extends State { children: [ ProfilePhoto( radius: 40, - image: Image.asset("assets/images/profile_photo.jpg"), + image: widget.user.profilePhotoUrl != null + ? Image.network(widget.user.profilePhotoUrl!) + : const Image( + image: AssetImage('assets/images/default_profile.png'), + ), ), Column( children: [ diff --git a/client/lib/features/friends/widgets/user_search_widget.dart b/client/lib/features/friends/widgets/user_search_widget.dart index 39a5b71..5e3a055 100644 --- a/client/lib/features/friends/widgets/user_search_widget.dart +++ b/client/lib/features/friends/widgets/user_search_widget.dart @@ -3,14 +3,20 @@ import 'package:georeal/common/profile_photo.dart'; import 'package:georeal/features/friends/view/user_profile_screen.dart'; import 'package:georeal/features/friends/view_model/friend_view_model.dart'; import 'package:georeal/global_variables.dart'; +import 'package:georeal/models/user.dart'; import 'package:georeal/providers/user_provider'; import 'package:provider/provider.dart'; -class UserSearchWidget extends StatelessWidget { - final String username; +class UserSearchWidget extends StatefulWidget { + final User user; - const UserSearchWidget({super.key, required this.username}); + const UserSearchWidget({super.key, required this.user}); + @override + State createState() => _UserSearchWidgetState(); +} + +class _UserSearchWidgetState extends State { @override Widget build(BuildContext context) { return GestureDetector( @@ -26,11 +32,15 @@ class UserSearchWidget extends StatelessWidget { children: [ ProfilePhoto( radius: 20, - image: Image.asset("assets/images/profile_photo.jpg"), + image: widget.user.profilePhotoUrl != null + ? Image.network(widget.user.profilePhotoUrl!) + : const Image( + image: AssetImage('assets/images/default_profile.png'), + ), ), const SizedBox(width: 10), Text( - username, + widget.user.username, style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold), ), @@ -48,7 +58,6 @@ class UserSearchWidget extends StatelessWidget { )); } - await viewModel.getUserByUsername(username, userProvider.user!.id); if (context.mounted) { if (viewModel.selectedUser == null) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar( @@ -59,8 +68,7 @@ class UserSearchWidget extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => - UserProfileScreen(user: viewModel.selectedUser!), + builder: (context) => UserProfileScreen(user: widget.user), ), ); } diff --git a/client/lib/features/profile/views/profile_photo_edit.dart b/client/lib/features/profile/views/profile_photo_edit.dart index 177a10d..0bce0ec 100644 --- a/client/lib/features/profile/views/profile_photo_edit.dart +++ b/client/lib/features/profile/views/profile_photo_edit.dart @@ -1,6 +1,4 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:georeal/common/profile_photo.dart'; import 'package:georeal/features/profile/view_model/profile_view_model.dart'; @@ -21,7 +19,7 @@ class ProfilePhotoEditScreen extends StatelessWidget { final model = Provider.of(context, listen: false); final user = Provider.of(context, listen: false).user!; model.fetchProfilePhoto(user.id); - log(user.profilePhotoUrl ?? 'No Photo Url'); + return Scaffold( backgroundColor: Theme.of(context).scaffoldBackgroundColor, appBar: AppBar( diff --git a/client/lib/features/profile/views/profile_screen.dart b/client/lib/features/profile/views/profile_screen.dart index 6c0d937..eb253e6 100644 --- a/client/lib/features/profile/views/profile_screen.dart +++ b/client/lib/features/profile/views/profile_screen.dart @@ -30,6 +30,7 @@ class _ProfileScreenState extends State { @override Widget build(BuildContext context) { final user = Provider.of(context, listen: false).user!; + log(user.profilePhotoUrl ?? "No profile photo", name: "ProfileScreen"); return Scaffold( appBar: AppBar( backgroundColor: GlobalVariables.backgroundColor, diff --git a/client/lib/models/user.dart b/client/lib/models/user.dart index 675ab8d..5d2b1a5 100644 --- a/client/lib/models/user.dart +++ b/client/lib/models/user.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:georeal/constants/env_variables.dart'; class User { @@ -8,9 +10,9 @@ class User { final int numPlaces; final int numPosts; final int numFriends; - String? - profilePhotoUrl; // Assuming profile photo is managed separately or not directly included in this model - // Assuming lastLocation, places, posts, and friends are managed separately or not directly included in this model + String? profilePhotoUrl; + List? + friendsIds; // IDs of friends, to be used primarily for the logged-in user User({ required this.id, @@ -21,15 +23,17 @@ class User { this.numPlaces = 0, this.numPosts = 0, this.numFriends = 0, + this.friendsIds, }); factory User.fromMap(Map data) { + log(data.toString(), name: 'User.fromMap'); String? profilePhotoPath = data['profile_photo']; String? fullProfilePhotoUrl = profilePhotoPath != null ? '${EnvVariables.uri}$profilePhotoPath' : null; return User( - id: data['id'], + id: data['user_id'], username: data['username'], email: data['email'], name: data['name'], @@ -37,9 +41,15 @@ class User { numPosts: data['num_posts'] ?? 0, numFriends: data['num_friends'] ?? 0, profilePhotoUrl: fullProfilePhotoUrl, + friendsIds: data['friends_ids']?.cast< + int>(), // Assuming `friends_ids` is part of the data map when applicable ); } + bool isFriend(int userId) { + return friendsIds?.contains(userId) ?? false; + } + Map toMap() { return { 'id': id, @@ -50,6 +60,7 @@ class User { 'num_posts': numPosts, 'num_friends': numFriends, 'profile_photo': profilePhotoUrl, + 'friends_ids': friendsIds, }; } } diff --git a/georeal/models.py b/georeal/models.py index 02e7cea..0cab851 100644 --- a/georeal/models.py +++ b/georeal/models.py @@ -25,6 +25,9 @@ class User(db.Model): num_posts = db.Column(db.Integer, default=0) num_friends = db.Column(db.Integer, default=0) + def get_friends_ids(self): + return [friend.id for friend in self.friends.all()] + class Geofence(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) diff --git a/georeal/routes/users.py b/georeal/routes/users.py index cbb7c7a..94c1c7d 100644 --- a/georeal/routes/users.py +++ b/georeal/routes/users.py @@ -19,13 +19,15 @@ def get_all_users(): user_data = { 'user_id': user.id, 'name': user.name, - 'username': user.username, - 'num_places': user.num_places, - 'num_posts': user.num_posts, - 'num_friends': user.num_friends, - 'is_friend': False, - 'profile_photo': user.profile_photo, + 'email': user.email, + 'username': user.username, + 'num_places': user.num_places, + 'num_posts': user.num_posts, + 'num_friends': user.num_friends, + 'profile_photo': user.profile_photo, + 'friends_ids': user.get_friends_ids(), } + users_list.append(user_data) return jsonify(users_list), 200 @@ -63,14 +65,14 @@ def get_user_details(): user_details = { 'user_id': user.id, - 'name': user.name, - 'username': user.username, - 'num_places': user.num_places, - 'num_posts': user.num_posts, - 'num_friends': user.num_friends, - 'is_friend': is_friend, - 'friend_request_status': friend_request_sent, - 'profile_photo': user.profile_photo, + 'name': user.name, + 'email': user.email, + 'username': user.username, + 'num_places': user.num_places, + 'num_posts': user.num_posts, + 'num_friends': user.num_friends, + 'profile_photo': user.profile_photo, + 'friends_ids': user.get_friends_ids(), } return jsonify(user_details), 200 @@ -178,13 +180,14 @@ def search_users(): for user in users: user_data = { 'user_id': user.id, - 'username': user.username, 'name': user.name, + 'email': user.email, + 'username': user.username, 'num_places': user.num_places, 'num_posts': user.num_posts, 'num_friends': user.num_friends, - 'is_friend': False, 'profile_photo': user.profile_photo, + 'friends_ids': user.get_friends_ids(), } users_list.append(user_data)