Compare commits
2 Commits
v0.1.2-dev
...
v0.1.2-dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 8116cfe7b1 | |||
| 4d483495fc |
@@ -107,7 +107,7 @@ jobs:
|
||||
SUDO=""
|
||||
fi
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y unzip xz-utils zip libstdc++6 libglu1-mesa clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev curl jq
|
||||
$SUDO apt-get install -y unzip xz-utils zip libstdc++6 libglu1-mesa clang cmake ninja-build pkg-config libgtk-3-dev libsecret-1-dev liblzma-dev curl jq
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
@@ -141,6 +141,15 @@ jobs:
|
||||
- android-build
|
||||
- linux-build
|
||||
steps:
|
||||
- name: Install jq
|
||||
run: |
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
SUDO="sudo"
|
||||
else
|
||||
SUDO=""
|
||||
fi
|
||||
$SUDO apt-get update
|
||||
$SUDO apt-get install -y jq
|
||||
- name: Download Android APK
|
||||
if: ${{ github.ref == 'refs/heads/dev' }}
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -220,7 +229,6 @@ jobs:
|
||||
>/dev/null
|
||||
done
|
||||
|
||||
|
||||
release-master:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
|
||||
@@ -37,6 +37,7 @@ void main() {
|
||||
ProxyProvider<AuthService, void>(
|
||||
update: (_, auth, __) {
|
||||
api.setTokenProvider(() => auth.token);
|
||||
api.setUnauthorizedHandler(() => auth.handleTokenExpired());
|
||||
},
|
||||
),
|
||||
ChangeNotifierProxyProvider<ApiService, DataService>(
|
||||
|
||||
@@ -2,10 +2,12 @@ import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
typedef TokenProvider = String? Function();
|
||||
typedef UnauthorizedHandler = Future<void> Function();
|
||||
|
||||
class ApiService {
|
||||
final String baseUrl;
|
||||
TokenProvider? _getToken;
|
||||
UnauthorizedHandler? _onUnauthorized;
|
||||
|
||||
ApiService({required this.baseUrl});
|
||||
|
||||
@@ -13,6 +15,10 @@ class ApiService {
|
||||
_getToken = provider;
|
||||
}
|
||||
|
||||
void setUnauthorizedHandler(UnauthorizedHandler handler) {
|
||||
_onUnauthorized = handler;
|
||||
}
|
||||
|
||||
Map<String, String> _buildHeaders(Map<String, String>? extra) {
|
||||
final token = _getToken?.call();
|
||||
final headers = {'accept': 'application/json', ...?extra};
|
||||
@@ -85,12 +91,19 @@ class ApiService {
|
||||
return {'Content-Type': 'application/json', if (extra != null) ...extra};
|
||||
}
|
||||
|
||||
dynamic _processResponse(http.Response res) {
|
||||
Future<dynamic> _processResponse(http.Response res) async {
|
||||
final body = res.body.isNotEmpty ? jsonDecode(res.body) : null;
|
||||
if (res.statusCode >= 200 && res.statusCode < 300) {
|
||||
return body;
|
||||
} else {
|
||||
throw Exception('API error ${res.statusCode}: $body');
|
||||
}
|
||||
|
||||
if (res.statusCode == 401 &&
|
||||
body is Map<String, dynamic> &&
|
||||
body['detail'] == 'Not authenticated' &&
|
||||
_onUnauthorized != null) {
|
||||
await _onUnauthorized!();
|
||||
}
|
||||
|
||||
throw Exception('API error ${res.statusCode}: $body');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:mileograph_flutter/objects/objects.dart';
|
||||
import 'package:mileograph_flutter/services/apiService.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class AuthService extends ChangeNotifier {
|
||||
final ApiService api;
|
||||
static const _tokenKey = 'auth_token';
|
||||
bool _restoring = false;
|
||||
|
||||
// secure storage instance
|
||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||
|
||||
AuthService({required this.api});
|
||||
|
||||
AuthenticatedUserData? _user;
|
||||
@@ -73,9 +76,10 @@ class AuthService extends ChangeNotifier {
|
||||
if (_restoring || _user != null) return;
|
||||
_restoring = true;
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final token = prefs.getString(_tokenKey);
|
||||
// read token from secure storage
|
||||
final token = await _storage.read(key: _tokenKey);
|
||||
if (token == null || token.isEmpty) return;
|
||||
|
||||
final userResponse = await api.get(
|
||||
'/users/me',
|
||||
headers: {
|
||||
@@ -83,6 +87,7 @@ class AuthService extends ChangeNotifier {
|
||||
'accept': 'application/json',
|
||||
},
|
||||
);
|
||||
|
||||
setLoginData(
|
||||
userId: userResponse['user_id'],
|
||||
username: userResponse['username'],
|
||||
@@ -98,13 +103,11 @@ class AuthService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> _persistToken(String token) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_tokenKey, token);
|
||||
await _storage.write(key: _tokenKey, value: token);
|
||||
}
|
||||
|
||||
Future<void> _clearToken() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_tokenKey);
|
||||
await _storage.delete(key: _tokenKey);
|
||||
}
|
||||
|
||||
Future<void> register({
|
||||
@@ -126,9 +129,13 @@ class AuthService extends ChangeNotifier {
|
||||
await api.postForm('/register', formData);
|
||||
}
|
||||
|
||||
void logout() {
|
||||
Future<void> handleTokenExpired() async {
|
||||
_user = null;
|
||||
_clearToken();
|
||||
await _clearToken();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void logout() {
|
||||
handleTokenExpired(); // reuse
|
||||
}
|
||||
}
|
||||
|
||||
36
lib/services/tokenStorageService.dart
Normal file
36
lib/services/tokenStorageService.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
class TokenStorageService {
|
||||
// Singleton pattern (optional but usually handy for services)
|
||||
TokenStorageService._internal();
|
||||
|
||||
static final TokenStorageService _instance = TokenStorageService._internal();
|
||||
|
||||
factory TokenStorageService() => _instance;
|
||||
|
||||
static const _tokenKey = 'auth_token';
|
||||
|
||||
// Use const constructor for secure storage
|
||||
final FlutterSecureStorage _storage = const FlutterSecureStorage();
|
||||
|
||||
/// Save or update the token
|
||||
Future<void> setToken(String token) async {
|
||||
await _storage.write(key: _tokenKey, value: token);
|
||||
}
|
||||
|
||||
/// Retrieve the stored token (null if none)
|
||||
Future<String?> getToken() async {
|
||||
return _storage.read(key: _tokenKey);
|
||||
}
|
||||
|
||||
/// Delete the token
|
||||
Future<void> clearToken() async {
|
||||
await _storage.delete(key: _tokenKey);
|
||||
}
|
||||
|
||||
/// Optional: check quickly if a token exists
|
||||
Future<bool> hasToken() async {
|
||||
final token = await getToken();
|
||||
return token != null && token.isNotEmpty;
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,13 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin.h>
|
||||
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
|
||||
|
||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||
g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
|
||||
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
|
||||
g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar =
|
||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin");
|
||||
flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
flutter_secure_storage_linux
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
@@ -6,9 +6,13 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import dynamic_color
|
||||
import flutter_secure_storage_darwin
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
}
|
||||
|
||||
80
pubspec.lock
80
pubspec.lock
@@ -94,6 +94,54 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_secure_storage:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_secure_storage
|
||||
sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.0"
|
||||
flutter_secure_storage_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_darwin
|
||||
sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
flutter_secure_storage_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_platform_interface
|
||||
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_secure_storage_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_web
|
||||
sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
flutter_secure_storage_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_windows
|
||||
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -216,6 +264,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.19"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -405,6 +477,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.15.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -35,6 +35,7 @@ dependencies:
|
||||
http: ^1.4.0
|
||||
provider: ^6.1.5
|
||||
dynamic_color: ^1.6.6
|
||||
flutter_secure_storage: ^10.0.0
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
#include "generated_plugin_registrant.h"
|
||||
|
||||
#include <dynamic_color/dynamic_color_plugin_c_api.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
|
||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
DynamicColorPluginCApiRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
|
||||
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
list(APPEND FLUTTER_PLUGIN_LIST
|
||||
dynamic_color
|
||||
flutter_secure_storage_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
|
||||
Reference in New Issue
Block a user