Compare commits

...

5 Commits

Author SHA1 Message Date
707ad2e9c4
Merge branch 'favorites' 2024-08-26 09:04:23 +02:00
13e319157d Add favorite page design
Co-authored-by: Reimar <mail@reim.ar>
2024-08-22 12:43:27 +02:00
158bdd91a9 Add favorite menu functionality
Co-authored-by: Reimar <mail@reim.ar>
2024-08-22 12:14:06 +02:00
67f3cd118f
Show favorites on map 2024-08-22 11:46:45 +02:00
cae02bfded Retrieve Favorites from Backend, Add Favorite Model
Co-authored-by: Reimar <mail@reim.ar>
2024-08-22 10:20:59 +02:00
7 changed files with 120 additions and 37 deletions

View File

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -69,7 +71,7 @@ Future<bool> isLoggedIn(BuildContext context) async {
try { try {
String base64 = token.split('.')[1]; String base64 = token.split('.')[1];
base64 += List.filled(4 - base64.length % 4, '=').join(); base64 += List.filled(base64.length % 4 == 0 ? 0 : 4 - base64.length % 4, '=').join();
final payload = jsonDecode(String.fromCharCodes(base64Decode(base64))); final payload = jsonDecode(String.fromCharCodes(base64Decode(base64)));
@ -81,6 +83,7 @@ Future<bool> isLoggedIn(BuildContext context) async {
} catch (e) { } catch (e) {
messenger.showSnackBar(const SnackBar(content: Text('Invalid token, please sign in again'))); messenger.showSnackBar(const SnackBar(content: Text('Invalid token, please sign in again')));
prefs.remove('token'); prefs.remove('token');
debugPrint(e.toString());
return false; return false;
} }

View File

@ -83,14 +83,14 @@ class _SideMenuState extends State<SideMenu> {
}, },
), ),
ListTile( ListTile(
title: const Text('Favourites'), title: const Text('Favorites'),
leading: const Icon(Icons.star), leading: const Icon(Icons.star),
selected: _selectedIndex == 1, selected: _selectedIndex == 1,
onTap: () { onTap: () {
// Update the state of the app // Update the state of the app
_onItemTapped(1); _onItemTapped(1);
// Then close the drawer // Then close the drawer
Navigator.pushReplacementNamed(context, '/favourites'); Navigator.pushReplacementNamed(context, '/favorites');
}, },
), ),
ListTile( ListTile(

61
Mobile/lib/favorites.dart Normal file
View File

@ -0,0 +1,61 @@
import 'dart:convert';
import 'api.dart' as api;
import 'package:flutter/material.dart';
import 'base/sidemenu.dart'; // Import the base layout widget
import 'models.dart';
class FavoritesPage extends StatefulWidget {
const FavoritesPage({super.key});
@override
State<FavoritesPage> createState() => _FavoritesPage();
}
class _FavoritesPage extends State<FavoritesPage> {
List<Favorite> _favorites = [];
@override
void didChangeDependencies() {
super.didChangeDependencies();
api.isLoggedIn(context).then((isLoggedIn) async {
if (!isLoggedIn || !mounted) return;
final response = await api.request(context, api.ApiService.app, 'GET', '/favorites', null);
if (response == null) return;
final List<dynamic> favorites = jsonDecode(response);
setState(() {
_favorites = favorites.map((favorite) => Favorite(favorite['id'], favorite['user_id'], favorite['lat'], favorite['lng'])).toList();
});
});
}
@override
Widget build(BuildContext context) {
return SideMenu(
body: Container(
decoration: BoxDecoration(color: Color(0xFFF9F9F9)),
width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.all(20.0),
child: Column(children:
_favorites.map((favorite) => Container(
width: double.infinity,
padding: const EdgeInsets.all(20.0),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(
color: Color(0x20000000),
offset: Offset(0, 1),
blurRadius: 4,
),
],
color: Colors.white
),
child: const Text("Favorite data here"),
)).toList(),
),
),
);
}
}

