feat: added authentification and redirection

This commit is contained in:
Alzalia 2025-08-08 01:03:48 +02:00
parent 1c9c5ce5fe
commit ef641d4023
24 changed files with 731 additions and 173 deletions

View file

@ -3,7 +3,9 @@
<application
android:label="seshat"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:fullBackupContent="@xml/backup_rules">
<activity
android:name=".MainActivity"
android:exported="true"

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="FlutterSecureStorage"/>
</full-backup-content>

View file

@ -0,0 +1 @@
const apiBasePath = "bal.ueauvergne.fr";

View file

@ -1,8 +1,23 @@
import "package:flutter/widgets.dart";
import "package:nested/nested.dart";
import "package:provider/provider.dart";
import "package:provider/single_child_widget.dart";
import "package:seshat/data/repositories/auth_repository.dart";
import "package:seshat/data/repositories/owner_repository.dart";
import "package:seshat/data/services/api_client.dart";
import "package:seshat/data/services/auth_client.dart";
import "package:seshat/data/services/websocket_client.dart";
List<SingleChildWidget> get providers {
return [Provider(create: (context) => WebsocketClient())];
return [
Provider(create: (context) => AuthClient()),
Provider(create: (context) => ApiClient(authClient: context.read())),
Provider(create: (context) => WebsocketClient()),
Provider(
create: (context) =>
OwnerRepository(apiClient: context.read(), wsClient: context.read()),
),
ChangeNotifierProvider(
create: (context) => AuthRepository(authClient: context.read()),
),
];
}

View file

@ -0,0 +1,43 @@
import 'package:flutter/foundation.dart';
import 'package:seshat/data/services/auth_client.dart';
import 'package:seshat/utils/result.dart';
class AuthRepository extends ChangeNotifier {
AuthRepository({required AuthClient authClient}) : _authClient = authClient;
final AuthClient _authClient;
bool? _isAuthenticated;
Future<bool> get isLoggedIn async {
if (_isAuthenticated != null) {
return _isAuthenticated!;
}
final result = await _authClient.hasValidToken();
switch (result) {
case Ok():
if (result.value) {
return true;
}
return false;
case Error():
return false;
}
}
Future<Result<void>> login(String username, String password) async {
try {
final result = await _authClient.login(username, password);
switch (result) {
case Ok():
_isAuthenticated = true;
return Result.ok(());
case Error():
return Result.error(result.error);
}
} catch (e, stackTrace) {
debugPrintStack(stackTrace: stackTrace);
return Result.error(Exception(e));
}
}
}

View file

