2024-09-05 17:23:02 +01:00
|
|
|
import 'dart:convert';
|
2024-09-10 12:32:14 +01:00
|
|
|
import 'dart:io';
|
2024-09-05 13:55:34 +01:00
|
|
|
import 'package:flutter/material.dart';
|
2024-09-10 12:32:14 +01:00
|
|
|
import 'package:image_picker/image_picker.dart';
|
2024-09-05 13:55:34 +01:00
|
|
|
import 'package:mobile/base/sidemenu.dart';
|
2024-09-10 14:44:50 +01:00
|
|
|
import 'models.dart' as models;
|
2024-09-13 08:59:21 +01:00
|
|
|
import 'services/api.dart' as api;
|
2024-09-10 14:44:50 +01:00
|
|
|
import 'package:path/path.dart' as path;
|
2024-09-05 13:55:34 +01:00
|
|
|
|
|
|
|
class CreateReviewPage extends StatefulWidget {
|
|
|
|
const CreateReviewPage({super.key});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<CreateReviewPage> createState() => _CreateReviewState();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class _CreateReviewState extends State<CreateReviewPage> {
|
2024-09-05 14:26:21 +01:00
|
|
|
final titleInput = TextEditingController();
|
|
|
|
final contentInput = TextEditingController();
|
2024-09-10 14:44:50 +01:00
|
|
|
models.Place? place;
|
2024-09-05 17:11:35 +01:00
|
|
|
var rating = 0;
|
2024-09-10 12:32:14 +01:00
|
|
|
File? _selectedImage;
|
2024-09-05 14:26:21 +01:00
|
|
|
|
2024-09-05 13:55:34 +01:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2024-09-10 14:44:50 +01:00
|
|
|
place = ModalRoute.of(context)!.settings.arguments as models.Place;
|
2024-09-05 13:55:34 +01:00
|
|
|
|
|
|
|
return SideMenu(
|
|
|
|
selectedIndex: -1,
|
|
|
|
body: Scaffold(
|
2024-09-05 17:11:35 +01:00
|
|
|
backgroundColor: const Color(0xFFF9F9F9),
|
2024-09-05 14:26:21 +01:00
|
|
|
body: SingleChildScrollView(
|
|
|
|
child: Center(
|
|
|
|
child: Container(
|
|
|
|
width: MediaQuery.of(context).size.width,
|
|
|
|
padding: const EdgeInsets.all(40),
|
|
|
|
constraints: const BoxConstraints(maxWidth: 400),
|
|
|
|
child: Column(children: [
|
2024-09-05 17:11:35 +01:00
|
|
|
Text(place!.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)),
|
|
|
|
Text(place!.description, style: const TextStyle(color: Colors.grey)),
|
2024-09-05 14:26:21 +01:00
|
|
|
const SizedBox(height: 50),
|
|
|
|
TextField(
|
|
|
|
controller: titleInput,
|
|
|
|
enableSuggestions: true,
|
|
|
|
autocorrect: true,
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
hintText: 'Review Title',
|
|
|
|
),
|
|
|
|
),
|
|
|
|
const SizedBox(height: 30),
|
|
|
|
TextField(
|
|
|
|
controller: contentInput,
|
|
|
|
minLines: 5,
|
|
|
|
maxLines: null,
|
|
|
|
decoration: const InputDecoration(
|
|
|
|
hintText: 'Write a review...',
|
|
|
|
)
|
2024-09-05 17:11:35 +01:00
|
|
|
),
|
|
|
|
const SizedBox(height: 30),
|
|
|
|
|
2024-09-10 12:32:14 +01:00
|
|
|
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",),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
|
2024-09-05 17:11:35 +01:00
|
|
|
|
2024-09-10 12:32:14 +01:00
|
|
|
// Review Stars
|
2024-09-05 17:11:35 +01:00
|
|
|
Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
for (var i = 0; i < rating; i++) IconButton(onPressed: () => setState(() => rating = i+1), icon: const Icon(Icons.star, color: Colors.yellow)),
|
|
|
|
for (var i = rating; i < 5; i++) IconButton(onPressed: () => setState(() => rating = i+1), icon: const Icon(Icons.star_border)),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
|
|
|
|
const SizedBox(height: 30),
|
|
|
|
ElevatedButton(onPressed: _submitReview, child: const Text('Submit Review')),
|
2024-09-05 14:26:21 +01:00
|
|
|
]),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
2024-09-05 13:55:34 +01:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2024-09-05 14:26:21 +01:00
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
2024-09-05 17:11:35 +01:00
|
|
|
super.dispose();
|
2024-09-05 14:26:21 +01:00
|
|
|
titleInput.dispose();
|
|
|
|
contentInput.dispose();
|
|
|
|
}
|
2024-09-10 12:32:14 +01:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
2024-09-05 17:11:35 +01:00
|
|
|
|
|
|
|
Future<void> _submitReview() async {
|
2024-09-10 14:44:50 +01:00
|
|
|
models.Image? image;
|
|
|
|
|
|
|
|
if (_selectedImage != null) {
|
|
|
|
final fileName = path.basename(_selectedImage!.path);
|
|
|
|
final response = await api.request(context, api.ApiService.app, 'POST', '/images?file_name=$fileName', _selectedImage!.readAsBytesSync());
|
|
|
|
if (response == null) return;
|
|
|
|
|
|
|
|
image = models.Image.fromJson(jsonDecode(response));
|
|
|
|
}
|
|
|
|
|
2024-09-05 17:11:35 +01:00
|
|
|
final response = await api.request(context, api.ApiService.app, 'POST', '/reviews', {
|
|
|
|
'title': titleInput.text,
|
|
|
|
'content': contentInput.text,
|
|
|
|
'place_name': place!.name,
|
|
|
|
'place_description': place!.description,
|
|
|
|
'rating': rating,
|
|
|
|
'lat': place!.point.latitude,
|
|
|
|
'lng': place!.point.longitude,
|
2024-09-10 14:44:50 +01:00
|
|
|
'image_id': image?.id,
|
2024-09-05 17:11:35 +01:00
|
|
|
});
|
2024-09-05 17:23:02 +01:00
|
|
|
|
|
|
|
if (response == null || !mounted) return;
|
|
|
|
|
2024-09-10 14:44:50 +01:00
|
|
|
final review = models.Review.fromJson(jsonDecode(response));
|
2024-09-05 17:23:02 +01:00
|
|
|
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Review submitted')));
|
|
|
|
|
|
|
|
Navigator.pop(context, review);
|
2024-09-05 17:11:35 +01:00
|
|
|
}
|
2024-09-05 13:55:34 +01:00
|
|
|
}
|