View File

@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
import 'base/sidemenu.dart'; // Import the base layout widget
class FavouritesPage extends StatelessWidget {
const FavouritesPage({super.key});
@override
Widget build(BuildContext context) {
return const SideMenu(
body: Center(
child: Text('This is Page 1'),
),
);
}
}

View File

@ -1,11 +1,16 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:mobile/favourites.dart'; import 'package:mobile/favorites.dart';
import 'package:mobile/register.dart'; import 'package:mobile/register.dart';
import 'login.dart'; import 'login.dart';
import 'base/sidemenu.dart'; import 'base/sidemenu.dart';
import 'profile.dart'; import 'profile.dart';
import 'api.dart' as api;
import 'models.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@ -26,7 +31,7 @@ class MyApp extends StatelessWidget {
routes: { routes: {
'/home': (context) => const MyHomePage(), '/home': (context) => const MyHomePage(),
'/profile': (context) => const ProfilePage(), '/profile': (context) => const ProfilePage(),
'/favourites': (context) => const FavouritesPage(), '/favorites': (context) => const FavoritesPage(),
'/login': (context) => const LoginPage(), '/login': (context) => const LoginPage(),
'/register': (context) => const RegisterPage(), '/register': (context) => const RegisterPage(),
}, },
@ -43,6 +48,26 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
List<Favorite> _favorites = [];
@override
void didChangeDependencies() {
super.didChangeDependencies();
api.isLoggedIn(context).then((isLoggedIn) async {
if (!isLoggedIn || !mounted) return;
final response = await api.request(context, api.ApiService.app, 'GET', '/favorites', null);
if (response == null) return;
final List<dynamic> favorites = jsonDecode(response);
setState(() {
_favorites = favorites.map((favorite) => Favorite(favorite['id'], favorite['user_id'], favorite['lat'], favorite['lng'])).toList();
});
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SideMenu( return SideMenu(
@ -50,23 +75,24 @@ class _MyHomePageState extends State<MyHomePage> {
key: _scaffoldKey, key: _scaffoldKey,
//drawer: navigationMenu, //drawer: navigationMenu,
body: FlutterMap( body: FlutterMap(
options: const MapOptions( options: const MapOptions(initialCenter: LatLng(55.9397, 9.5156), initialZoom: 7.0),
initialCenter: LatLng(55.9397, 9.5156), initialZoom: 7.0),
children: [ children: [
openStreetMapTileLayer, openStreetMapTileLayer,
const MarkerLayer(markers: [ ..._favorites.map((favorite) =>
Marker( MarkerLayer(markers: [
point: LatLng(56.465511, 9.411366), Marker(
width: 60, point: LatLng(favorite.lat, favorite.lng),
height: 100, width: 60,
alignment: Alignment.center, height: 100,
child: Icon( alignment: Alignment.center,
Icons.location_pin, child: const Icon(
size: 60, Icons.location_pin,
color: Colors.purple, size: 60,
), color: Colors.yellow,
), )
]), )
])
),
], ],
), ),
), ),

8
Mobile/lib/models.dart Normal file
View File

@ -0,0 +1,8 @@
class Favorite {
int id;
String userId;
double lat;
double lng;
Favorite(this.id, this.userId, this.lat, this.lng);
}

View File

@ -17,10 +17,10 @@ class _RegisterPageState extends State<RegisterPage> {
Future<void> _register() async { Future<void> _register() async {
if (passwordInput.text != confirmPasswordInput.text) { if (passwordInput.text != confirmPasswordInput.text) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Passwords do not match'))); ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Passwords do not match')));
return; return;
} }
final result = await api.request(context, api.ApiService.auth, 'POST', '/api/Users', { final result = await api.request(context, api.ApiService.auth, 'POST', '/api/Users', {
'username': usernameInput.text, 'username': usernameInput.text,
'email': emailInput.text, 'email': emailInput.text,