Compare commits

...

2 Commits

Author SHA1 Message Date
debbddd05b
Implement login and save token 2024-08-21 10:03:03 +02:00
c9769fcada
Add environment variables 2024-08-21 09:19:04 +02:00
16 changed files with 259 additions and 23 deletions

2
Mobile/.gitignore vendored
View File

@ -41,3 +41,5 @@ app.*.map.json
/android/app/debug /android/app/debug
/android/app/profile /android/app/profile
/android/app/release /android/app/release
environment.json

View File

@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
<option name="additionalArgs" value="--dart-define-from-file environment.json" />
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
<method v="2" />
</configuration>
</component>

15
Mobile/.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch",
"request": "launch",
"type": "dart",
"program": "lib/main.dart",
"args": [
"--dart-define-from-file",
"environment.json"
]
}
]
}

6
Mobile/README.md Normal file
View File

@ -0,0 +1,6 @@
# SkanTravels frontend
### Running
- Copy `environment.example.json` to `environment.json` and fill out the values
- Run `flutter run --dart-define-from-file environment.json`

View File

@ -1,4 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="mobile" android:label="mobile"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -0,0 +1,4 @@
{
"AUTH_SERVICE_HOST": "http://localhost:5287",
"APP_SERVICE_HOST": "http://localhost:8080"
}

53
Mobile/lib/api.dart Normal file
View File

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
enum ApiService {
auth,
app,
}
Future<String?> request(BuildContext context, ApiService service, String method, String path, Object? body) async {
final messenger = ScaffoldMessenger.of(context);
final host = switch (service) {
ApiService.auth => const String.fromEnvironment('AUTH_SERVICE_HOST'),
ApiService.app => const String.fromEnvironment('APP_SERVICE_HOST'),
};
final http.Response response;
try {
if (method == 'GET') {
response = await http.get(Uri.parse(host + path));
} else {
final function = switch (method) {
'POST' => http.post,
'PUT' => http.put,
'DELETE' => http.delete,
_ => throw const FormatException('Invalid method'),
};
response = await function(
Uri.parse(host + path),
headers: {'Content-Type': 'application/json'},
body: body != null ? jsonEncode(body) : null,
);
}
} catch (_) {
messenger.showSnackBar(const SnackBar(content: Text('Unable to connect to server')));
return null;
}
if (response.statusCode < 200 || response.statusCode >= 300) {
try {
final json = jsonDecode(response.body);
messenger.showSnackBar(SnackBar(content: Text(json['message'])));
} catch (_) {
messenger.showSnackBar(SnackBar(content: Text('Something went wrong (HTTP ${response.statusCode})')));
}
return null;
}
return response.body;
}

View File

@ -1,5 +1,9 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'register.dart'; import 'register.dart';
import 'api.dart' as api;
class LoginPage extends StatefulWidget { class LoginPage extends StatefulWidget {
const LoginPage({super.key, required this.title}); const LoginPage({super.key, required this.title});
@ -11,6 +15,26 @@ class LoginPage extends StatefulWidget {
} }
class _LoginPageState extends State<LoginPage> { class _LoginPageState extends State<LoginPage> {
final emailInput = TextEditingController();
final passwordInput = TextEditingController();
Future<void> _login() async {
final token = await api.request(context, api.ApiService.auth, 'POST', '/api/Users/login', {
'email': emailInput.text,
'password': passwordInput.text,
});
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.pop(context);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -25,16 +49,16 @@ class _LoginPageState extends State<LoginPage> {
children: [ children: [
const SizedBox(height: 80), const SizedBox(height: 80),
const Text('Email'), const Text('Email'),
const TextField(), TextField(controller: emailInput),
const SizedBox(height: 30), const SizedBox(height: 30),
const Text('Password'), const Text('Password'),
const TextField(obscureText: true, enableSuggestions: false, autocorrect: false), TextField(controller: passwordInput, obscureText: true, enableSuggestions: false, autocorrect: false),
const SizedBox(height: 30), const SizedBox(height: 30),
ElevatedButton(child: const Text('Log ind'), onPressed: () => Navigator.pop(context)), ElevatedButton(onPressed: _login, child: const Text('Log ind')),
const SizedBox(height: 10), const SizedBox(height: 10),
TextButton( TextButton(
child: const Text('Registrer konto'), child: const Text('Registrer konto'),
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const RegisterPage(title: 'Registrer'))), onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const RegisterPage(title: 'Registrer'))),
) )
] ]
) )
@ -42,4 +66,11 @@ class _LoginPageState extends State<LoginPage> {
) )
); );
} }
@override
void dispose() {
emailInput.dispose();
passwordInput.dispose();
super.dispose();
}
} }

View File

@ -37,7 +37,7 @@ class _RegisterPageState extends State<RegisterPage> {
const SizedBox(height: 10), const SizedBox(height: 10),
TextButton( TextButton(
child: const Text('Log ind'), child: const Text('Log ind'),
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const LoginPage(title: 'Log ind'))) onPressed: () => Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => const LoginPage(title: 'Log ind')))
), ),
] ]
) )

View File

@ -5,6 +5,8 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import shared_preferences_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
} }

View File

@ -8,5 +8,7 @@
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>com.apple.security.network.client</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -4,5 +4,7 @@
<dict> <dict>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.network.client</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -65,6 +65,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -91,6 +107,11 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@ -211,6 +232,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.0" version: "1.9.0"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
polylabel: polylabel:
dependency: transitive dependency: transitive
description: description:
@ -227,6 +288,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974
url: "https://pub.dev"
source: hosted
version: "2.3.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
url: "https://pub.dev"
source: hosted
version: "2.5.2"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -328,6 +445,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
url: "https://pub.dev"
source: hosted
version: "1.0.4"
sdks: sdks:
dart: ">=3.3.4 <4.0.0" dart: ">=3.4.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54" flutter: ">=3.22.0"

View File

@ -37,6 +37,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
shared_preferences: ^2.3.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -1,16 +0,0 @@
# mobile
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.