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:
parent
08d3682164
commit
ce961e8703
@ -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', {
|
||||||
|
@ -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
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user