Fix when no profile image in edit profile, add image buttons on create review

Co-authored-by: Reimar <mail@reim.ar>
This commit is contained in:
Alexandertp 2024-09-10 13:32:14 +02:00
parent 08d3682164
commit ce961e8703
4 changed files with 120 additions and 59 deletions

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mobile/base/sidemenu.dart'; import 'package:mobile/base/sidemenu.dart';
import 'models.dart'; import 'models.dart';
import 'api.dart' as api; import 'api.dart' as api;
@ -18,6 +19,7 @@ class _CreateReviewState extends State<CreateReviewPage> {
final contentInput = TextEditingController(); final contentInput = TextEditingController();
Place? place; Place? place;
var rating = 0; var rating = 0;
File? _selectedImage;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -56,8 +58,16 @@ class _CreateReviewState extends State<CreateReviewPage> {
), ),
const SizedBox(height: 30), 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( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -82,6 +92,23 @@ class _CreateReviewState extends State<CreateReviewPage> {
titleInput.dispose(); titleInput.dispose();
contentInput.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<void> _submitReview() async { Future<void> _submitReview() async {
final response = await api.request(context, api.ApiService.app, 'POST', '/reviews', { final response = await api.request(context, api.ApiService.app, 'POST', '/reviews', {

View File

@ -48,16 +48,18 @@ class _ProfilePageState extends State<EditProfilePage> {
Future _pickImageFromGallery() async{ Future _pickImageFromGallery() async{
final image = await ImagePicker().pickImage(source: ImageSource.gallery); final image = await ImagePicker().pickImage(source: ImageSource.gallery);
if (image == null) return; if (image == null) return;
setState(() {
_selectedImage = File(image.path); setState(() {
_selectedImage = File(image.path);
}); });
} }
Future _pickImageFromCamera() async{ Future _pickImageFromCamera() async{
final image = await ImagePicker().pickImage(source: ImageSource.camera); final image = await ImagePicker().pickImage(source: ImageSource.camera);
if (image == null) return; if (image == null) return;
setState(() {
_selectedImage = File(image.path); setState(() {
_selectedImage = File(image.path);
}); });
} }
@ -156,9 +158,9 @@ class _ProfilePageState extends State<EditProfilePage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('Edit Profile'), title: const Text('Edit Profile'),
leading: IconButton( leading: IconButton(
icon: Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
onPressed: () { onPressed: () {
Navigator.pop(context); // Navigates back when the back button is pressed Navigator.pop(context); // Navigates back when the back button is pressed
}, },
@ -170,71 +172,77 @@ class _ProfilePageState extends State<EditProfilePage> {
children: [ children: [
TextField( TextField(
controller: usernameInput, controller: usernameInput,
decoration: InputDecoration(labelText: 'Name'), decoration: const InputDecoration(labelText: 'Name'),
), ),
TextField( TextField(
controller: emailInput, controller: emailInput,
decoration: InputDecoration(labelText: 'Email'), decoration: const InputDecoration(labelText: 'Email'),
), ),
TextField( TextField(
controller: passwordInput, controller: passwordInput,
decoration: InputDecoration(labelText: 'New password'), decoration: const InputDecoration(labelText: 'New password'),
), ),
TextField( TextField(
controller: confirmPasswordInput, controller: confirmPasswordInput,
decoration: InputDecoration(labelText: 'Repeat new password'), decoration: const InputDecoration(labelText: 'Repeat new password'),
), ),
SizedBox(height: 20), const SizedBox(height: 20),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text('ProfilePicture', const Text('Profile Picture', style: TextStyle(fontSize: 17)),
style: TextStyle(fontSize: 17)), if (_selectedImage != null)
if (_selectedImage != null) ClipOval(
ClipOval( child: Image(
child: Image( image: FileImage(_selectedImage!),
image: FileImage(_selectedImage!), height: 100,
height: 100, width: 100,
width: 100, fit: BoxFit.cover,
fit: BoxFit.cover,
),
)
else
ClipOval(
child: Image(
image: NetworkImage(user!.profilePicture),
height: 100,
width: 100,
fit: BoxFit.cover,
),
), ),
if (_selectedImage != null) )
Text(_selectedImage!.path.toString()), else if (user!.profilePicture != null && user!.profilePicture.isNotEmpty)
//until here ClipOval(
Row( 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: [ children: [
Text('Change using'), const Text('Change using'),
TextButton(onPressed: _pickImageFromGallery, child: Text('Gallery')), TextButton(onPressed: _pickImageFromGallery, child: const Text('Gallery')),
SizedBox(width: 5), const SizedBox(width: 5),
Text('or'), const Text('or'),
SizedBox(width: 5), const SizedBox(width: 5),
TextButton(onPressed: _pickImageFromCamera, child: Text('Camera')) TextButton(onPressed: _pickImageFromCamera, child: const Text('Camera'))
], ],
), ),
], ],
), ),
SizedBox(height: 40), const SizedBox(height: 40),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ElevatedButton( ElevatedButton(
onPressed: _saveProfile, // Save and pop onPressed: _saveProfile, // Save and pop
child: Text('Save'), child: const Text('Save'),
), ),
SizedBox(width: 10), const SizedBox(width: 10),
ElevatedButton( ElevatedButton(
onPressed: () => _deleteProfile(context), onPressed: () => _deleteProfile(context),
child: Text('Delete'), child: const Text('Delete'),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
foregroundColor: Colors.red, // Red text foregroundColor: Colors.red, // Red text
), ),

View File

@ -41,8 +41,6 @@ class _ProfilePageState extends State<ProfilePage> {
Map<String, dynamic> json = jsonDecode(response); Map<String, dynamic> json = jsonDecode(response);
User jsonUser = User.fromJson(json); User jsonUser = User.fromJson(json);
print(jsonUser.profilePicture);
setState(() { setState(() {
userData = jsonUser; userData = jsonUser;
user = jsonUser; user = jsonUser;
@ -73,15 +71,15 @@ class _ProfilePageState extends State<ProfilePage> {
: Column( : Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
userData?.profilePicture != null ? userData?.profilePicture != null && userData!.profilePicture.isNotEmpty ?
ClipOval( ClipOval(
child: Image( child: Image(
image: NetworkImage(userData!.profilePicture), image: NetworkImage(userData!.profilePicture),
height: 100, height: 100,
width: 100, // Ensure width matches the height to make it fully round 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 fit: BoxFit.cover, // This makes sure the image fits inside the circle properly
), ),
) )
: const Icon( : const Icon(
Icons.account_circle, Icons.account_circle,
size: 100, size: 100,

View File

@ -8,6 +8,7 @@ use models::{Favorite, Review, Image};
use serde::Deserialize; use serde::Deserialize;
use actix_web::web::Bytes; use actix_web::web::Bytes;
use aws_sdk_s3::primitives::ByteStream; use aws_sdk_s3::primitives::ByteStream;
use rusqlite::types::Null;
mod embedded { mod embedded {
use refinery::embed_migrations; use refinery::embed_migrations;
@ -141,14 +142,40 @@ struct CreateReviewRequest {
title: String, title: String,
content: String, content: String,
rating: i64, rating: i64,
image_id: Option<i64>,
} }
#[post("/reviews")] #[post("/reviews")]
async fn create_review(auth: AuthorizedUser, data: web::Data<AppData>, input: web::Json<CreateReviewRequest>) -> impl Responder { async fn create_review(auth: AuthorizedUser, data: web::Data<AppData>, input: web::Json<CreateReviewRequest>) -> impl Responder {
let db = data.database.lock().unwrap(); 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( 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), (":user_id", &auth.user_id),
(":lat", &input.lat.to_string()), (":lat", &input.lat.to_string()),
@ -158,6 +185,7 @@ async fn create_review(auth: AuthorizedUser, data: web::Data<AppData>, input: we
(":title", &input.title), (":title", &input.title),
(":content", &input.content), (":content", &input.content),
(":rating", &input.rating.to_string()), (":rating", &input.rating.to_string()),
(":image_id", &image_id),
], ],
) { ) {
Ok(_) => HttpResponse::Created().json(Review { Ok(_) => HttpResponse::Created().json(Review {