diff --git a/Mobile/lib/createreview.dart b/Mobile/lib/createreview.dart index c784de2..b7d6b2e 100644 --- a/Mobile/lib/createreview.dart +++ b/Mobile/lib/createreview.dart @@ -1,6 +1,7 @@ import 'dart:convert'; - +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:mobile/base/sidemenu.dart'; import 'models.dart'; import 'api.dart' as api; @@ -18,6 +19,7 @@ class _CreateReviewState extends State { final contentInput = TextEditingController(); Place? place; var rating = 0; + File? _selectedImage; @override Widget build(BuildContext context) { @@ -56,8 +58,16 @@ class _CreateReviewState extends State { ), const SizedBox(height: 30), - // Review Stars + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton(onPressed: _pickImageFromGallery, icon: const Icon(Icons.image, color: Colors.grey), tooltip: "Pick an image from your gallery",), + IconButton(onPressed: _pickImageFromCamera, icon: const Icon(Icons.camera_alt, color: Colors.grey), tooltip: "Take a picture with your camera",), + ], + ), + + // Review Stars Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -82,6 +92,23 @@ class _CreateReviewState extends State { titleInput.dispose(); contentInput.dispose(); } + Future _pickImageFromGallery() async { + final image = await ImagePicker().pickImage(source: ImageSource.gallery); + if (image == null) return; + + setState(() { + _selectedImage = File(image.path); + }); + } + + Future _pickImageFromCamera() async { + final image = await ImagePicker().pickImage(source: ImageSource.camera); + if (image == null) return; + + setState(() { + _selectedImage = File(image.path); + }); + } Future _submitReview() async { final response = await api.request(context, api.ApiService.app, 'POST', '/reviews', { diff --git a/Mobile/lib/editprofile.dart b/Mobile/lib/editprofile.dart index 24ac03b..38353dd 100644 --- a/Mobile/lib/editprofile.dart +++ b/Mobile/lib/editprofile.dart @@ -48,16 +48,18 @@ class _ProfilePageState extends State { Future _pickImageFromGallery() async{ final image = await ImagePicker().pickImage(source: ImageSource.gallery); if (image == null) return; - setState(() { - _selectedImage = File(image.path); + + setState(() { + _selectedImage = File(image.path); }); } Future _pickImageFromCamera() async{ final image = await ImagePicker().pickImage(source: ImageSource.camera); if (image == null) return; - setState(() { - _selectedImage = File(image.path); + + setState(() { + _selectedImage = File(image.path); }); } @@ -156,9 +158,9 @@ class _ProfilePageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('Edit Profile'), + title: const Text('Edit Profile'), leading: IconButton( - icon: Icon(Icons.arrow_back), + icon: const Icon(Icons.arrow_back), onPressed: () { Navigator.pop(context); // Navigates back when the back button is pressed }, @@ -170,71 +172,77 @@ class _ProfilePageState extends State { children: [ TextField( controller: usernameInput, - decoration: InputDecoration(labelText: 'Name'), + decoration: const InputDecoration(labelText: 'Name'), ), TextField( controller: emailInput, - decoration: InputDecoration(labelText: 'Email'), + decoration: const InputDecoration(labelText: 'Email'), ), TextField( controller: passwordInput, - decoration: InputDecoration(labelText: 'New password'), + decoration: const InputDecoration(labelText: 'New password'), ), TextField( controller: confirmPasswordInput, - decoration: InputDecoration(labelText: 'Repeat new password'), + decoration: const InputDecoration(labelText: 'Repeat new password'), ), - SizedBox(height: 20), + const SizedBox(height: 20), Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text('ProfilePicture', - style: TextStyle(fontSize: 17)), - if (_selectedImage != null) - ClipOval( - child: Image( - image: FileImage(_selectedImage!), - height: 100, - width: 100, - fit: BoxFit.cover, - ), - ) - else - ClipOval( - child: Image( - image: NetworkImage(user!.profilePicture), - height: 100, - width: 100, - fit: BoxFit.cover, - ), + const Text('Profile Picture', style: TextStyle(fontSize: 17)), + if (_selectedImage != null) + ClipOval( + child: Image( + image: FileImage(_selectedImage!), + height: 100, + width: 100, + fit: BoxFit.cover, ), - if (_selectedImage != null) - Text(_selectedImage!.path.toString()), - //until here - Row( + ) + else if (user!.profilePicture != null && user!.profilePicture.isNotEmpty) + ClipOval( + child: Image( + image: NetworkImage(user!.profilePicture), + height: 100, + width: 100, + fit: BoxFit.cover, + ), + ) + else + const Icon( + Icons.account_circle, + size: 100, + color: Colors.grey, + ), + if (_selectedImage != null) + Text(_selectedImage!.path.toString()), + //until here + Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('Change using'), - TextButton(onPressed: _pickImageFromGallery, child: Text('Gallery')), - SizedBox(width: 5), - Text('or'), - SizedBox(width: 5), - TextButton(onPressed: _pickImageFromCamera, child: Text('Camera')) + const Text('Change using'), + TextButton(onPressed: _pickImageFromGallery, child: const Text('Gallery')), + const SizedBox(width: 5), + const Text('or'), + const SizedBox(width: 5), + TextButton(onPressed: _pickImageFromCamera, child: const Text('Camera')) ], ), ], ), - SizedBox(height: 40), + const SizedBox(height: 40), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: _saveProfile, // Save and pop - child: Text('Save'), + child: const Text('Save'), ), - SizedBox(width: 10), + const SizedBox(width: 10), ElevatedButton( onPressed: () => _deleteProfile(context), - child: Text('Delete'), + child: const Text('Delete'), style: ElevatedButton.styleFrom( foregroundColor: Colors.red, // Red text ), diff --git a/Mobile/lib/profile.dart b/Mobile/lib/profile.dart index 173e6be..650dce9 100644 --- a/Mobile/lib/profile.dart +++ b/Mobile/lib/profile.dart @@ -41,8 +41,6 @@ class _ProfilePageState extends State { Map json = jsonDecode(response); User jsonUser = User.fromJson(json); - print(jsonUser.profilePicture); - setState(() { userData = jsonUser; user = jsonUser; @@ -73,15 +71,15 @@ class _ProfilePageState extends State { : Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - userData?.profilePicture != null ? - ClipOval( - child: Image( - image: NetworkImage(userData!.profilePicture), - height: 100, - width: 100, // Ensure width matches the height to make it fully round - fit: BoxFit.cover, // This makes sure the image fits inside the circle properly - ), - ) + userData?.profilePicture != null && userData!.profilePicture.isNotEmpty ? + ClipOval( + child: Image( + image: NetworkImage(userData!.profilePicture), + height: 100, + width: 100, // Ensure width matches the height to make it fully round + fit: BoxFit.cover, // This makes sure the image fits inside the circle properly + ), + ) : const Icon( Icons.account_circle, size: 100, diff --git a/rust-backend/src/main.rs b/rust-backend/src/main.rs index 5b1750c..84650dd 100644 --- a/rust-backend/src/main.rs +++ b/rust-backend/src/main.rs @@ -8,6 +8,7 @@ use models::{Favorite, Review, Image}; use serde::Deserialize; use actix_web::web::Bytes; use aws_sdk_s3::primitives::ByteStream; +use rusqlite::types::Null; mod embedded { use refinery::embed_migrations; @@ -141,14 +142,40 @@ struct CreateReviewRequest { title: String, content: String, rating: i64, + image_id: Option, } #[post("/reviews")] async fn create_review(auth: AuthorizedUser, data: web::Data, input: web::Json) -> impl Responder { let db = data.database.lock().unwrap(); + let image_id = match input.image_id { + Some(image_id) => image_id.to_string(), + None => "NULL".to_string(), + } + match db.execute( - "INSERT INTO reviews (user_id, lat, lng, place_name, place_description, title, content, rating) VALUES (:user_id, :lat, :lng, :place_name, :place_description, :title, :content, :rating)", + "INSERT INTO reviews ( + user_id, + lat, + lng, + place_name, + place_description, + title, + content, + rating, + image_id + ) VALUES ( + :user_id, + :lat, + :lng, + :place_name, + :place_description, + :title, + :content, + :rating, + :image_id + )", &[ (":user_id", &auth.user_id), (":lat", &input.lat.to_string()), @@ -158,6 +185,7 @@ async fn create_review(auth: AuthorizedUser, data: web::Data, input: we (":title", &input.title), (":content", &input.content), (":rating", &input.rating.to_string()), + (":image_id", &image_id), ], ) { Ok(_) => HttpResponse::Created().json(Review {