From 2d197614f81c16011f83ba245d88dd667ab21c3e Mon Sep 17 00:00:00 2001 From: LilleBRG Date: Thu, 22 Aug 2024 13:28:24 +0200 Subject: [PATCH 1/3] init: UI for profile added. Login on API changed --- Mobile/assets/logo.png | Bin 0 -> 30750 bytes .../_composite.stamp | 1 - .../gen_dart_plugin_registrant.stamp | 1 - .../gen_localizations.stamp | 1 - Mobile/build/web/.dockerignore | 11 - Mobile/build/web/.last_build_id | 1 - Mobile/build/web/flutter_service_worker.js | 206 ----------------- Mobile/lib/api.dart | 5 - Mobile/lib/base/sidemenu.dart | 215 +++++++++--------- Mobile/lib/favourites.dart | 1 + Mobile/lib/login.dart | 16 +- Mobile/lib/main.dart | 1 + Mobile/lib/models.dart | 15 ++ Mobile/lib/profile.dart | 88 ++++++- Mobile/lib/register.dart | 16 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 + Mobile/pubspec.lock | 40 ++++ Mobile/pubspec.yaml | 10 +- 18 files changed, 282 insertions(+), 348 deletions(-) create mode 100644 Mobile/assets/logo.png delete mode 100644 Mobile/build/8f264e038ea189c3c7110ab09ae876d1/_composite.stamp delete mode 100644 Mobile/build/8f264e038ea189c3c7110ab09ae876d1/gen_dart_plugin_registrant.stamp delete mode 100644 Mobile/build/8f264e038ea189c3c7110ab09ae876d1/gen_localizations.stamp delete mode 100644 Mobile/build/web/.dockerignore delete mode 100644 Mobile/build/web/.last_build_id delete mode 100644 Mobile/build/web/flutter_service_worker.js create mode 100644 Mobile/lib/models.dart diff --git a/Mobile/assets/logo.png b/Mobile/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ddd1dbfe67b4caf3fe611265c51219727eb055fc GIT binary patch literal 30750 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 { - self.skipWaiting(); - return event.waitUntil( - caches.open(TEMP).then((cache) => { - return cache.addAll( - CORE.map((value) => new Request(value, {'cache': 'reload'}))); - }) - ); -}); -// During activate, the cache is populated with the temp files downloaded in -// install. If this service worker is upgrading from one with a saved -// MANIFEST, then use this to retain unchanged resource files. -self.addEventListener("activate", function(event) { - return event.waitUntil(async function() { - try { - var contentCache = await caches.open(CACHE_NAME); - var tempCache = await caches.open(TEMP); - var manifestCache = await caches.open(MANIFEST); - var manifest = await manifestCache.match('manifest'); - // When there is no prior manifest, clear the entire cache. - if (!manifest) { - await caches.delete(CACHE_NAME); - contentCache = await caches.open(CACHE_NAME); - for (var request of await tempCache.keys()) { - var response = await tempCache.match(request); - await contentCache.put(request, response); - } - await caches.delete(TEMP); - // Save the manifest to make future upgrades efficient. - await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES))); - // Claim client to enable caching on first launch - self.clients.claim(); - return; - } - var oldManifest = await manifest.json(); - var origin = self.location.origin; - for (var request of await contentCache.keys()) { - var key = request.url.substring(origin.length + 1); - if (key == "") { - key = "/"; - } - // If a resource from the old manifest is not in the new cache, or if - // the MD5 sum has changed, delete it. Otherwise the resource is left - // in the cache and can be reused by the new service worker. - if (!RESOURCES[key] || RESOURCES[key] != oldManifest[key]) { - await contentCache.delete(request); - } - } - // Populate the cache with the app shell TEMP files, potentially overwriting - // cache files preserved above. - for (var request of await tempCache.keys()) { - var response = await tempCache.match(request); - await contentCache.put(request, response); - } - await caches.delete(TEMP); - // Save the manifest to make future upgrades efficient. - await manifestCache.put('manifest', new Response(JSON.stringify(RESOURCES))); - // Claim client to enable caching on first launch - self.clients.claim(); - return; - } catch (err) { - // On an unhandled exception the state of the cache cannot be guaranteed. - console.error('Failed to upgrade service worker: ' + err); - await caches.delete(CACHE_NAME); - await caches.delete(TEMP); - await caches.delete(MANIFEST); - } - }()); -}); -// The fetch handler redirects requests for RESOURCE files to the service -// worker cache. -self.addEventListener("fetch", (event) => { - if (event.request.method !== 'GET') { - return; - } - var origin = self.location.origin; - var key = event.request.url.substring(origin.length + 1); - // Redirect URLs to the index.html - if (key.indexOf('?v=') != -1) { - key = key.split('?v=')[0]; - } - if (event.request.url == origin || event.request.url.startsWith(origin + '/#') || key == '') { - key = '/'; - } - // If the URL is not the RESOURCE list then return to signal that the - // browser should take over. - if (!RESOURCES[key]) { - return; - } - // If the URL is the index.html, perform an online-first request. - if (key == '/') { - return onlineFirst(event); - } - event.respondWith(caches.open(CACHE_NAME) - .then((cache) => { - return cache.match(event.request).then((response) => { - // Either respond with the cached resource, or perform a fetch and - // lazily populate the cache only if the resource was successfully fetched. - return response || fetch(event.request).then((response) => { - if (response && Boolean(response.ok)) { - cache.put(event.request, response.clone()); - } - return response; - }); - }) - }) - ); -}); -self.addEventListener('message', (event) => { - // SkipWaiting can be used to immediately activate a waiting service worker. - // This will also require a page refresh triggered by the main worker. - if (event.data === 'skipWaiting') { - self.skipWaiting(); - return; - } - if (event.data === 'downloadOffline') { - downloadOffline(); - return; - } -}); -// Download offline will check the RESOURCES for all files not in the cache -// and populate them. -async function downloadOffline() { - var resources = []; - var contentCache = await caches.open(CACHE_NAME); - var currentContent = {}; - for (var request of await contentCache.keys()) { - var key = request.url.substring(origin.length + 1); - if (key == "") { - key = "/"; - } - currentContent[key] = true; - } - for (var resourceKey of Object.keys(RESOURCES)) { - if (!currentContent[resourceKey]) { - resources.push(resourceKey); - } - } - return contentCache.addAll(resources); -} -// Attempt to download the resource online before falling back to -// the offline cache. -function onlineFirst(event) { - return event.respondWith( - fetch(event.request).then((response) => { - return caches.open(CACHE_NAME).then((cache) => { - cache.put(event.request, response.clone()); - return response; - }); - }).catch((error) => { - return caches.open(CACHE_NAME).then((cache) => { - return cache.match(event.request).then((response) => { - if (response != null) { - return response; - } - throw error; - }); - }); - }) - ); -} diff --git a/Mobile/lib/api.dart b/Mobile/lib/api.dart index 7a4e6a8..2f11dcd 100644 --- a/Mobile/lib/api.dart +++ b/Mobile/lib/api.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; -import 'dart:developer'; enum ApiService { auth, @@ -11,7 +10,6 @@ enum ApiService { Future request(BuildContext context, ApiService service, String method, String path, Object? body) async { - log('hello'); final messenger = ScaffoldMessenger.of(context); final host = switch (service) { @@ -19,9 +17,6 @@ Future request(BuildContext context, ApiService service, String method, ApiService.app => const String.fromEnvironment('APP_SERVICE_HOST'), }; - log('hello'); - log(const String.fromEnvironment('AUTH_SERVICE_HOST')); - final http.Response response; try { diff --git a/Mobile/lib/base/sidemenu.dart b/Mobile/lib/base/sidemenu.dart index 7f74d20..6b410d0 100644 --- a/Mobile/lib/base/sidemenu.dart +++ b/Mobile/lib/base/sidemenu.dart @@ -1,35 +1,27 @@ import 'package:flutter/material.dart'; import 'package:mobile/api.dart' as api; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:google_fonts/google_fonts.dart'; + class SideMenu extends StatefulWidget { final Widget body; + final int selectedIndex ; - const SideMenu({super.key, required this.body}); + const SideMenu({super.key, required this.body, required this.selectedIndex}); @override State createState() => _SideMenuState(); } class _SideMenuState extends State { - final GlobalKey _scaffoldKey = GlobalKey(); - int _selectedIndex = 0; bool _isLoggedIn = false; + late int _selectedIndex; - void _onItemTapped(int index) { - setState(() { - _selectedIndex = index; - }); - } - - Future _postNavigationCallback(dynamic _) async { - final isLoggedIn = await api.isLoggedIn(context); - setState(() => _isLoggedIn = isLoggedIn); - - // Close sidebar - if (mounted && _scaffoldKey.currentState?.isDrawerOpen == true) { - Navigator.pop(context); - } + @override + void initState() { + super.initState(); + _selectedIndex = widget.selectedIndex; // Initialize _selectedIndex with the value from the widget } void _logout() async { @@ -41,7 +33,8 @@ class _SideMenuState extends State { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Successfully logged out'))); - Navigator.pop(context); + Navigator.pushReplacementNamed(context, '/login'); + } } @@ -55,97 +48,103 @@ class _SideMenuState extends State { } @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - title: const Text('SkanTavels'), +Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Row( + children: [ + const SizedBox(width: 55), + Text('SkanTravels', + style: GoogleFonts.jacquesFrancois( + fontSize: 30, + color: Colors.black, + ), + ), + ], ), - drawer: Drawer( - child: ListView( - padding: EdgeInsets.zero, - children: [ - const DrawerHeader( - decoration: BoxDecoration( - color: Colors.blue, + ), + drawer: Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + child: Column( + children: [ + const Image( + image: AssetImage('assets/logo.png'), + height: 100, ), - child: Text('Drawer Header'), - ), - ListTile( - title: const Text('Home'), - leading: const Icon(Icons.home), - selected: _selectedIndex == 0, - onTap: () { - // Update the state of the app - _onItemTapped(0); - // Then close the drawer - Navigator.pushReplacementNamed(context, '/home'); - }, - ), - ListTile( - title: const Text('Favourites'), - leading: const Icon(Icons.star), - selected: _selectedIndex == 1, - onTap: () { - // Update the state of the app - _onItemTapped(1); - // Then close the drawer - Navigator.pushReplacementNamed(context, '/favourites'); - }, - ), - ListTile( - title: const Text('Profile'), - leading: const Icon(Icons.person), - selected: _selectedIndex == 2, - onTap: () { - // Update the state of the app - _onItemTapped(2); - // Then close the drawer - Navigator.pushReplacementNamed(context, '/profile'); - }, - ), - const Divider( - color: Colors.grey, - thickness: 2, - indent: 40, - ), - ...(_isLoggedIn - ? [ - ListTile( - title: const Text('Log out'), - leading: const Icon(Icons.logout), - selected: false, - onTap: _logout, - ) - ] - : [ - ListTile( - title: const Text('Register'), - leading: const Icon(Icons.add_box_outlined), - selected: _selectedIndex == 3, - onTap: () { - // Update the state of the app - _onItemTapped(3); - // Then close the drawer - Navigator.pushReplacementNamed(context, '/register'); - }, - ), - ListTile( - title: const Text('Login'), - leading: const Icon(Icons.login), - selected: _selectedIndex == 4, - onTap: () { - // Update the state of the app - _onItemTapped(4); - // Then close the drawer - Navigator.pushReplacementNamed(context, '/login'); - }, - ) - ]) - ], - ), + Text( + 'SkanTravels', + style: GoogleFonts.jacquesFrancois( + fontSize: 20, + color: Color(0xFF1862E7), + ), + ), + ], + ) + ), + ListTile( + title: const Text('Home'), + leading: const Icon(Icons.home), + selected: _selectedIndex == 0, + onTap: () { + Navigator.pushReplacementNamed(context, '/home'); + }, + ), + ListTile( + title: const Text('Favourites'), + leading: const Icon(Icons.star), + selected: _selectedIndex == 1, + onTap: () { + Navigator.pushReplacementNamed(context, '/favourites'); + }, + ), + ListTile( + title: const Text('Profile'), + leading: const Icon(Icons.person), + selected: _selectedIndex == 2, + onTap: () { + Navigator.pushReplacementNamed(context, '/profile'); + }, + ), + const Divider( + color: Colors.grey, + thickness: 2, + indent: 40, + ), + ...(_isLoggedIn + ? [ + ListTile( + title: const Text('Log out'), + leading: const Icon(Icons.logout), + selected: false, + onTap: _logout, + ) + ] + : [ + ListTile( + title: const Text('Register'), + leading: const Icon(Icons.add_box_outlined), + selected: _selectedIndex == 3, + onTap: () { + Navigator.pushReplacementNamed(context, '/register'); + }, + ), + ListTile( + title: const Text('Login'), + leading: const Icon(Icons.login), + selected: _selectedIndex == 4, + onTap: () { + Navigator.pushReplacementNamed(context, '/login'); + }, + ) + ]) + ], ), - body: widget.body, - ); - } + ), + body: widget.body, + ); } +} \ No newline at end of file diff --git a/Mobile/lib/favourites.dart b/Mobile/lib/favourites.dart index fd01f2a..65d3e74 100644 --- a/Mobile/lib/favourites.dart +++ b/Mobile/lib/favourites.dart @@ -7,6 +7,7 @@ class FavouritesPage extends StatelessWidget { @override Widget build(BuildContext context) { return const SideMenu( + selectedIndex: 1, body: Center( child: Text('This is Page 1'), ), diff --git a/Mobile/lib/login.dart b/Mobile/lib/login.dart index 224b905..f5d2432 100644 --- a/Mobile/lib/login.dart +++ b/Mobile/lib/login.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:mobile/base/sidemenu.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'api.dart' as api; class LoginPage extends StatefulWidget { @@ -36,12 +37,24 @@ class _LoginPageState extends State { @override Widget build(BuildContext context) { return SideMenu( + selectedIndex: 4, body: Scaffold( body: Center( child: Container( constraints: const BoxConstraints(minWidth: 100, maxWidth: 400), child: Column(children: [ - const SizedBox(height: 80), + const Image( + image: AssetImage('assets/logo.png'), + height: 200, + ), + Text( + 'SkanTravels', + style: GoogleFonts.jacquesFrancois( + fontSize: 30, + color: const Color(0xFF1862E7), + ), + ), + const SizedBox(height: 40), const Text('Email'), TextField(controller: emailInput), const SizedBox(height: 30), @@ -54,6 +67,7 @@ class _LoginPageState extends State { const SizedBox(height: 30), ElevatedButton(onPressed: _login, child: const Text('Login')), const SizedBox(height: 10), + const Text('or'), TextButton( child: const Text('Register account'), onPressed: () => diff --git a/Mobile/lib/main.dart b/Mobile/lib/main.dart index 65f5548..9aac884 100644 --- a/Mobile/lib/main.dart +++ b/Mobile/lib/main.dart @@ -46,6 +46,7 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return SideMenu( + selectedIndex: 0, body: Scaffold( key: _scaffoldKey, //drawer: navigationMenu, diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart new file mode 100644 index 0000000..5ab25e3 --- /dev/null +++ b/Mobile/lib/models.dart @@ -0,0 +1,15 @@ +class Favorite { + int id; + String userId; + double lat; + double lng; + + Favorite(this.id, this.userId, this.lat, this.lng); +} + +class Login { + String id; + String token; + + Login(this.id, this.token); +} \ No newline at end of file diff --git a/Mobile/lib/profile.dart b/Mobile/lib/profile.dart index 7b8fcae..51d7622 100644 --- a/Mobile/lib/profile.dart +++ b/Mobile/lib/profile.dart @@ -1,14 +1,90 @@ import 'package:flutter/material.dart'; -import 'base/sidemenu.dart'; // Import the base layout widget +import 'base/sidemenu.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'api.dart' as api; -class ProfilePage extends StatelessWidget { - const ProfilePage({super.key}); + +class ProfilePage extends StatefulWidget { + final String id; + + const ProfilePage({super.key, required this.id}); + + @override + State createState() => _ProfilePageState(); +} + +class _ProfilePageState extends State { + late String _id; + + @override + void initState() { + super.initState(); + _id = widget.id; // Initialize _selectedIndex with the value from the widget + } + +Future _getUser() async { + final token = await api + .request(context, api.ApiService.auth, 'GET', '/api/Users/$_id', { + }); + + if (token == null) return; + + final prefs = await SharedPreferences.getInstance(); + prefs.setString('token', token); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Successfully logged in'))); + Navigator.pushReplacementNamed(context, '/home'); + } + } @override Widget build(BuildContext context) { - return const SideMenu( - body: Center( - child: Text('This is Page 1'), + return SideMenu( + selectedIndex: 2, + body: Stack( + children: [ + const Align( + alignment: Alignment.topLeft, + child: Padding( + padding: EdgeInsets.all(16.0), + child: Text( + 'Your Profile', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + ), + ), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.account_circle, + size: 100, + color: Colors.grey, + ), + const SizedBox(height: 20), + const Text( + 'Username', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + const Text( + 'email@example.com', + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + const SizedBox(height: 50), + ElevatedButton( + onPressed: () { + // Add your edit action here + }, + child: const Text('Edit'), + ), + ], + ), + ), + ], ), ); } diff --git a/Mobile/lib/register.dart b/Mobile/lib/register.dart index 6959ed0..4a26684 100644 --- a/Mobile/lib/register.dart +++ b/Mobile/lib/register.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:mobile/base/sidemenu.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'api.dart' as api; class RegisterPage extends StatefulWidget { @@ -34,12 +35,24 @@ class _RegisterPageState extends State { @override Widget build(BuildContext context) { return SideMenu( + selectedIndex: 3, body: Scaffold( body: Center( child: Container( constraints: const BoxConstraints(minWidth: 100, maxWidth: 400), child: Column(children: [ - const SizedBox(height: 80), + const Image( + image: AssetImage('assets/logo.png'), + height: 200, + ), + Text( + 'SkanTravels', + style: GoogleFonts.jacquesFrancois( + fontSize: 30, + color: Color(0xFF1862E7), + ), + ), + const SizedBox(height: 40), const Text('Username'), TextField(controller: usernameInput), const SizedBox(height: 30), @@ -56,6 +69,7 @@ class _RegisterPageState extends State { ElevatedButton( onPressed: _register, child: const Text('Register')), const SizedBox(height: 10), + const Text('or'), TextButton( child: const Text('Login'), onPressed: () => Navigator.pushReplacementNamed(context, '/login')), diff --git a/Mobile/macos/Flutter/GeneratedPluginRegistrant.swift b/Mobile/macos/Flutter/GeneratedPluginRegistrant.swift index 724bb2a..b8e2b22 100644 --- a/Mobile/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/Mobile/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/Mobile/pubspec.lock b/Mobile/pubspec.lock index 74b0042..d73b93f 100644 --- a/Mobile/pubspec.lock +++ b/Mobile/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + url: "https://pub.dev" + source: hosted + version: "3.0.5" cupertino_icons: dependency: "direct main" description: @@ -112,6 +120,14 @@ packages: description: flutter source: sdk version: "0.0.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 + url: "https://pub.dev" + source: hosted + version: "6.2.1" http: dependency: "direct main" description: @@ -232,6 +248,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + url: "https://pub.dev" + source: hosted + version: "2.2.10" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" path_provider_linux: dependency: transitive description: diff --git a/Mobile/pubspec.yaml b/Mobile/pubspec.yaml index ab442b3..302ed3f 100644 --- a/Mobile/pubspec.yaml +++ b/Mobile/pubspec.yaml @@ -38,6 +38,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6 shared_preferences: ^2.3.2 + google_fonts: ^6.2.1 dev_dependencies: flutter_test: @@ -55,15 +56,12 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. + assets: + - assets/ uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware From 57d52c3fd774c2dd09e0a1531f29a8442bc1c285 Mon Sep 17 00:00:00 2001 From: LilleBRG Date: Fri, 23 Aug 2024 12:34:38 +0200 Subject: [PATCH 2/3] global variables added and implenented with login/logout --- Mobile/lib/api.dart | 8 +++++- Mobile/lib/base/sidemenu.dart | 17 ++++++++---- Mobile/lib/base/variables.dart | 6 +++++ Mobile/lib/login.dart | 29 +++++++++++++++----- Mobile/lib/models.dart | 22 +++++++++++++--- Mobile/lib/profile.dart | 48 ++++++++++++++++++---------------- 6 files changed, 90 insertions(+), 40 deletions(-) create mode 100644 Mobile/lib/base/variables.dart diff --git a/Mobile/lib/api.dart b/Mobile/lib/api.dart index 2f11dcd..827a55d 100644 --- a/Mobile/lib/api.dart +++ b/Mobile/lib/api.dart @@ -3,6 +3,9 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; +import 'base/variables.dart'; + + enum ApiService { auth, app, @@ -61,7 +64,10 @@ Future isLoggedIn(BuildContext context) async { final prefs = await SharedPreferences.getInstance(); final token = prefs.getString('token'); - if (token == null) return false; + if (token == null){ + loggedIn = false; + return false; + } try { String base64 = token.split('.')[1]; diff --git a/Mobile/lib/base/sidemenu.dart b/Mobile/lib/base/sidemenu.dart index 6b410d0..1798fbc 100644 --- a/Mobile/lib/base/sidemenu.dart +++ b/Mobile/lib/base/sidemenu.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:mobile/api.dart' as api; import 'package:shared_preferences/shared_preferences.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'variables.dart'; class SideMenu extends StatefulWidget { @@ -15,7 +16,6 @@ class SideMenu extends StatefulWidget { } class _SideMenuState extends State { - bool _isLoggedIn = false; late int _selectedIndex; @override @@ -28,7 +28,10 @@ class _SideMenuState extends State { final prefs = await SharedPreferences.getInstance(); prefs.remove('token'); - setState(() => _isLoggedIn = false); + setState(() { + loggedIn = false; + }); + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -43,8 +46,12 @@ class _SideMenuState extends State { super.didChangeDependencies(); api - .isLoggedIn(context) - .then((value) => setState(() => _isLoggedIn = value)); + .isLoggedIn(context).then((value) { + setState(() { + loggedIn = value; // Update the second variable here + }); +}); + } @override @@ -114,7 +121,7 @@ Widget build(BuildContext context) { thickness: 2, indent: 40, ), - ...(_isLoggedIn + ...(loggedIn ? [ ListTile( title: const Text('Log out'), diff --git a/Mobile/lib/base/variables.dart b/Mobile/lib/base/variables.dart new file mode 100644 index 0000000..9eb7224 --- /dev/null +++ b/Mobile/lib/base/variables.dart @@ -0,0 +1,6 @@ +import 'package:mobile/models.dart'; + +//Global variables +bool loggedIn = false; +User? user; + diff --git a/Mobile/lib/login.dart b/Mobile/lib/login.dart index f5d2432..5b59fe1 100644 --- a/Mobile/lib/login.dart +++ b/Mobile/lib/login.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:mobile/base/sidemenu.dart'; +import 'package:mobile/base/variables.dart'; +import 'package:mobile/models.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'dart:convert'; import 'api.dart' as api; class LoginPage extends StatefulWidget { @@ -16,16 +19,28 @@ class _LoginPageState extends State { final passwordInput = TextEditingController(); Future _login() async { - final token = await api - .request(context, api.ApiService.auth, 'POST', '/api/Users/login', { - 'email': emailInput.text, - 'password': passwordInput.text, - }); + final token = await api + .request(context, api.ApiService.auth, 'POST', '/api/Users/login', { + 'email': emailInput.text, + 'password': passwordInput.text, + }); - if (token == null) return; + if (token == null) return; + + // Assuming token is a JSON string + Map json = jsonDecode(token); + User jsonUser = User.fromJson(json); final prefs = await SharedPreferences.getInstance(); - prefs.setString('token', token); + prefs.setString('token', jsonUser.token); + prefs.setString('id', jsonUser.id); + + + setState(() + {user = jsonUser; + loggedIn == true; + }); + if (mounted) { ScaffoldMessenger.of(context).showSnackBar( diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart index 5ab25e3..f435a3b 100644 --- a/Mobile/lib/models.dart +++ b/Mobile/lib/models.dart @@ -7,9 +7,23 @@ class Favorite { Favorite(this.id, this.userId, this.lat, this.lng); } -class Login { - String id; +class User { String token; + String id; + String email; + String username; + DateTime createdAt; + + User(this.token, this.id, this.email, this.username, this.createdAt); + + factory User.fromJson(Map json) { + return User( + json['token'], + json['id'], + json['email'], + json['username'], + DateTime.parse(json['createdAt']), + ); + } +} - Login(this.id, this.token); -} \ No newline at end of file diff --git a/Mobile/lib/profile.dart b/Mobile/lib/profile.dart index 51d7622..3b83790 100644 --- a/Mobile/lib/profile.dart +++ b/Mobile/lib/profile.dart @@ -1,44 +1,46 @@ import 'package:flutter/material.dart'; +import 'package:mobile/base/variables.dart'; +import 'package:mobile/models.dart'; import 'base/sidemenu.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'api.dart' as api; +import 'package:mobile/base/variables.dart'; + + class ProfilePage extends StatefulWidget { - final String id; - const ProfilePage({super.key, required this.id}); + const ProfilePage({super.key}); + //const ProfilePage({super.key, required this.id}); + @override State createState() => _ProfilePageState(); } class _ProfilePageState extends State { - late String _id; + late User userData; - @override - void initState() { + @override + void initState() { super.initState(); - _id = widget.id; // Initialize _selectedIndex with the value from the widget - } -Future _getUser() async { - final token = await api - .request(context, api.ApiService.auth, 'GET', '/api/Users/$_id', { - }); - - if (token == null) return; - - final prefs = await SharedPreferences.getInstance(); - prefs.setString('token', token); - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Successfully logged in'))); - Navigator.pushReplacementNamed(context, '/home'); + // Check if the user is logged in when the page is entered + if (loggedIn == false) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please log in'))); + Navigator.pushReplacementNamed(context, '/login'); + }); } + + setState(() { + userData = user!; + }); } + + + @override Widget build(BuildContext context) { return SideMenu( From a4103ab11e754e248c165fb04ac5844287ed5514 Mon Sep 17 00:00:00 2001 From: LilleBRG Date: Fri, 23 Aug 2024 15:19:15 +0200 Subject: [PATCH 3/3] profile data can now be displayed when logged in --- Mobile/lib/api.dart | 2 +- Mobile/lib/login.dart | 9 +-- Mobile/lib/models.dart | 18 +++++- Mobile/lib/profile.dart | 126 ++++++++++++++++++++++++---------------- 4 files changed, 95 insertions(+), 60 deletions(-) diff --git a/Mobile/lib/api.dart b/Mobile/lib/api.dart index 827a55d..9b9250d 100644 --- a/Mobile/lib/api.dart +++ b/Mobile/lib/api.dart @@ -13,7 +13,7 @@ enum ApiService { Future request(BuildContext context, ApiService service, String method, String path, Object? body) async { - final messenger = ScaffoldMessenger.of(context); + final messenger = ScaffoldMessenger.of(context); final host = switch (service) { ApiService.auth => const String.fromEnvironment('AUTH_SERVICE_HOST'), diff --git a/Mobile/lib/login.dart b/Mobile/lib/login.dart index 5b59fe1..c13fffc 100644 --- a/Mobile/lib/login.dart +++ b/Mobile/lib/login.dart @@ -29,17 +29,14 @@ class _LoginPageState extends State { // Assuming token is a JSON string Map json = jsonDecode(token); - User jsonUser = User.fromJson(json); + Login jsonUser = Login.fromJson(json); final prefs = await SharedPreferences.getInstance(); prefs.setString('token', jsonUser.token); prefs.setString('id', jsonUser.id); - - + setState(() - {user = jsonUser; - loggedIn == true; - }); + {loggedIn == true;}); if (mounted) { diff --git a/Mobile/lib/models.dart b/Mobile/lib/models.dart index f435a3b..9e74f90 100644 --- a/Mobile/lib/models.dart +++ b/Mobile/lib/models.dart @@ -7,18 +7,30 @@ class Favorite { Favorite(this.id, this.userId, this.lat, this.lng); } -class User { +class Login { String token; String id; + + Login(this.token, this.id); + + factory Login.fromJson(Map json) { + return Login( + json['token'], + json['id'], + ); + } +} + +class User { + String id; String email; String username; DateTime createdAt; - User(this.token, this.id, this.email, this.username, this.createdAt); + User( this.id, this.email, this.username, this.createdAt); factory User.fromJson(Map json) { return User( - json['token'], json['id'], json['email'], json['username'], diff --git a/Mobile/lib/profile.dart b/Mobile/lib/profile.dart index 3b83790..e76be05 100644 --- a/Mobile/lib/profile.dart +++ b/Mobile/lib/profile.dart @@ -1,45 +1,63 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:mobile/base/variables.dart'; import 'package:mobile/models.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'base/sidemenu.dart'; -import 'package:mobile/base/variables.dart'; - - - +import 'api.dart' as api; class ProfilePage extends StatefulWidget { - const ProfilePage({super.key}); - //const ProfilePage({super.key, required this.id}); - @override State createState() => _ProfilePageState(); } class _ProfilePageState extends State { - late User userData; + User? userData; @override - void initState() { + void initState() { super.initState(); - - // Check if the user is logged in when the page is entered - if (loggedIn == false) { - WidgetsBinding.instance.addPostFrameCallback((_) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Please log in'))); - Navigator.pushReplacementNamed(context, '/login'); - }); - } - - setState(() { - userData = user!; - }); + getProfile(); } + Future getProfile() async { + if (!loggedIn) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Please log in')), + ); + Navigator.pushReplacementNamed(context, '/login'); + }); + return; + } - + if (user != null) { + setState(() { + userData = user!; + }); + } else { + final prefs = await SharedPreferences.getInstance(); + String? id = prefs.getString('id'); + + if (id != null) { + final response = await api + .request(context, api.ApiService.auth, 'GET', '/api/users/$id', null); + + if (response == null) return; + + Map json = jsonDecode(response); + User jsonUser = User.fromJson(json); + + setState(() { + userData = jsonUser; + user = jsonUser; + }); + } + } + } @override Widget build(BuildContext context) { @@ -58,33 +76,41 @@ class _ProfilePageState extends State { ), ), Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.account_circle, - size: 100, - color: Colors.grey, - ), - const SizedBox(height: 20), - const Text( - 'Username', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 10), - const Text( - 'email@example.com', - style: TextStyle(fontSize: 16, color: Colors.grey), - ), - const SizedBox(height: 50), - ElevatedButton( - onPressed: () { - // Add your edit action here - }, - child: const Text('Edit'), - ), - ], - ), + child: userData == null + ? const CircularProgressIndicator() + : Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.account_circle, + size: 100, + color: Colors.grey, + ), + const SizedBox(height: 20), + Text( + userData!.username, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 10), + Text( + userData!.email, + style: const TextStyle( + fontSize: 16, + color: Colors.grey, + ), + ), + const SizedBox(height: 50), + ElevatedButton( + onPressed: () { + // Add your edit action here + }, + child: const Text('Edit'), + ), + ], + ), ), ], ),