@ -14,32 +14,42 @@ class OwnerRepository {
final ApiClient _apiClient;
final WebsocketClient _wsClient;
List<Owner>? _cachedData;
List<Owner>? _cachedOwners;
Future<Result<Owner>> postOwner(
String firstName,
String lastName,
String contact,
) async {
return Result.ok(
Owner(firstName: firstName, lastName: lastName, contact: contact, id: 50),
);
}
Future<Result<List<Owner>>> getOwners() async {
if (_cachedData == null) {
if (_cachedOwners == null) {
final result = await _apiClient.getOwners();
if (result is Ok<List<Owner>>) {
_cachedData = result.value;
_cachedOwners = result.value;
}
return result;
} else {
return Result.ok(_cachedData!);
return Result.ok(_cachedOwners!);
}
}
Stream<Owner> liveOwners() async* {
await for (String data in _wsClient.connect()) {
await for (String data in await _wsClient.connect()) {
Map<String, dynamic> decodedData = jsonDecode(
data,
).cast<Map<String, dynamic>>();
Owner owner = Owner.fromJSON(decodedData);
if (_cachedData == null) {
getOwners();
if (_cachedOwners == null) {
await getOwners();
} else {
_cachedData!.add(owner);
_cachedOwners!.add(owner);
yield* Stream.value(owner);
}
}

View file

@ -1,8 +1,61 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:seshat/config/constants.dart';
import 'package:seshat/data/services/auth_client.dart';
import 'package:seshat/domain/models/owner.dart';
import 'package:seshat/utils/command.dart';
import 'package:seshat/utils/result.dart';
typedef AuthHeaderProvider = String? Function();
class ApiClient {
ApiClient({
String? host,
int? port,
HttpClient Function()? clientFactory,
required AuthClient authClient,
}) : _authClient = authClient;
final AuthClient _authClient;
late final Command0 load;
String? token;
bool isReady = false;
FlutterSecureStorage? _secureStorage;
Future<void> _initStore() async {
_secureStorage ??= const FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
);
}
Future<Result<List<Owner>>> getOwners() async {
return Result.ok(<Owner>[]);
final client = HttpClient();
try {
await _initStore();
final request = await client.getUrl(
Uri.parse("https://$apiBasePath/owners"),
);
final token = await _secureStorage!.read(key: "token");
debugPrint("\n\n\n\nFOUND TOKEN : $token\n\n\n\n");
// await _authHeader(request.headers);
request.headers.add(HttpHeaders.authorizationHeader, "Bearer $token");
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(Utf8Decoder()).join();
final json = jsonDecode(stringData) as List<dynamic>;
return Result.ok(
json.map((element) => Owner.fromJSON(element)).toList(),
);
} else {
return const Result.error(HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
}

View file

@ -0,0 +1,77 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:seshat/config/constants.dart';
import 'package:seshat/utils/result.dart';
import "package:http/http.dart" as http;
class AuthClient {
AuthClient();
FlutterSecureStorage? _secureStorage;
Future<void> _initStore() async {
_secureStorage ??= const FlutterSecureStorage(
aOptions: AndroidOptions(encryptedSharedPreferences: true),
);
}
Future<Result<bool>> hasValidToken() async {
try {
await _initStore();
bool hasToken = await _secureStorage!.containsKey(key: "token");
debugPrint("\n\n\n${hasToken == true} => HAS_TOKEN\n\n\n");
if (hasToken) {
var token = await _secureStorage!.read(key: "token");
var url = Uri.parse("https://$apiBasePath/token-check");
var response = await http.post(
url,
headers: {"Content-Type": "application/json"},
body: jsonEncode({"token": token}),
);
debugPrint(
"\n\n\n${response.body is String} => ${response.body}\n\n\n",
);
if (response.body == "true") {
return Result.ok(true);
}
}
return Result.ok(false);
} catch (e) {
debugPrint(e.toString());
return Result.error(Exception(e));
}
}
Future<Result<String>> login(String username, String password) async {
var client = http.Client();
try {
await _initStore();
var url = Uri.parse("https://$apiBasePath/auth");
var response = await client.post(
url,
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
body: jsonEncode({"password": password, "username": username}),
);
if (response.statusCode == 200) {
var json = jsonDecode(response.body);
await _secureStorage!.write(key: "token", value: json["access_token"]);
return Result.ok(json["access_token"]);
} else if (response.statusCode == 401) {
return Result.error(Exception("Wrong credentials"));
} else {
return Result.error(Exception("Token creation error"));
}
} catch (e, stackTrace) {
debugPrint(e.toString());
debugPrintStack(stackTrace: stackTrace);
return Result.error(Exception(e));
} finally {
client.close();
}
}
}

View file

@ -1,10 +1,16 @@
import 'package:seshat/config/constants.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
class WebsocketClient {
static const String _host = "ws://bal.ninjdai.fr:3000";
Future<Stream<dynamic>> connect() async {
final channel = WebSocketChannel.connect(
Uri.parse("wss://$apiBasePath/ws"),
);
await channel.ready;
channel.sink.add("json-token: ");
Stream<dynamic> connect() {
final channel = WebSocketChannel.connect(Uri.parse("$_host/ws"));
return channel.stream;
}
}

View file

@ -1,12 +1,15 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:seshat/config/dependencies.dart';
import 'package:seshat/routing/router.dart';
void main() {
Logger.root.level = Level.ALL;
WidgetsFlutterBinding.ensureInitialized();
// runApp(MultiProvider(providers: providers, child: const MyApp()));
runApp(const MyApp());
runApp(MultiProvider(providers: providers, child: const MyApp()));
// runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@ -15,7 +18,7 @@ class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp.router(routerConfig: router());
return MaterialApp.router(routerConfig: router(context.read()));
}
}

View file

@ -1,12 +1,31 @@
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:seshat/data/repositories/auth_repository.dart';
import 'package:seshat/routing/routes.dart';
import 'package:seshat/ui/add_page/view_model/add_view_model.dart';
import 'package:seshat/ui/add_page/widgets/add_page.dart';
import 'package:seshat/ui/auth/viewmodel/login_view_model.dart';
import 'package:seshat/ui/auth/widgets/login_page.dart';
import 'package:seshat/ui/home_page/home_page.dart';
import 'package:seshat/ui/sell_page/sell_page.dart';
GoRouter router() => GoRouter(
GoRouter router(AuthRepository authRepository) => GoRouter(
initialLocation: Routes.add,
redirect: (context, state) async {
final loggedIn = await context.read<AuthRepository>().isLoggedIn;
final logginIn = state.matchedLocation == Routes.login;
if (!loggedIn) {
return Routes.login;
}
if (logginIn) {
return Routes.add;
}
return null;
},
refreshListenable: authRepository,
routes: [
GoRoute(
path: Routes.home,
@ -14,8 +33,10 @@ GoRouter router() => GoRouter(
routes: [
GoRoute(
path: Routes.add,
pageBuilder: (context, state) =>
NoTransitionPage(child: AddPage(viewModel: AddViewModel())),
pageBuilder: (context, state) {
final viewModel = AddViewModel(ownerRepository: context.read());
return NoTransitionPage(child: AddPage(viewModel: viewModel));
},
// routes: [
// GoRoute(path: Routes.addForm),
// GoRoute(path: Routes.addOwner),
@ -26,6 +47,13 @@ GoRouter router() => GoRouter(
path: Routes.sell,
pageBuilder: (context, state) => NoTransitionPage(child: SellPage()),
),
GoRoute(
path: Routes.login,
pageBuilder: (context, state) {
final viewModel = LoginViewModel(authRepository: context.read());
return NoTransitionPage(child: LoginPage(viewModel: viewModel));
},
),
],
),
],

View file

@ -10,4 +10,7 @@ abstract final class Routes {
// ==[ SELL ]==
static const sell = '/sell';
// ==[ AUTH ]==
static const login = '/login';
}

View file

@ -1,12 +1,19 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:mobile_scanner/mobile_scanner.dart';
import 'package:seshat/data/repositories/owner_repository.dart';
import 'package:seshat/domain/models/book.dart';
import 'package:seshat/domain/models/owner.dart';
import 'package:seshat/utils/command.dart';
import 'package:seshat/utils/result.dart';
class AddViewModel extends ChangeNotifier {
AddViewModel();
AddViewModel({required OwnerRepository ownerRepository})
: _ownerRepository = ownerRepository {
load = Command0(_load)..execute();
}
final OwnerRepository _ownerRepository;
/*
* ====================
@ -21,37 +28,37 @@ class AddViewModel extends ChangeNotifier {
notifyListeners();
}
final List<Owner> _owners = [];
List<Owner> _owners = [];
List<Owner>? get owners => _owners;
Owner addOwner(String firstName, String lastName, String contact) {
if (_owners.isEmpty) {
_owners.add(
Owner(
firstName: firstName,
lastName: lastName,
contact: contact,
id: 1,
),
);
} else {
_owners.add(
Owner(
firstName: firstName,
lastName: lastName,
contact: contact,
id: _owners.last.id + 1,
),
);
}
notifyListeners();
return Owner(
firstName: firstName,
lastName: lastName,
contact: contact,
id: 0,
Future<Result<Owner>> addOwner(
String firstName,
String lastName,
String contact,
) async {
final result = await _ownerRepository.postOwner(
firstName,
lastName,
contact,
);
switch (result) {
case Ok():
final secondResult = await _ownerRepository.getOwners();
switch (secondResult) {
case Ok():
_owners = secondResult.value;
_currentOwner = result.value;
notifyListeners();
return Result.ok(result.value);
case Error():
return Result.error(secondResult.error);
}
case Error():
return Result.error(result.error);
}
}
/*
@ -87,8 +94,38 @@ class AddViewModel extends ChangeNotifier {
);
}
/// Sens an api request with
/// Sends an api request with
// Result<BookInstance> newBookInstance() {
// };
/*
* =================================
* =====[ COMMAND AND LOADING ]=====
* =================================
*/
late final Command0 load;
bool isLoaded = false;
Future<Result<void>> _load() async {
return await _loadOwners();
}
Future<Result<void>> _loadOwners() async {
final result = await _ownerRepository.getOwners();
switch (result) {
case Ok():
_owners = result.value;
isLoaded = true;
case Error():
debugPrint("Oupsie daysie, ${result.error}");
}
notifyListeners();
_ownerRepository.liveOwners().listen((Owner owner) {
_owners.add(owner);
notifyListeners();
});
return result;
}
}

View file

@ -32,130 +32,137 @@ class _AddPageState extends State<AddPage> {
// builder: (context, screen, child) {
return Scaffold(
bottomNavigationBar: AppNavigationBar(startIndex: 1),
body: Stack(
children: [
ColoredBox(color: Colors.black),
MobileScanner(
controller: controller,
onDetect: (barcodes) async {
if (widget.viewModel.currentOwner == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Attention : vous devez choisir un·e propriétaire",
),
behavior: SnackBarBehavior.floating,
),
);
return;
}
void setPrice(num newPrice) async {
setState(() {
price = newPrice;
});
}
Result<Book> result = await widget.viewModel.scanBook(barcodes);
switch (result) {
case Ok():
await _confirmationDialogBuilder(
context,
setPrice,
controller,
widget.viewModel,
result.value,
);
break;
case Error():
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Erreur : ${result.error}"),
behavior: SnackBarBehavior.floating,
),
);
break;
}
},
),
SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Card(
margin: EdgeInsets.symmetric(horizontal: 50),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListenableBuilder(
listenable: widget.viewModel,
builder: (context, child) => ListTile(
leading: Icon(Icons.person),
title: TextButton(
child: Text(
(widget.viewModel.currentOwner == null)
? "Aucun"
: "${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}",
),
onPressed: () => _ownerDialogBuilder(
context,
controller,
widget.viewModel,
),
),
),
),
ListTile(
leading: Icon(Icons.attach_money),
title: TextButton(
child: Text(
(widget.viewModel.askPrice)
? "Demander à chaque fois"
: "Prix libre toujours",
),
onPressed: () {
setState(() {
widget.viewModel.askPrice =
!widget.viewModel.askPrice;
});
},
),
),
],
body: ListenableBuilder(
listenable: widget.viewModel,
builder: (context, child) => switch (widget.viewModel.isLoaded) {
false => CircularProgressIndicator(),
true => Stack(
children: [
ColoredBox(color: Colors.black),
MobileScanner(
controller: controller,
onDetect: (barcodes) async {
if (widget.viewModel.currentOwner == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Attention : vous devez choisir un·e propriétaire",
),
behavior: SnackBarBehavior.floating,
),
),
),
SizedBox(height: 100),
SvgPicture.asset('assets/scan-overlay.svg'),
],
);
return;
}
void setPrice(num newPrice) async {
setState(() {
price = newPrice;
});
}
Result<Book> result = await widget.viewModel.scanBook(
barcodes,
);
switch (result) {
case Ok():
await _confirmationDialogBuilder(
context,
setPrice,
controller,
widget.viewModel,
result.value,
);
break;
case Error():
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Erreur : ${result.error}"),
behavior: SnackBarBehavior.floating,
),
);
break;
}
},
),
),
),
SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Center(
child: TextButton(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(theme.cardColor),
),
onPressed: () => _formDialogBuilder(
context,
controller,
widget.viewModel,
),
child: Text("Enregistrer manuellement"),
SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Card(
margin: EdgeInsets.symmetric(horizontal: 50),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.person),
title: TextButton(
child: Text(
(widget.viewModel.currentOwner == null)
? "Aucun"
: "${widget.viewModel.currentOwner!.firstName} ${widget.viewModel.currentOwner!.lastName}",
),
onPressed: () => _ownerDialogBuilder(
context,
controller,
widget.viewModel,
),
),
),
ListTile(
leading: Icon(Icons.attach_money),
title: TextButton(
child: Text(
(widget.viewModel.askPrice)
? "Demander à chaque fois"
: "Prix libre toujours",
),
onPressed: () {
setState(() {
widget.viewModel.askPrice =
!widget.viewModel.askPrice;
});
},
),
),
],
),
),
),
SizedBox(height: 100),
SvgPicture.asset('assets/scan-overlay.svg'),
],
),
),
],
),
),
SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Center(
child: TextButton(
style: ButtonStyle(
backgroundColor: WidgetStatePropertyAll(
theme.cardColor,
),
),
onPressed: () => _formDialogBuilder(
context,
controller,
widget.viewModel,
),
child: Text("Enregistrer manuellement"),
),
),
],
),
),
],
),
],
},
),
);
// },

View file

@ -141,11 +141,14 @@ class _OwnerPopupState extends State<OwnerPopup> {
),
SizedBox(height: 10),
ElevatedButton(
onPressed: () {
onPressed: () async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
widget.viewModel.currentOwner = widget.viewModel
.addOwner(firstName!, lastName!, contact!);
await widget.viewModel.addOwner(
firstName!,
lastName!,
contact!,
);
setState(() {
showNewOwner = false;
});

View file

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:seshat/data/repositories/auth_repository.dart';
import 'package:seshat/utils/command.dart';
import 'package:seshat/utils/result.dart';
class LoginViewModel extends ChangeNotifier {
LoginViewModel({required AuthRepository authRepository})
: _authRepository = authRepository {
login = Command1<void, (String username, String password)>(_login);
}
final AuthRepository _authRepository;
late Command1 login;
Future<Result<void>> _login((String, String) credentials) async {
final (username, password) = credentials;
final result = await _authRepository.login(username, password);
if (result is Error<void>) {
debugPrint("Hehe no");
}
return result;
}
}

View file

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:seshat/routing/routes.dart';
import 'package:seshat/ui/auth/viewmodel/login_view_model.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key, required this.viewModel});
final LoginViewModel viewModel;
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _username = TextEditingController(
text: "ueauvergne",
);
final TextEditingController _password = TextEditingController(
text: "ueauvergne",
);
@override
void initState() {
super.initState();
widget.viewModel.login.addListener(_onResult);
}
@override
void didUpdateWidget(covariant LoginPage oldWidget) {
super.didUpdateWidget(oldWidget);
oldWidget.viewModel.removeListener(_onResult);
widget.viewModel.login.addListener(_onResult);
}
@override
void dispose() {
widget.viewModel.login.removeListener(_onResult);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextField(controller: _username),
TextField(controller: _password),
ListenableBuilder(
listenable: widget.viewModel.login,
builder: (context, child) {
return FilledButton(
onPressed: () {
widget.viewModel.login.execute((
_username.value.text,
_password.value.text,
));
},
child: Text("Connexion"),
);
},
),
],
),
);
}
void _onResult() {
if (widget.viewModel.login.completed) {
widget.viewModel.login.clearResult();
context.go(Routes.add);
}
if (widget.viewModel.login.error) {
widget.viewModel.login.clearResult();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Une erreur est survenue lors de la connexion."),
action: SnackBarAction(
label: "Réessayer",
onPressed: () => widget.viewModel.login.execute((
_username.value.text,
_password.value.text,
)),
),
),
);
}
}
}

View file

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
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);
}

View file

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View file

@ -5,8 +5,12 @@
import FlutterMacOS
import Foundation
import flutter_secure_storage_macos
import mobile_scanner
import path_provider_foundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
}

View file

@ -73,6 +73,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
flutter:
dependency: "direct main"
description: flutter
@ -86,6 +94,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
url: "https://pub.dev"
source: hosted
version: "9.2.4"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
url: "https://pub.dev"
source: hosted
version: "1.2.3"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev"
source: hosted
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev"
source: hosted
version: "3.1.2"
flutter_svg:
dependency: "direct main"
description:
@ -128,6 +184,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
leak_tracker:
dependency: transitive
description:
@ -224,6 +288,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
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: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
url: "https://pub.dev"
source: hosted
version: "2.2.17"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
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"
petitparser:
dependency: transitive
description:
@ -232,6 +344,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
@ -373,6 +493,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.3"
win32:
dependency: transitive
description:
name: win32
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
url: "https://pub.dev"
source: hosted
version: "5.14.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:

View file

@ -42,6 +42,7 @@ dependencies:
http: ^1.4.0
web_socket_channel: ^3.0.3
nested: ^1.0.0
flutter_secure_storage: ^9.2.4
dev_dependencies:
flutter_test:

View file

@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
}

View file

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST