From f7d9cc0fa0711e23431281be63d86da79214f20c Mon Sep 17 00:00:00 2001 From: Sandertp Date: Tue, 3 Sep 2024 13:33:50 +0200 Subject: [PATCH 1/8] Show reviews on frontend with colored pins Co-authored-by: Reimar --- Mobile/lib/main.dart | 39 ++++++++++++++++++- Mobile/lib/models.dart | 26 +++++++++++++ Mobile/pubspec.lock | 8 ++-- .../migrations/V3__create_reviews_table.sql | 2 +- 4 files changed, 68 insertions(+), 7 deletions(-) diff --git a/Mobile/lib/main.dart b/Mobile/lib/main.dart index 332536d..e71f742 100644 --- a/Mobile/lib/main.dart +++ b/Mobile/lib/main.dart @@ -60,6 +60,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { final GlobalKey _scaffoldKey = GlobalKey(); List _favorites = []; + List _reviews = []; LatLng? _selectedPoint; LatLng _currentPosition = LatLng(55.656707, 10.563214); LatLng? _userPosition; @@ -81,6 +82,7 @@ class _MyHomePageState extends State { if (!isLoggedIn || !mounted) return; _fetchFavorites(); + _fetchReviews(); }); } @@ -216,7 +218,16 @@ class _MyHomePageState extends State { }); } - + Future _fetchReviews() async { + final response = await api.request(context, api.ApiService.app, 'GET', '/reviews', null); + if (response == null) return; + + final List reviews = jsonDecode(response); + setState(() { + _reviews = reviews.map((review) => Review.fromJson(review)).toList(); + debugPrint(_reviews.length.toString()); + }); + } Future GetOpenStreetMapArea() async { final dynamic location; @@ -329,7 +340,31 @@ class _MyHomePageState extends State { ) ], )), - ..._favorites.map((favorite) => MarkerLayer( + ..._reviews.map((review) => MarkerLayer( + markers: [ + Marker( + point: LatLng(review.lat, review.lng), + width: 30, + height: 50, + alignment: Alignment.center, + child: Stack( + children: [ + IconButton( + padding: const EdgeInsets.only(bottom: 10), + icon: const Icon(Icons.location_pin, size: 30, color:Colors.purpleAccent), + onPressed: () => _showLocation(LatLng(review.lat, review.lng), review.place_name, review.place_description), + ), + IconButton( + padding: const EdgeInsets.only(bottom: 10), + icon: const Icon(Icons.location_on_outlined, size: 30, color: Colors.purple), + onPressed: () => _showLocation(LatLng(review.lat, review.lng), review.place_name, review.place_description), + ), + ], + ) + ) + ], + )), + ..._favorites.map((favorite) => MarkerLayer( markers: [ Marker( point: LatLng(favorite.lat, favorite.lng), diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart index 8739fd1..92b5730 100644 --- a/Mobile/lib/models.dart +++ b/Mobile/lib/models.dart @@ -22,6 +22,32 @@ class Favorite { } } +class Review { + int id; + String userId; + double lat; + double lng; + String place_name; + String place_description; + String title; + int rating; + + Review(this.id, this.userId, this.lat, this.lng, this.place_name, this.place_description, this.title, this.rating); + + factory Review.fromJson(Map json) { + return Review( + json['id'], + json['user_id'], + json['lat'], + json['lng'], + json['place_name'], + json['place_description'], + json['title'], + json['rating'] + ); + } +} + class Login { String token; String id; diff --git a/Mobile/pubspec.lock b/Mobile/pubspec.lock index b5d1008..3061e4c 100644 --- a/Mobile/pubspec.lock +++ b/Mobile/pubspec.lock @@ -396,10 +396,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974 + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_foundation: dependency: transitive description: @@ -521,10 +521,10 @@ packages: dependency: transitive description: name: uuid - sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77 url: "https://pub.dev" source: hosted - version: "4.4.2" + version: "4.5.0" vector_math: dependency: transitive description: diff --git a/rust-backend/migrations/V3__create_reviews_table.sql b/rust-backend/migrations/V3__create_reviews_table.sql index 6ab31b4..6b76a9f 100644 --- a/rust-backend/migrations/V3__create_reviews_table.sql +++ b/rust-backend/migrations/V3__create_reviews_table.sql @@ -7,6 +7,6 @@ CREATE TABLE reviews ( place_description TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, - rating REAL NOT NULL + rating INTEGER NOT NULL ); From 6fd77b3330666b9517709508d9169305bc92b7ad Mon Sep 17 00:00:00 2001 From: Sandertp Date: Wed, 4 Sep 2024 11:53:32 +0200 Subject: [PATCH 2/8] Add ReviewListPage and show list of reviews Co-authored-by: Reimar --- Mobile/lib/main.dart | 27 +++++++++++++--- Mobile/lib/models.dart | 6 ++-- Mobile/lib/reviewList.dart | 64 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 Mobile/lib/reviewList.dart diff --git a/Mobile/lib/main.dart b/Mobile/lib/main.dart index e71f742..de4136f 100644 --- a/Mobile/lib/main.dart +++ b/Mobile/lib/main.dart @@ -6,6 +6,7 @@ import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:mobile/favorites.dart'; import 'package:mobile/register.dart'; +import 'package:mobile/reviewList.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'login.dart'; import 'base/sidemenu.dart'; @@ -45,6 +46,7 @@ class MyApp extends StatelessWidget { '/favorites': (context) => const FavoritesPage(), '/login': (context) => const LoginPage(), '/register': (context) => const RegisterPage(), + '/reviews': (context) => const ReviewListPage(), }, ); } @@ -128,6 +130,7 @@ class _MyHomePageState extends State { setState(() => _selectedPoint = null); } + // Open location bottom menu Future _showLocation(LatLng point, String name, String description) async { await showModalBottomSheet( barrierColor: Colors.black.withOpacity(0.3), @@ -140,6 +143,7 @@ class _MyHomePageState extends State { padding: const EdgeInsets.all(20), width: MediaQuery.of(context).size.width, child: Row(children: [ + // Location information Expanded(child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -148,14 +152,27 @@ class _MyHomePageState extends State { Text(description), ], )), + Column(children: [ + // Toggle favorite button IconButton( - icon: const Icon(Icons.star), - iconSize: 32, - color: _favorites.where((fav) => fav.lat == point.latitude && fav.lng == point.longitude).isEmpty ? Colors.grey : Colors.yellow, - onPressed: () => _toggleFavorite(point, name, description, setModalState, context) + icon: const Icon(Icons.star), + iconSize: 32, + color: _favorites.where((fav) => fav.lat == point.latitude && fav.lng == point.longitude).isEmpty ? Colors.grey : Colors.yellow, + onPressed: () => _toggleFavorite(point, name, description, setModalState, context) + ), + + // View reviews button + IconButton( + icon: const Icon(Icons.rate_review), + iconSize: 32, + onPressed: () => + Navigator.pushReplacementNamed( + context, + '/reviews', + arguments: _reviews.where((review) => review.lat == point.latitude && review.lng == point.longitude).toList() + ), ), - const IconButton(icon: Icon(Icons.rate_review), iconSize: 32, onPressed: null), ]), ]), ), diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart index 92b5730..2a55ccf 100644 --- a/Mobile/lib/models.dart +++ b/Mobile/lib/models.dart @@ -30,9 +30,10 @@ class Review { String place_name; String place_description; String title; + String content; int rating; - Review(this.id, this.userId, this.lat, this.lng, this.place_name, this.place_description, this.title, this.rating); + Review(this.id, this.userId, this.lat, this.lng, this.place_name, this.place_description, this.title, this.content, this.rating); factory Review.fromJson(Map json) { return Review( @@ -43,7 +44,8 @@ class Review { json['place_name'], json['place_description'], json['title'], - json['rating'] + json['content'], + json['rating'], ); } } diff --git a/Mobile/lib/reviewList.dart b/Mobile/lib/reviewList.dart new file mode 100644 index 0000000..d75f656 --- /dev/null +++ b/Mobile/lib/reviewList.dart @@ -0,0 +1,64 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:mobile/base/sidemenu.dart'; +import 'models.dart'; + +class ReviewListPage extends StatefulWidget { + const ReviewListPage({super.key}); + + @override + State createState() => _ReviewListState(); +} + +class _ReviewListState extends State { + @override + Widget build(BuildContext context) { + // TODO: implement build + final reviews = ModalRoute.of(context)!.settings.arguments as List; + + return SideMenu( + selectedIndex: -1, + body: SingleChildScrollView(child: Container( + decoration: const BoxDecoration(color: Color(0xFFF9f9f9)), + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.all(20.0), + child: Column(children: + reviews.map((review) => Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + margin: const EdgeInsets.only(bottom: 10), + decoration: const BoxDecoration( + boxShadow: [ + BoxShadow( + color: Color(0x20000000), + offset: Offset(0,1), + blurRadius: 4, + ), + ], + color: Colors.white, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(top: 3), + child: Icon(Icons.radio, color: Colors.purple, size: 36), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(review.title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), + Text(review.content), + ], + ), + ), + ], + ) + )).toList(), + ), + )), + ); + } +} \ No newline at end of file From 9b6ebd05b0cccce8a69806fc5e69d83f819693df Mon Sep 17 00:00:00 2001 From: Sandertp Date: Wed, 4 Sep 2024 12:27:17 +0200 Subject: [PATCH 3/8] Add Floating Action Button to ReviewListPage Co-authored-by: Reimar --- Mobile/lib/reviewList.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Mobile/lib/reviewList.dart b/Mobile/lib/reviewList.dart index d75f656..b5604c1 100644 --- a/Mobile/lib/reviewList.dart +++ b/Mobile/lib/reviewList.dart @@ -18,7 +18,7 @@ class _ReviewListState extends State { return SideMenu( selectedIndex: -1, - body: SingleChildScrollView(child: Container( + body: Scaffold(body: SingleChildScrollView(child: Container( decoration: const BoxDecoration(color: Color(0xFFF9f9f9)), width: MediaQuery.of(context).size.width, padding: const EdgeInsets.all(20.0), @@ -59,6 +59,13 @@ class _ReviewListState extends State { )).toList(), ), )), - ); + floatingActionButton: const FloatingActionButton( + onPressed: null, + backgroundColor: Colors.blue, + focusColor: Colors.blueGrey, + tooltip: "Write a Review", + child: Icon(CupertinoIcons.plus), + ), + )); } } \ No newline at end of file From 24051c2b7321ae8eba72a2f0894a850f985b85ff Mon Sep 17 00:00:00 2001 From: Alexandertp Date: Thu, 5 Sep 2024 14:55:34 +0200 Subject: [PATCH 4/8] Add CreateReview Page Co-authored-by: Reimar --- Mobile/lib/createreview.dart | 35 +++++++++++++++++++ Mobile/lib/main.dart | 7 ++-- Mobile/lib/models.dart | 14 ++++++++ .../lib/{reviewList.dart => reviewlist.dart} | 13 +++---- Mobile/pubspec.lock | 24 ++++++------- 5 files changed, 73 insertions(+), 20 deletions(-) create mode 100644 Mobile/lib/createreview.dart rename Mobile/lib/{reviewList.dart => reviewlist.dart} (84%) diff --git a/Mobile/lib/createreview.dart b/Mobile/lib/createreview.dart new file mode 100644 index 0000000..9772dc1 --- /dev/null +++ b/Mobile/lib/createreview.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:mobile/base/sidemenu.dart'; +import 'models.dart'; + +class CreateReviewPage extends StatefulWidget { + const CreateReviewPage({super.key}); + + @override + State createState() => _CreateReviewState(); + +} + +class _CreateReviewState extends State { + @override + Widget build(BuildContext context) { + final place = ModalRoute.of(context)!.settings.arguments as Place; + + return SideMenu( + selectedIndex: -1, + body: Scaffold( + body: SingleChildScrollView(child: Container( + decoration: const BoxDecoration(color: Color(0xFFF9F9F9)), + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.all(20), + child: Column(children: [ + Text(place.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), + Text(place.description, style: const TextStyle(color: Colors.grey)) + ]), + + + )) + ) + ); + } +} \ No newline at end of file diff --git a/Mobile/lib/main.dart b/Mobile/lib/main.dart index de4136f..a65b7ce 100644 --- a/Mobile/lib/main.dart +++ b/Mobile/lib/main.dart @@ -4,9 +4,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; +import 'package:mobile/createreview.dart'; import 'package:mobile/favorites.dart'; import 'package:mobile/register.dart'; -import 'package:mobile/reviewList.dart'; +import 'package:mobile/reviewlist.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'login.dart'; import 'base/sidemenu.dart'; @@ -47,6 +48,7 @@ class MyApp extends StatelessWidget { '/login': (context) => const LoginPage(), '/register': (context) => const RegisterPage(), '/reviews': (context) => const ReviewListPage(), + '/create-review': (context) => const CreateReviewPage(), }, ); } @@ -166,11 +168,12 @@ class _MyHomePageState extends State { IconButton( icon: const Icon(Icons.rate_review), iconSize: 32, + color: Colors.grey, onPressed: () => Navigator.pushReplacementNamed( context, '/reviews', - arguments: _reviews.where((review) => review.lat == point.latitude && review.lng == point.longitude).toList() + arguments: ReviewList(_reviews.where((review) => review.lat == point.latitude && review.lng == point.longitude).toList(), Place(name, description)) ), ), ]), diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart index 2a55ccf..f11b733 100644 --- a/Mobile/lib/models.dart +++ b/Mobile/lib/models.dart @@ -50,6 +50,20 @@ class Review { } } +class Place { + String name; + String description; + + Place(this.name, this.description); +} + +class ReviewList { + List reviews; + Place place; + + ReviewList(this.reviews, this.place); +} + class Login { String token; String id; diff --git a/Mobile/lib/reviewList.dart b/Mobile/lib/reviewlist.dart similarity index 84% rename from Mobile/lib/reviewList.dart rename to Mobile/lib/reviewlist.dart index b5604c1..29f791f 100644 --- a/Mobile/lib/reviewList.dart +++ b/Mobile/lib/reviewlist.dart @@ -13,13 +13,14 @@ class ReviewListPage extends StatefulWidget { class _ReviewListState extends State { @override Widget build(BuildContext context) { - // TODO: implement build - final reviews = ModalRoute.of(context)!.settings.arguments as List; + final arg = ModalRoute.of(context)!.settings.arguments as ReviewList; + final reviews = arg.reviews; + final place = arg.place; return SideMenu( selectedIndex: -1, body: Scaffold(body: SingleChildScrollView(child: Container( - decoration: const BoxDecoration(color: Color(0xFFF9f9f9)), + decoration: const BoxDecoration(color: Color(0xFFF9F9F9)), width: MediaQuery.of(context).size.width, padding: const EdgeInsets.all(20.0), child: Column(children: @@ -59,12 +60,12 @@ class _ReviewListState extends State { )).toList(), ), )), - floatingActionButton: const FloatingActionButton( - onPressed: null, + floatingActionButton: FloatingActionButton( + onPressed: () => Navigator.pushNamed(context, '/create-review', arguments: place), backgroundColor: Colors.blue, focusColor: Colors.blueGrey, tooltip: "Write a Review", - child: Icon(CupertinoIcons.plus), + child: const Icon(CupertinoIcons.plus), ), )); } diff --git a/Mobile/pubspec.lock b/Mobile/pubspec.lock index 3061e4c..4018227 100644 --- a/Mobile/pubspec.lock +++ b/Mobile/pubspec.lock @@ -220,18 +220,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: @@ -276,18 +276,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.12.0" mgrs_dart: dependency: transitive description: @@ -497,10 +497,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.0" typed_data: dependency: transitive description: @@ -537,10 +537,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.1" web: dependency: transitive description: From bd9a6dfa037c59df7798f7a3459fae8ec0033227 Mon Sep 17 00:00:00 2001 From: Alexandertp Date: Thu, 5 Sep 2024 15:26:21 +0200 Subject: [PATCH 5/8] Add TextFields for review title and review content Co-authored-by: Reimar --- Mobile/lib/createreview.dart | 52 ++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/Mobile/lib/createreview.dart b/Mobile/lib/createreview.dart index 9772dc1..4d5d5aa 100644 --- a/Mobile/lib/createreview.dart +++ b/Mobile/lib/createreview.dart @@ -11,6 +11,9 @@ class CreateReviewPage extends StatefulWidget { } class _CreateReviewState extends State { + final titleInput = TextEditingController(); + final contentInput = TextEditingController(); + @override Widget build(BuildContext context) { final place = ModalRoute.of(context)!.settings.arguments as Place; @@ -18,18 +21,45 @@ class _CreateReviewState extends State { return SideMenu( selectedIndex: -1, body: Scaffold( - body: SingleChildScrollView(child: Container( - decoration: const BoxDecoration(color: Color(0xFFF9F9F9)), - width: MediaQuery.of(context).size.width, - padding: const EdgeInsets.all(20), - child: Column(children: [ - Text(place.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), - Text(place.description, style: const TextStyle(color: Colors.grey)) - ]), - - - )) + backgroundColor: Color(0xFFF9F9F9), + 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: [ + Text(place.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), + Text(place.description, style: const TextStyle(color: Colors.grey)), + 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...', + ) + ) + ]), + ) + ) + ) ) ); } + + @override + void dispose() { + titleInput.dispose(); + contentInput.dispose(); + } } \ No newline at end of file From e03131a8aa9d6f70480f588d4d5014be6b6585ba Mon Sep 17 00:00:00 2001 From: Alexandertp Date: Thu, 5 Sep 2024 18:11:35 +0200 Subject: [PATCH 6/8] Implement submitting reviews Co-authored-by: Reimar --- Mobile/lib/createreview.dart | 40 +++++++++++++++++++++++++++++++----- Mobile/lib/main.dart | 2 +- Mobile/lib/models.dart | 7 ++++--- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/Mobile/lib/createreview.dart b/Mobile/lib/createreview.dart index 4d5d5aa..b944fd2 100644 --- a/Mobile/lib/createreview.dart +++ b/Mobile/lib/createreview.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mobile/base/sidemenu.dart'; import 'models.dart'; +import 'api.dart' as api; class CreateReviewPage extends StatefulWidget { const CreateReviewPage({super.key}); @@ -13,15 +14,17 @@ class CreateReviewPage extends StatefulWidget { class _CreateReviewState extends State { final titleInput = TextEditingController(); final contentInput = TextEditingController(); + Place? place; + var rating = 0; @override Widget build(BuildContext context) { - final place = ModalRoute.of(context)!.settings.arguments as Place; + place = ModalRoute.of(context)!.settings.arguments as Place; return SideMenu( selectedIndex: -1, body: Scaffold( - backgroundColor: Color(0xFFF9F9F9), + backgroundColor: const Color(0xFFF9F9F9), body: SingleChildScrollView( child: Center( child: Container( @@ -29,8 +32,8 @@ class _CreateReviewState extends State { padding: const EdgeInsets.all(40), constraints: const BoxConstraints(maxWidth: 400), child: Column(children: [ - Text(place.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), - Text(place.description, style: const TextStyle(color: Colors.grey)), + Text(place!.name, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), + Text(place!.description, style: const TextStyle(color: Colors.grey)), const SizedBox(height: 50), TextField( controller: titleInput, @@ -48,7 +51,21 @@ class _CreateReviewState extends State { decoration: const InputDecoration( hintText: 'Write a review...', ) - ) + ), + const SizedBox(height: 30), + + // Review Stars + + 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')), ]), ) ) @@ -59,7 +76,20 @@ class _CreateReviewState extends State { @override void dispose() { + super.dispose(); titleInput.dispose(); contentInput.dispose(); } + + Future _submitReview() async { + 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, + }); + } } \ No newline at end of file diff --git a/Mobile/lib/main.dart b/Mobile/lib/main.dart index a65b7ce..47354dd 100644 --- a/Mobile/lib/main.dart +++ b/Mobile/lib/main.dart @@ -173,7 +173,7 @@ class _MyHomePageState extends State { Navigator.pushReplacementNamed( context, '/reviews', - arguments: ReviewList(_reviews.where((review) => review.lat == point.latitude && review.lng == point.longitude).toList(), Place(name, description)) + arguments: ReviewList(_reviews.where((review) => review.lat == point.latitude && review.lng == point.longitude).toList(), Place(name, description, point)) ), ), ]), diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart index f11b733..e0c0cfa 100644 --- a/Mobile/lib/models.dart +++ b/Mobile/lib/models.dart @@ -53,8 +53,9 @@ class Review { class Place { String name; String description; + LatLng point; - Place(this.name, this.description); + Place(this.name, this.description, this.point); } class ReviewList { @@ -98,10 +99,10 @@ class User { } } -class SearchResults{ +class SearchResults { LatLng location; String name; String description; -SearchResults(this.location, this.name, this.description); + SearchResults(this.location, this.name, this.description); } From 0dcfb2ea1ecded342edf7b90f88a5bc95def4d1b Mon Sep 17 00:00:00 2001 From: Alexandertp Date: Thu, 5 Sep 2024 18:23:02 +0200 Subject: [PATCH 7/8] Show newly submitted reviews on reviews list Co-authored-by: Reimar --- Mobile/lib/createreview.dart | 10 ++++++++++ Mobile/lib/reviewlist.dart | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Mobile/lib/createreview.dart b/Mobile/lib/createreview.dart index b944fd2..c784de2 100644 --- a/Mobile/lib/createreview.dart +++ b/Mobile/lib/createreview.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:mobile/base/sidemenu.dart'; import 'models.dart'; @@ -91,5 +93,13 @@ class _CreateReviewState extends State { 'lat': place!.point.latitude, 'lng': place!.point.longitude, }); + + if (response == null || !mounted) return; + + final review = Review.fromJson(jsonDecode(response)); + + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Review submitted'))); + + Navigator.pop(context, review); } } \ No newline at end of file diff --git a/Mobile/lib/reviewlist.dart b/Mobile/lib/reviewlist.dart index 29f791f..bd0313b 100644 --- a/Mobile/lib/reviewlist.dart +++ b/Mobile/lib/reviewlist.dart @@ -61,7 +61,10 @@ class _ReviewListState extends State { ), )), floatingActionButton: FloatingActionButton( - onPressed: () => Navigator.pushNamed(context, '/create-review', arguments: place), + onPressed: () async { + final review = await Navigator.pushNamed(context, '/create-review', arguments: place) as Review?; + if (review != null) reviews.add(review); + }, backgroundColor: Colors.blue, focusColor: Colors.blueGrey, tooltip: "Write a Review", From d767e0bb85387665801d06d0cbdb8c1c36cf982a Mon Sep 17 00:00:00 2001 From: Alexandertp Date: Thu, 5 Sep 2024 18:39:43 +0200 Subject: [PATCH 8/8] Add stars to review list Co-authored-by: Reimar --- Mobile/lib/reviewlist.dart | 106 ++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/Mobile/lib/reviewlist.dart b/Mobile/lib/reviewlist.dart index bd0313b..6c21f05 100644 --- a/Mobile/lib/reviewlist.dart +++ b/Mobile/lib/reviewlist.dart @@ -19,57 +19,65 @@ class _ReviewListState extends State { return SideMenu( selectedIndex: -1, - body: Scaffold(body: SingleChildScrollView(child: Container( - decoration: const BoxDecoration(color: Color(0xFFF9F9F9)), - width: MediaQuery.of(context).size.width, - padding: const EdgeInsets.all(20.0), - child: Column(children: - reviews.map((review) => Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - margin: const EdgeInsets.only(bottom: 10), - decoration: const BoxDecoration( - boxShadow: [ - BoxShadow( - color: Color(0x20000000), - offset: Offset(0,1), - blurRadius: 4, - ), - ], - color: Colors.white, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Padding( - padding: EdgeInsets.only(top: 3), - child: Icon(Icons.radio, color: Colors.purple, size: 36), - ), - const SizedBox(width: 20), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(review.title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), - Text(review.content), - ], + body: Scaffold( + backgroundColor: Color(0xFFF9F9F9), + body: SingleChildScrollView(child: Container( + decoration: const BoxDecoration(color: Color(0xFFF9F9F9)), + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.all(20.0), + child: Column(children: + reviews.map((review) => Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + margin: const EdgeInsets.only(bottom: 10), + decoration: const BoxDecoration( + boxShadow: [ + BoxShadow( + color: Color(0x20000000), + offset: Offset(0,1), + blurRadius: 4, ), - ), - ], - ) - )).toList(), + ], + color: Colors.white, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.only(top: 3), + child: Icon(Icons.radio, color: Colors.purple, size: 36), + ), + const SizedBox(width: 20), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(review.title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 24)), + Text(review.content), + const SizedBox(height: 10), + Row(children: [ + for (var i = 0; i < review.rating; i++) const Icon(Icons.star, color: Colors.yellow), + for (var i = review.rating; i < 5; i++) const Icon(Icons.star_border), + ]), + ], + ), + ), + ], + ), + )).toList(), + ), + )), + floatingActionButton: FloatingActionButton( + onPressed: () async { + final review = await Navigator.pushNamed(context, '/create-review', arguments: place) as Review?; + if (review != null) reviews.add(review); + }, + backgroundColor: Colors.blue, + focusColor: Colors.blueGrey, + tooltip: "Write a Review", + child: const Icon(CupertinoIcons.plus), ), - )), - floatingActionButton: FloatingActionButton( - onPressed: () async { - final review = await Navigator.pushNamed(context, '/create-review', arguments: place) as Review?; - if (review != null) reviews.add(review); - }, - backgroundColor: Colors.blue, - focusColor: Colors.blueGrey, - tooltip: "Write a Review", - child: const Icon(CupertinoIcons.plus), ), - )); + ); } } \ No newline at end of file