Compare commits

...

16 Commits

Author SHA1 Message Date
452faf05d8 Merge pull request 'feature/pagination' (#42) from feature/pagination into main
Reviewed-on: #42
2025-06-02 21:22:13 +00:00
3a64f1ae36 add pagination for other pages 2025-06-02 23:03:49 +02:00
afae3293c4 add current date 2025-06-02 22:22:33 +02:00
600bf8d2f4 add pagination with limit 2025-05-30 23:39:50 +02:00
ec7a286074 pagination wip 2025-05-27 23:36:19 +02:00
6c1c650fa1 add last data 2025-05-25 23:39:37 +02:00
7468c5f24c change fetchCOunt 2025-05-23 23:47:17 +02:00
1bcc8372c6 pagination work for main page 2025-05-23 00:01:48 +02:00
510e366216 add pagination 2025-03-24 22:47:49 +01:00
52580c6568 fetchCount to 0 2025-03-24 22:10:49 +01:00
4f7c8f60d0 add increment and decrement to pagination 2025-03-19 23:40:38 +01:00
ce2a061bf0 Merge pull request 'persist encrypt shared' (#41) from feature/add-encrypted-sharepreferences into main
Reviewed-on: #41
2025-03-12 21:58:27 +00:00
04f4b7ce8e persist encrypt shared 2025-03-12 22:48:35 +01:00
78ea5f0c10 Merge pull request 'add remember me' (#40) from feature/persist-connect into main
Reviewed-on: #40
2025-03-06 22:38:26 +00:00
395d3390cf add remember me 2025-03-06 23:37:29 +01:00
daef20db66 Merge pull request 'feature/check-token' (#39) from feature/check-token into main
Reviewed-on: #39
2025-03-06 20:39:44 +00:00
8 changed files with 288 additions and 22 deletions

View File

@ -10,6 +10,8 @@ import 'alert.dart';
import '../variable/globals.dart' as globals; import '../variable/globals.dart' as globals;
import '../pages/LoginDemo.dart'; import '../pages/LoginDemo.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv
import 'package:encrypt_shared_preferences/provider.dart';
class MyDrawer extends StatelessWidget with ShowAlertDialog { class MyDrawer extends StatelessWidget with ShowAlertDialog {
Future<void> logout(BuildContext context) async { Future<void> logout(BuildContext context) async {
@ -28,7 +30,20 @@ class MyDrawer extends StatelessWidget with ShowAlertDialog {
print("Status code logout ${response.statusCode}"); print("Status code logout ${response.statusCode}");
if (response.statusCode == 200) { if (response.statusCode == 200) {
await prefs.remove("access_token"); // Correctly remove the token await prefs.remove("access_token");
await dotenv.load(fileName: ".env"); // Load .env file
final keyEncrypt = dotenv.env['KEY_ENCRYPT'] ?? '';
if (keyEncrypt.isNotEmpty) {
await EncryptedSharedPreferences.initialize(keyEncrypt);
var sharedPref = EncryptedSharedPreferences.getInstance();
String username = sharedPref.getString("username") ?? "";
String password = sharedPref.getString("password") ?? "";
if ((username.isEmpty) || (password.isEmpty)) {
sharedPref.remove("username");
sharedPref.remove("password");
}
}
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
context, context,
MaterialPageRoute(builder: (_) => LoginDemo()), MaterialPageRoute(builder: (_) => LoginDemo()),

View File

@ -4,11 +4,14 @@ import 'package:http/http.dart' as http;
import '../variable/globals.dart' as globals; import '../variable/globals.dart' as globals;
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:encrypt_shared_preferences/provider.dart';
import '../pages/LoginDemo.dart'; import '../pages/LoginDemo.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv
class AuthService { class AuthService {
// Login with username and password // Login with username and password
Future<bool> login(String username, String password) async { Future<bool> login(String username, String password,
{bool rememberMe = false}) async {
final url = Uri.parse("${globals.api}/token"); final url = Uri.parse("${globals.api}/token");
try { try {
@ -18,14 +21,22 @@ class AuthService {
'accept': 'application/json', 'accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
}, },
body: { body: {"username": username, "password": password},
"username": username,
"password": password,
},
); );
if (response.statusCode == 200 || response.statusCode == 201) { if (response.statusCode == 200 || response.statusCode == 201) {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
if (rememberMe) {
await dotenv.load(fileName: ".env"); // Load .env file
final keyEncrypt = dotenv.env['KEY_ENCRYPT'] ?? '';
if (keyEncrypt.isNotEmpty) {
await EncryptedSharedPreferences.initialize(keyEncrypt);
var sharedPref = EncryptedSharedPreferences.getInstance();
sharedPref.setString("username", username);
sharedPref.setString("password", password);
}
}
final cookies = response.headers["set-cookie"]?.split(";") ?? []; final cookies = response.headers["set-cookie"]?.split(";") ?? [];
for (final cookie in cookies) { for (final cookie in cookies) {
@ -79,8 +90,25 @@ class AuthService {
return true; return true;
} else { } else {
print("Token is invalid. Status code: ${response.statusCode}"); print("Token is invalid. Status code: ${response.statusCode}");
await dotenv.load(fileName: ".env"); // Load .env file
final keyEncrypt = dotenv.env['KEY_ENCRYPT'] ?? '';
if (keyEncrypt.isNotEmpty) {
await EncryptedSharedPreferences.initialize(keyEncrypt);
var sharedPref = EncryptedSharedPreferences.getInstance();
String username = sharedPref.getString("username") ?? "";
String password = sharedPref.getString("password") ?? "";
if ((username.isEmpty) || (password.isEmpty)) {
sharedPref.remove("username");
sharedPref.remove("password");
await prefs.remove("access_token"); // Clear invalid token await prefs.remove("access_token"); // Clear invalid token
return false; return false;
} else {
return login(username, password);
}
} else {
return false;
}
} }
} catch (e) { } catch (e) {
print("Error while checking token: $e"); print("Error while checking token: $e");

View File

@ -42,17 +42,53 @@ class ListItemOrganizers extends StatefulWidget {
// homepage state // homepage state
class _MyHomePageState extends State<ListItemOrganizers> { class _MyHomePageState extends State<ListItemOrganizers> {
int _fetchCount = 0;
bool _isLoading = false;
late ScrollController _scrollController;
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
void _incrementFetchCount() {
setState(() {
_fetchCount++;
});
}
void _scrollListener() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_incrementFetchCount();
_fetchData();
}
}
Future<void> _fetchData() async {
print("Counter : ${_fetchCount}");
if (_isLoading) return;
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 2));
getPosts(widget.organizer, count: _fetchCount);
setState(() {
_isLoading = false;
});
}
// variable to call and store future list of posts // variable to call and store future list of posts
// function to fetch data from api and return future list of posts // function to fetch data from api and return future list of posts
static Future<List<Events>> getPosts(organizer) async { static Future<List<Events>> getPosts(organizer, {count = 0}) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? ""; var accessToken = prefs.getString("access_token") ?? "";
final List<Events> body = []; final List<Events> body = [];
if (accessToken.isNotEmpty) { if (accessToken.isNotEmpty) {
var url = Uri.parse("${globals.api}/events?organizers=${organizer}"); DateTime currentDatetime = DateTime.now();
num limit = 20 * (count + 1);
var url = Uri.parse(
"${globals.api}/events?organizers=${organizer}&limit=${limit}&current_datetime=${currentDatetime.toString()}");
final response = await http.get(url, headers: { final response = await http.get(url, headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=${accessToken}" HttpHeaders.cookieHeader: "access_token=${accessToken}"
@ -67,6 +103,14 @@ class _MyHomePageState extends State<ListItemOrganizers> {
void initState() { void initState() {
super.initState(); super.initState();
_authService.checkTokenStatus(context); _authService.checkTokenStatus(context);
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
} }
// build function // build function
@ -109,7 +153,10 @@ class _MyHomePageState extends State<ListItemOrganizers> {
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
body: ListView.separated( body: ListView.separated(
itemCount: posts.length, controller: _scrollController,
itemCount: posts.isNotEmpty
? posts.length + (_isLoading ? 1 : 0) // Add 1 only if loading
: 0,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final post = posts[index]; final post = posts[index];
final startDate = DateTime.parse(post.startDate!); final startDate = DateTime.parse(post.startDate!);

View File

@ -44,15 +44,53 @@ class ListItemTags extends StatefulWidget {
class _MyHomePageState extends State<ListItemTags> { class _MyHomePageState extends State<ListItemTags> {
// variable to call and store future list of posts // variable to call and store future list of posts
int _fetchCount = 0;
bool _isLoading = false;
late ScrollController _scrollController;
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
void _incrementFetchCount() {
setState(() {
_fetchCount++;
});
}
void _scrollListener() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_incrementFetchCount();
_fetchData();
}
}
Future<void> _fetchData() async {
print("Counter : ${_fetchCount}");
if (_isLoading) return;
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 2));
getPosts(widget.tags, count: _fetchCount);
setState(() {
_isLoading = false;
});
}
// function to fetch data from api and return future list of posts // function to fetch data from api and return future list of posts
static Future<List<Events>> getPosts(tags) async { static Future<List<Events>> getPosts(tags, {count = 0}) async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? ""; var accessToken = prefs.getString("access_token") ?? "";
final List<Events> body = []; final List<Events> body = [];
if (accessToken.isNotEmpty) { if (accessToken.isNotEmpty) {
var url = Uri.parse("${globals.api}/events?tags=${tags}"); DateTime currentDatetime = DateTime.now();
num limit = 20 * (count + 1);
var url = Uri.parse(
"${globals.api}/events?tags=${tags}&limit=${limit}&current_datetime=${currentDatetime.toString()}");
final response = await http.get(url, headers: { final response = await http.get(url, headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=${accessToken}" HttpHeaders.cookieHeader: "access_token=${accessToken}"
@ -67,6 +105,15 @@ class _MyHomePageState extends State<ListItemTags> {
void initState() { void initState() {
super.initState(); super.initState();
_authService.checkTokenStatus(context); _authService.checkTokenStatus(context);
_authService.checkTokenStatus(context);
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
} }
// build function // build function
@ -108,7 +155,10 @@ class _MyHomePageState extends State<ListItemTags> {
foregroundColor: Colors.white, foregroundColor: Colors.white,
), ),
body: ListView.separated( body: ListView.separated(
itemCount: posts.length, controller: _scrollController,
itemCount: posts.isNotEmpty
? posts.length + (_isLoading ? 1 : 0) // Add 1 only if loading
: 0,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final post = posts[index]; final post = posts[index];
final startDate = DateTime.parse(post.startDate!); final startDate = DateTime.parse(post.startDate!);

View File

@ -48,6 +48,9 @@ class ListItemMenu extends StatefulWidget {
class _MyHomePageState extends State<ListItemMenu> { class _MyHomePageState extends State<ListItemMenu> {
BannerAd? _bannerAd; BannerAd? _bannerAd;
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
late ScrollController _scrollController;
int _fetchCount = 0;
bool _isLoading = false;
Future<List<Events>> postsFuture = getPosts(); Future<List<Events>> postsFuture = getPosts();
List<Events> filteredPosts = []; List<Events> filteredPosts = [];
@ -144,6 +147,37 @@ class _MyHomePageState extends State<ListItemMenu> {
return "${year}-${month}-${day}"; return "${year}-${month}-${day}";
} }
void _incrementFetchCount() {
setState(() {
_fetchCount++;
});
_fetchData();
}
void _scrollListener() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_incrementFetchCount();
}
// Scroll to top
}
Future<void> _fetchData() async {
print("Counter : ${_fetchCount}");
if (_isLoading) return;
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 2));
fetchPostsByLocation();
setState(() {
_isLoading = false;
});
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -153,10 +187,19 @@ class _MyHomePageState extends State<ListItemMenu> {
_bannerAd = ad; _bannerAd = ad;
}); });
}); });
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
// Initialize data fetch when the page loads // Initialize data fetch when the page loads
_getCurrentLocation(); _getCurrentLocation();
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
// Get the device's current location // Get the device's current location
Future<void> _getCurrentLocation() async { Future<void> _getCurrentLocation() async {
PermissionStatus status = await Permission.location.status; PermissionStatus status = await Permission.location.status;
@ -346,8 +389,10 @@ class _MyHomePageState extends State<ListItemMenu> {
} else { } else {
queryParameters = dateParameter; queryParameters = dateParameter;
} }
int limit = 20 * (_fetchCount + 1);
return Uri.parse("${globals.api}/$endpoint?$queryParameters"); return Uri.parse(
"${globals.api}/$endpoint?$queryParameters&limit=${limit}");
} }
Future<void> searchSuggestionsByItem(String input) async { Future<void> searchSuggestionsByItem(String input) async {
@ -358,7 +403,7 @@ class _MyHomePageState extends State<ListItemMenu> {
var url = await getUrlForEvents(); var url = await getUrlForEvents();
final response = await http.get(url, headers: { final response = await http.get(url, headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=$accessToken" HttpHeaders.cookieHeader: "acce0ss_token=$accessToken"
}); });
if (response.statusCode == 200) { if (response.statusCode == 200) {
@ -422,16 +467,17 @@ class _MyHomePageState extends State<ListItemMenu> {
if (response.statusCode == 200) { if (response.statusCode == 200) {
final List<dynamic> body = json.decode(utf8.decode(response.bodyBytes)); final List<dynamic> body = json.decode(utf8.decode(response.bodyBytes));
print("results fetch : ${body}"); print("results fetch : ${body}");
print("fetch count : ${_fetchCount}");
// Update state after getting the response // Update state after getting the response
if (body.isEmpty) {
_fetchCount--;
}
setState(() { setState(() {
if (body.isNotEmpty) { if (body.isNotEmpty) {
// If we have results, map them to Events // If we have results, map them to Events
filteredPosts = body filteredPosts = body
.map((e) => Events.fromJson(e as Map<String, dynamic>)) .map((e) => Events.fromJson(e as Map<String, dynamic>))
.toList(); .toList();
} else {
// If no results, clear filteredPosts
filteredPosts.clear();
} }
}); });
} else { } else {
@ -570,6 +616,7 @@ class _MyHomePageState extends State<ListItemMenu> {
controller: inputItem, controller: inputItem,
labelText: 'Search by item', labelText: 'Search by item',
onChanged: (value) { onChanged: (value) {
_fetchCount = 0;
if (value.isNotEmpty) { if (value.isNotEmpty) {
setState(() { setState(() {
itemName = value; itemName = value;
@ -587,6 +634,7 @@ class _MyHomePageState extends State<ListItemMenu> {
} }
}, },
onClear: () { onClear: () {
_fetchCount = 0;
setState(() { setState(() {
inputItem.clear(); inputItem.clear();
itemName = ''; itemName = '';
@ -598,6 +646,7 @@ class _MyHomePageState extends State<ListItemMenu> {
}, },
suggestions: suggestionsItem, suggestions: suggestionsItem,
onSuggestionTap: (suggestion) async { onSuggestionTap: (suggestion) async {
_fetchCount = 0;
setState(() { setState(() {
itemName = suggestion['name']; itemName = suggestion['name'];
inputItem.text = itemName; inputItem.text = itemName;
@ -613,6 +662,7 @@ class _MyHomePageState extends State<ListItemMenu> {
controller: inputTags, controller: inputTags,
labelText: 'Search by tags', labelText: 'Search by tags',
onChanged: (value) { onChanged: (value) {
_fetchCount = 0;
if (value.isNotEmpty) { if (value.isNotEmpty) {
setState(() { setState(() {
itemTags = value; itemTags = value;
@ -630,6 +680,7 @@ class _MyHomePageState extends State<ListItemMenu> {
} }
}, },
onClear: () { onClear: () {
_fetchCount = 0;
setState(() { setState(() {
inputTags.clear(); inputTags.clear();
}); });
@ -637,6 +688,8 @@ class _MyHomePageState extends State<ListItemMenu> {
}, },
suggestions: suggestionsTags, suggestions: suggestionsTags,
onSuggestionTap: (suggestion) async { onSuggestionTap: (suggestion) async {
_fetchCount = 0;
setState(() { setState(() {
itemTags = suggestion['name']; itemTags = suggestion['name'];
inputTags.text = itemTags; inputTags.text = itemTags;
@ -660,6 +713,8 @@ class _MyHomePageState extends State<ListItemMenu> {
controller: inputGeo, controller: inputGeo,
labelText: 'Search by geographical zone', labelText: 'Search by geographical zone',
onChanged: (value) async { onChanged: (value) async {
_fetchCount = 0;
if (value.isNotEmpty) { if (value.isNotEmpty) {
setState(() { setState(() {
geographicalZone = value; geographicalZone = value;
@ -681,6 +736,7 @@ class _MyHomePageState extends State<ListItemMenu> {
} }
}, },
onClear: () async { onClear: () async {
_fetchCount = 0;
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
prefs.remove("city_lat"); prefs.remove("city_lat");
prefs.remove("city_long"); prefs.remove("city_long");
@ -696,6 +752,7 @@ class _MyHomePageState extends State<ListItemMenu> {
}, },
suggestions: suggestionsGeo, suggestions: suggestionsGeo,
onSuggestionTap: (suggestion) async { onSuggestionTap: (suggestion) async {
_fetchCount = 0;
final latitude = suggestion['geometry']['coordinates'][1]; final latitude = suggestion['geometry']['coordinates'][1];
final longitude = suggestion['geometry']['coordinates'][0]; final longitude = suggestion['geometry']['coordinates'][0];
setState(() { setState(() {
@ -770,12 +827,22 @@ class _MyHomePageState extends State<ListItemMenu> {
} }
return ListView.separated( return ListView.separated(
itemCount: displayedPosts.length, controller: _scrollController,
itemCount: displayedPosts.isNotEmpty
? displayedPosts.length +
(_isLoading ? 1 : 0) // Add 1 only if loading
: 0,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (index >= displayedPosts.length) {
return _isLoading
? Center(child: CircularProgressIndicator())
: SizedBox.shrink();
}
final post = displayedPosts[index]; final post = displayedPosts[index];
final startDate = DateTime.parse(post.startDate!); final startDate = DateTime.parse(post.startDate!);
final date = DateFormat.yMd().format(startDate); final date = DateFormat.yMd().format(startDate);
final time = DateFormat.Hm().format(startDate); final time = DateFormat.Hm().format(startDate);
return ListTile( return ListTile(
title: Text('${post.name!}'), title: Text('${post.name!}'),
subtitle: Text('${post.place!}\n${date} ${time}'), subtitle: Text('${post.place!}\n${date} ${time}'),

View File

@ -18,6 +18,7 @@ class _LoginDemoState extends State<LoginDemo> with ShowAlertDialog {
TextEditingController inputPseudo = TextEditingController(); TextEditingController inputPseudo = TextEditingController();
TextEditingController inputPassword = TextEditingController(); TextEditingController inputPassword = TextEditingController();
final AuthService _authService = AuthService(); final AuthService _authService = AuthService();
bool _rememberMe = false;
Future<void> _login(BuildContext context) async { Future<void> _login(BuildContext context) async {
final pseudo = inputPseudo.text; final pseudo = inputPseudo.text;
@ -28,7 +29,8 @@ class _LoginDemoState extends State<LoginDemo> with ShowAlertDialog {
return; return;
} }
bool success = await _authService.login(pseudo, password); bool success =
await _authService.login(pseudo, password, rememberMe: _rememberMe);
if (success) { if (success) {
Navigator.push( Navigator.push(
@ -53,7 +55,6 @@ class _LoginDemoState extends State<LoginDemo> with ShowAlertDialog {
Future<void> _checkLoginStatus() async { Future<void> _checkLoginStatus() async {
bool loggedIn = await _authService.isLoggedIn(); bool loggedIn = await _authService.isLoggedIn();
print("logged status : ${loggedIn}");
if (loggedIn) { if (loggedIn) {
Navigator.push( Navigator.push(
context, MaterialPageRoute(builder: (_) => ListItemMenu())); context, MaterialPageRoute(builder: (_) => ListItemMenu()));
@ -109,6 +110,15 @@ class _LoginDemoState extends State<LoginDemo> with ShowAlertDialog {
), ),
), ),
), ),
CheckboxListTile(
title: Text("Se souvenir de moi"),
value: _rememberMe,
onChanged: (newValue) {
setState(() {
_rememberMe = newValue ?? false;
});
},
),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.push(context, Navigator.push(context,

View File

@ -9,6 +9,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.6.1" version: "3.6.1"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
asn1lib:
dependency: transitive
description:
name: asn1lib
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
url: "https://pub.dev"
source: hosted
version: "1.5.8"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -89,6 +105,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@ -137,6 +161,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.0" version: "2.0.0"
encrypt:
dependency: transitive
description:
name: encrypt
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
url: "https://pub.dev"
source: hosted
version: "5.0.3"
encrypt_shared_preferences:
dependency: "direct main"
description:
name: encrypt_shared_preferences
sha256: "35cd218e5e9d12fe4a63a545f46f2144d861909e4c4f2c4606fc75ffb53d9a46"
url: "https://pub.dev"
source: hosted
version: "0.8.8"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -648,6 +688,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -51,6 +51,7 @@ dependencies:
url_launcher: ^6.3.1 url_launcher: ^6.3.1
mapbox_gl: ^0.16.0 mapbox_gl: ^0.16.0
google_mobile_ads: ^5.3.1 google_mobile_ads: ^5.3.1
encrypt_shared_preferences: ^0.8.8
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: