177 Commits

Author SHA1 Message Date
b6134c5506 upgrade mapbox 2025-08-29 23:24:27 +02:00
6e0b54a925 remove debug 2025-08-27 23:51:08 +02:00
5d9802d56e ask permission 2025-08-26 20:57:50 +02:00
8a7515aaf6 add list in listitem 2025-08-26 20:34:01 +02:00
f9934a0d5d debug ok 2025-08-26 20:29:55 +02:00
09c82da57f add notification test 2025-08-25 23:41:54 +02:00
75b42dd091 Merge pull request 'feature/event-interested' (#48) from feature/event-interested into main
Reviewed-on: #48
2025-08-20 21:40:43 +00:00
dfe21960be add toggle interested in list organizer and tag 2025-08-20 23:39:20 +02:00
4a936cc267 change for heart and and count interested people 2025-08-20 23:22:25 +02:00
bd5bfa94ca remove print debug 2025-08-20 22:49:51 +02:00
f30aa8f630 togleInterested debug 2025-08-19 21:09:44 +02:00
924794e8a0 ad toogle interested method 2025-08-19 00:11:40 +02:00
7528e55dca add button star 2025-08-17 17:42:02 +02:00
02ba067615 Merge pull request 'feature/link' (#47) from feature/link into main
Reviewed-on: #47
2025-08-15 21:19:14 +00:00
9eafb30374 add link and ticket backend side 2025-08-15 23:05:53 +02:00
24cb90959d fix edit event 2025-08-15 17:14:53 +02:00
1620d84137 add link and ticket inàut 2025-08-15 00:29:48 +02:00
6e8ba642a2 add link and ticket 2025-08-12 23:33:15 +02:00
0113fcc382 add Link 2025-08-12 22:30:12 +02:00
e4571cabe7 Merge pull request 'feature/internationale' (#46) from feature/internationale into main
Reviewed-on: #46
2025-08-06 20:38:36 +00:00
76db2f8254 add cache for language 2025-08-06 22:34:01 +02:00
0a62011b3a translate date on list by tag and by organizer 2025-08-06 22:17:53 +02:00
cbc75bbc7b translate date 2025-07-30 23:54:15 +02:00
7a418d82a7 Add german translation 2025-07-29 21:21:21 +02:00
e310197aa7 translate updateevent 100% 2025-07-28 23:21:37 +02:00
5d02f2b1fb translate updateevent 50% 2025-07-28 22:56:39 +02:00
119dfcbdfe translate mapbox page 100% 2025-07-27 20:51:55 +02:00
9e50b6d6f6 translate mapbox page 50% 2025-07-27 20:43:35 +02:00
dcc2ec25de translate mapbox page 10% 2025-07-27 20:04:55 +02:00
2c2eedb7ce translate loginpage 100% 2025-07-27 15:16:38 +02:00
089aa58f4a ListItemOrganizer and Tags 2025-07-27 12:13:52 +02:00
b4b0199fc2 ItemMenu 100% 2025-07-26 19:23:59 +02:00
b48483c9f0 translate forgotpassword 100% again 2025-07-26 19:00:28 +02:00
22d0581da3 translate forgotpassword 100% 2025-07-25 23:44:58 +02:00
337fab4a08 translate editsettings 100% 2025-07-25 23:20:51 +02:00
a99986813e translate editprofile 100% 2025-07-25 23:04:28 +02:00
199000035e translate editprofile 75% 2025-07-24 21:04:27 +02:00
f143036ca8 translate editevent 100% 2025-07-24 20:42:25 +02:00
271c3ba118 translate editevent 75% 2025-07-24 20:25:52 +02:00
f8b5c24efd translate editevent 55% 2025-07-23 23:28:16 +02:00
1df36987d9 translate editevent 50% 2025-07-23 23:18:52 +02:00
b4dc29aff6 translate displaypicturescreen 100% 2025-07-21 22:50:56 +02:00
79563e829c Add displaypicture 2025-07-18 19:38:10 +02:00
413807f039 Add camera dart 2025-07-18 19:06:38 +02:00
26372368b2 full translate for AddProfile pages 100% 2025-07-17 20:53:07 +02:00
f81a8c264c full translate for AddProfile pages 28% 2025-07-16 23:32:35 +02:00
c5985f2954 full translate for AddProfile pages 26% 2025-07-16 23:05:49 +02:00
1f8d18343c full translate for AddProfile pages 25% 2025-07-16 22:58:01 +02:00
c3b8b0df14 full translate for AddProfile pages 20% 2025-07-16 22:46:41 +02:00
ec8ce404ab full translate for AddProfile pages 16% 2025-07-16 22:08:08 +02:00
1208982b15 full translate for AddProfile pages 15% 2025-07-14 23:20:22 +02:00
479ab76fb7 full translate for AddProfile pages 10% 2025-07-14 22:53:34 +02:00
1646a0b6e3 full translate for ItemListMenu pages 50% 2025-07-09 23:33:04 +02:00
e21b03d13c full translate for ItemListMenu pages 2025-07-09 23:05:50 +02:00
45cdb253e4 internationlization wip 2025-07-06 20:59:57 +02:00
75b443758a changement de langue ok sur drawer 2025-07-02 22:35:53 +02:00
e2195e6500 change language ok 2025-07-02 22:03:51 +02:00
4f41aff572 change button language 2025-06-30 23:25:56 +02:00
1c21f59420 Merge pull request 'feature/localeDate' (#45) from feature/localeDate into main
Reviewed-on: #45
2025-06-25 19:16:50 +00:00
7441af6f13 remove if body isnotempty 2025-06-25 21:14:45 +02:00
4f4b0b609c add date locale in list by tags 2025-06-23 23:25:34 +02:00
2e6814c33d add date locale in list by organizers 2025-06-23 23:17:05 +02:00
909b321158 test date format french ok for first page 2025-06-23 23:09:37 +02:00
6641c7938c Merge pull request 'add fetchcount' (#44) from hotfix/fix-counter into main
Reviewed-on: #44
2025-06-23 20:02:46 +00:00
8904032265 add fetchcount 2025-06-18 23:36:24 +02:00
9d35a59ba6 Merge pull request 'hotfix/duplicate-event' (#43) from hotfix/duplicate-event into main
Reviewed-on: #43
2025-06-16 21:05:14 +00:00
303447ff1e check duplicate event 2025-06-16 22:51:01 +02:00
32532246eb add duplicate event check 2025-06-11 23:05:00 +02:00
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
9df499d198 check token 100% 2025-03-06 21:38:47 +01:00
e7afe8fddb add check token 25% 2025-03-06 21:27:06 +01:00
5c2a58e484 remove package fix 2025-03-06 20:38:15 +01:00
e8bb2f6180 Merge pull request 'feature/oauth' (#38) from feature/oauth into main
Reviewed-on: #38
2025-03-06 19:27:01 +00:00
e4d8648fcc remove pakcage 2025-03-06 20:26:02 +01:00
7f3240242d remove google and facebook auth 2025-03-06 20:21:59 +01:00
c936a02836 re-organisation login info 2025-03-02 16:49:19 +01:00
3352bab860 Merge pull request 'feature/publicite' (#37) from feature/publicite into main
Reviewed-on: #37
2025-02-23 21:20:24 +00:00
c31e275dae add pub 2025-02-23 22:18:01 +01:00
38e9908533 add pub 2025-02-19 23:32:31 +01:00
47c014cdda add pub in update event 2025-02-19 23:24:21 +01:00
729e0ce1ca use many pub in different page 2025-02-19 23:03:12 +01:00
5c2fa27aa7 ad helper 2025-02-18 22:56:50 +01:00
9846c614a1 Merge pull request 'feature/forgotPassword' (#36) from feature/forgotPassword into main
Reviewed-on: #36
2025-02-15 11:26:45 +00:00
d46f268c1b mise en forme 2025-02-15 12:25:03 +01:00
b0477c889c add page password forgot 2025-02-14 23:39:44 +01:00
6fdb5cb2fe Merge pull request 'feature/add-user' (#35) from feature/add-user into main
Reviewed-on: #35
2025-02-14 20:21:05 +00:00
c1352b8eeb add profil ok 2025-02-14 21:19:44 +01:00
90e61cebbf add profile wip 2025-01-22 23:33:09 +01:00
6a3ec9a969 remove useless function 2025-01-22 23:12:47 +01:00
11c8cd74d2 add profile 2025-01-22 23:03:06 +01:00
39c60632f1 Merge pull request 'add getDouble' (#34) from feature/parameter-kilometer into main
Reviewed-on: #34
2025-01-22 21:02:45 +00:00
c5a280d91b add getDouble 2025-01-22 21:49:16 +01:00
280909deb9 Merge pull request 'feature/refactor' (#33) from feature/refactor into main
Reviewed-on: #33
2025-01-21 06:57:24 +00:00
b7fe1da681 refactor code 2025-01-20 21:42:14 +01:00
56ae77137c refactor listitemmenu 2025-01-19 22:41:13 +01:00
06e26240ab refactor itemmenu 2025-01-19 22:26:45 +01:00
908d94c269 fix import 2025-01-19 21:49:09 +01:00
642f35e73c Merge pull request 'feature/hamburger-bar' (#32) from feature/hamburger-bar into main
Reviewed-on: #32
2025-01-10 22:20:41 +01:00
524427a29f add drawer to every page 2025-01-10 21:06:41 +01:00
54c95a230b set kilometer in double 2025-01-09 23:27:22 +01:00
18d5c83181 add kilometer 2025-01-09 22:13:20 +01:00
b156cd084b remove useless input editSettings 2025-01-09 21:48:24 +01:00
3bb85a198a add edit settings 2025-01-09 21:44:43 +01:00
0f40c3e225 add home link 2025-01-09 21:35:31 +01:00
30b9be35ef edit profile ok 2025-01-06 23:40:17 +01:00
db3cb20255 add check condition isnotempty 2025-01-05 21:02:39 +01:00
83c65be610 informations filled 2025-01-05 17:53:14 +01:00
da3659e84a add edit profile 2025-01-05 17:38:31 +01:00
39b3efca33 redirect to page update profile 2025-01-04 14:25:34 +01:00
82c31acf99 editprofile complete 2025-01-03 19:15:35 +01:00
f84f513e67 update function update 2025-01-03 18:03:24 +01:00
3615c1f476 fix edit profile wip 2025-01-03 17:22:14 +01:00
5419da7a98 edit profile wip 2025-01-03 15:06:08 +01:00
ef8b8c96c0 edit profile wip 2025-01-02 23:22:25 +01:00
ad19ea54d1 add account update 2024-12-30 23:48:03 +01:00
ef87a8bfe2 add logout button + logout function 2024-12-30 23:33:51 +01:00
8adcd80306 message about 2024-12-30 22:51:47 +01:00
43d77f778b change alert message 2024-12-30 22:34:17 +01:00
938b677b6e test hamburger bar 2024-12-30 22:14:46 +01:00
6e5994e4dd Merge pull request 'change suggestions list for input geo' (#31) from hotfix/refactor-input-tags into main
Reviewed-on: #31
2024-12-30 21:55:30 +01:00
b2e7080265 change suggestions list for input geo 2024-12-28 15:03:55 +01:00
d8b3bf7ca2 Merge pull request 'feature/suggestion-tag' (#30) from feature/suggestion-tag into main
Reviewed-on: #30
2024-12-28 14:52:39 +01:00
0df538ef46 recherche par tags terminé 2024-12-28 14:47:39 +01:00
48c785c586 change mapbox place to google place api 2024-12-27 23:31:49 +01:00
c5de20d64b change place api mapbox to google place api 2024-12-25 22:18:02 +01:00
43124d9cb9 add list adress from v6 mapbox and searchbox 2024-12-22 22:37:17 +01:00
03f3e1c55b add suggestion itemé 2024-12-22 22:11:20 +01:00
ba8db9fb4c add suggestion for inputtags 2024-12-22 16:00:10 +01:00
53a60a581a fix tags input 2024-12-21 20:19:33 +01:00
e4836ac4eb manage show input 2024-12-21 19:06:37 +01:00
f267e3ede9 add inputtext simple 2024-12-21 14:15:49 +01:00
eadf07177b simple textfield 2024-12-20 22:44:05 +01:00
18fdc7d6c4 add tags 2024-12-16 22:26:58 +01:00
8d8c30f506 Merge pull request 'feature/edit-image' (#29) from feature/edit-image into main
Reviewed-on: #29
2024-12-15 20:23:19 +01:00
ea3d5ef8de add post image in edit eventé 2024-12-15 20:10:19 +01:00
578107c83c add cameraedit 2024-12-14 21:30:32 +01:00
49a108905c add button edit image 2024-12-14 19:10:23 +01:00
32281233e0 Merge pull request 'feature/edit-event' (#28) from feature/edit-event into main
Reviewed-on: #28
2024-12-14 18:44:21 +01:00
63f6bd2af2 edit event without edit image 2024-12-14 00:34:08 +01:00
c58127342b display edit event 2024-12-13 23:49:56 +01:00
0dc098554c tags and organizers work 2024-12-11 22:20:32 +01:00
b8e6adf2e8 try tags organizers 2024-12-09 23:34:44 +01:00
ac566053b1 fix datepicker 2024-12-09 23:00:54 +01:00
c12a957099 fix datetpicker wip 2024-12-08 21:23:51 +01:00
4b71867ee0 add editevent wip 2024-12-07 18:44:53 +01:00
8c4c436241 add floating button 2024-12-07 17:10:02 +01:00
1cc14277e8 Merge pull request 'feature/fix-search' (#27) from feature/fix-search into main
Reviewed-on: #27
2024-12-04 23:29:39 +01:00
cf100651eb check existing event before add 2024-12-04 23:28:09 +01:00
76f1de27ec add future event only 2024-12-04 23:01:06 +01:00
eef7e44999 change endpoint 2024-12-01 21:23:22 +01:00
3315cf1961 Merge pull request 'hide clear button' (#26) from feature/hide-clear-button into main
Reviewed-on: #26
2024-11-30 19:29:24 +01:00
a4b747a273 hide clear button 2024-11-30 19:28:43 +01:00
6b53dc170d Merge pull request 'feature/suggestion-item' (#25) from feature/suggestion-item into main
Reviewed-on: #25
2024-11-30 19:00:07 +01:00
0977389695 suggestion search by item 2024-11-30 18:45:41 +01:00
559b35c7c2 add suggestion for search item 2024-11-30 00:39:57 +01:00
edd2cd1581 add suggesstion item wip 2024-11-28 23:44:11 +01:00
d012ac409b Merge pull request 'fix input geo search' (#24) from hotfix/inputgeo into main
Reviewed-on: #24
2024-11-28 22:32:44 +01:00
4ec4b79f2c fix input geo search 2024-11-28 22:26:12 +01:00
7f4f59b6c9 Merge pull request 'fix suggesstion list' (#23) from hotfix/fix-inputgeo into main
Reviewed-on: #23
2024-11-26 21:46:51 +01:00
0e3d75e8a8 fix suggesstion list 2024-11-26 21:43:14 +01:00
d93c10f319 Merge pull request 'change place input + fix endpoint' (#22) from hotfix/search into main
Reviewed-on: #22
2024-11-24 22:45:24 +01:00
a5533aa0d3 change place input + fix endpoint 2024-11-24 22:44:35 +01:00
196 changed files with 20990 additions and 837 deletions

View File

@@ -6,6 +6,9 @@
android:label="covas_mobile"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-4855855675386260~3438207239"/>
<activity
android:name=".MainActivity"
android:exported="true"

View File

@@ -1,3 +1,4 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true
flutter.compileSdkVersion=35

View File

@@ -47,5 +47,8 @@
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your location is needed for showing nearby events</string>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-4855855675386260~3438207239</string>
</dict>
</plist>

4
covas_mobile/l10n.yaml Normal file
View File

@@ -0,0 +1,4 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations

View File

@@ -0,0 +1,209 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'dart:io';
import '../pages/EditProfile.dart';
import '../pages/EditSettings.dart';
import '../pages/ListItemMenu.dart';
import 'alert.dart';
import '../variable/globals.dart' as globals;
import '../pages/LoginDemo.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv
import 'package:encrypt_shared_preferences/provider.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class MyDrawer extends StatelessWidget with ShowAlertDialog {
Future<void> logout(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var url = Uri.parse("${globals.api}/token");
try {
var response = await http.delete(url, headers: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=${accessToken}"
});
print("Status code logout ${response.statusCode}");
if (response.statusCode == 200) {
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(
context,
MaterialPageRoute(builder: (_) => LoginDemo()),
(route) => false, // Remove all previous routes
);
} else {
String errorMessage;
switch (response.statusCode) {
case 400:
errorMessage = "Bad Request: Please check your input.";
break;
case 401:
errorMessage = "Unauthorized: Invalid credentials.";
break;
case 403:
errorMessage = "Forbidden: You don't have permission.";
break;
case 404:
errorMessage = "Not Found: The resource was not found.";
break;
case 500:
errorMessage =
"Server Error: Something went wrong on the server.";
break;
default:
errorMessage = "Unexpected Error: ${response.statusCode}";
break;
}
print(errorMessage);
showAlertDialog(context, "Error", errorMessage);
}
} catch (e) {
print("Error: $e");
showAlertDialog(
context, "Error", "An error occurred. Please try again.");
}
} else {
showAlertDialog(context, "Error", "Invalid token.");
}
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
final localeProvider = Provider.of<LocaleProvider>(context);
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
// Drawer Header
DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text(
'Menu',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
// Drawer Items
ListTile(
leading: Icon(Icons.home),
title: Text(loc?.home ?? "Home"),
onTap: () {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (_) => ListItemMenu()));
/// Close the drawer
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text(loc?.settings ?? 'Settings'),
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => EditSettings())); // Close the drawer
},
),
ListTile(
leading: Icon(Icons.account_circle),
title: Text(loc?.update_profile ?? 'Update profile'),
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => EditProfile())); // Close the drawer
},
),
ListTile(
leading: Icon(Icons.language),
title: Text(loc?.language ?? 'Language'),
onTap: () {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text(loc?.select_language ?? 'Select Language'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.flag),
title: Text(loc?.french ?? 'Français'),
onTap: () {
Provider.of<LocaleProvider>(context, listen: false)
.setLocale(const Locale('fr'));
Navigator.of(context).pop();
},
),
ListTile(
leading: Icon(Icons.flag_outlined),
title: Text(loc?.english ?? 'English'),
onTap: () {
Provider.of<LocaleProvider>(context, listen: false)
.setLocale(const Locale('en'));
Navigator.of(context).pop();
},
),
ListTile(
leading: Icon(Icons.flag_outlined),
title: Text(loc?.german ?? 'German'),
onTap: () {
Provider.of<LocaleProvider>(context, listen: false)
.setLocale(const Locale('de'));
Navigator.of(context).pop();
},
),
],
),
),
);
},
),
ListTile(
leading: Icon(Icons.info),
title: Text(loc?.about ?? 'About'),
onTap: () {
showAlertDialog(context, loc?.about ?? 'About',
"Version 0.0.1"); // Close the drawer
},
),
ListTile(
leading: Icon(Icons.logout),
title: Text(loc?.log_out ?? 'Log out'),
onTap: () async {
logout(context);
// Close the drawer
},
),
],
),
);
}
}

View File

@@ -0,0 +1,27 @@
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class AdHelper {
static Future<BannerAd> createBannerAd(Function setStateCallback) async {
await dotenv.load(fileName: ".env");
final adUnitId = dotenv.env['AD_UNIT_ID'] ?? '';
BannerAd bannerAd = BannerAd(
adUnitId: adUnitId,
size: AdSize.banner,
request: AdRequest(),
listener: BannerAdListener(
onAdLoaded: (ad) {
setStateCallback(() {});
},
onAdFailedToLoad: (ad, error) {
print('Banner Ad failed to load: $error');
ad.dispose();
},
),
);
bannerAd.load();
return bannerAd;
}
}

View File

@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
mixin ShowErrorDialog<T extends StatefulWidget> on State<T> {
void showErrorDialog(BuildContext context, String text) {
mixin ShowAlertDialog {
void showAlertDialog(BuildContext context, String title, String text) {
// Create AlertDialog
AlertDialog dialog = AlertDialog(
title: Text("Error"),
title: Text(title),
content: Text(text),
actions: [
ElevatedButton(

View File

@@ -0,0 +1,141 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import '../variable/globals.dart' as globals;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:encrypt_shared_preferences/provider.dart';
import '../pages/LoginDemo.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv
class AuthService {
// Login with username and password
Future<bool> login(String username, String password,
{bool rememberMe = false}) async {
final url = Uri.parse("${globals.api}/token");
try {
final response = await http.post(
url,
headers: {
'accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {"username": username, "password": password},
);
if (response.statusCode == 200 || response.statusCode == 201) {
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(";") ?? [];
for (final cookie in cookies) {
final cookieParts = cookie.split(",");
for (final part in cookieParts) {
final keyValue = part.split("=");
if (keyValue.length == 2 && keyValue[0] == "access_token") {
prefs.setString("access_token", keyValue[1]);
}
}
}
return true;
} else {
return false;
}
} catch (e) {
print("Login error: $e");
return false;
}
}
// Logout
Future<void> logout() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove("access_token");
}
Future<bool> isLoggedIn() async {
final prefs = await SharedPreferences.getInstance();
final accessToken = prefs.getString("access_token");
if (accessToken == null || accessToken.isEmpty) {
print("No access token found.");
return false;
}
print("Checking token validity...");
var url = Uri.parse("${globals.api}/token");
try {
final response = await http.get(
url,
headers: {
HttpHeaders.cookieHeader: "access_token=$accessToken",
"Content-Type": "application/json",
},
);
if (response.statusCode == 200) {
print("Token is valid.");
return true;
} else {
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
return false;
} else {
return login(username, password);
}
} else {
return false;
}
}
} catch (e) {
print("Error while checking token: $e");
return false;
}
}
Future<void> checkTokenStatus(context) async {
bool loggedIn = await isLoggedIn();
SharedPreferences prefs = await SharedPreferences.getInstance();
if (!loggedIn) {
await prefs.remove("access_token"); // Correctly remove the token
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => LoginDemo()),
(route) => false, // Remove all previous routes
);
}
}
// Get stored access token
Future<String?> getAccessToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString("access_token");
}
// Login with Google
}

View File

@@ -5,19 +5,50 @@ class Events {
String? startDate;
String? endDate;
String? description;
String? link;
String? ticket;
double? latitude;
double? longitude;
Events({this.place, this.id, this.name, this.startDate});
List<String>? tags;
List<String>? organizers;
String? imgUrl;
int? interestedCount;
bool? interested;
Events(
{this.place,
this.id,
this.name,
this.startDate,
this.description,
this.endDate,
this.tags,
this.latitude,
this.longitude,
this.organizers,
this.link,
this.ticket,
this.imgUrl,
this.interestedCount,
this.interested});
Events.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
place = json['place'];
startDate = json["start_date"];
endDate = json['end_date'];
description = json['description'];
latitude = json['latitude'];
longitude = json['longitude'];
id = json['id'] as String?;
name = json['name'] as String?;
place = json['place'] as String?;
startDate = json['start_date'] as String?;
endDate = json['end_date'] as String?;
description = json['description'] as String?;
latitude = (json['latitude'] as num?)?.toDouble(); // Safely cast to double
longitude =
(json['longitude'] as num?)?.toDouble(); // Safely cast to double
tags = (json['tags'] as List<dynamic>?)
?.cast<String>(); // Convert List<dynamic> to List<String>
organizers = (json['organizers'] as List<dynamic>?)
?.cast<String>(); // Convert List<dynamic> to List<String>
imgUrl = json['imgUrl'] as String?;
link = json['link'] as String?;
ticket = json['ticket'] as String?;
interested = json['interested'] as bool?;
interestedCount = json['interested_count'] as int?;
}
}

View File

@@ -0,0 +1,91 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
class NotificationService {
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
FlutterLocalNotificationsPlugin();
/// Initialisation (à appeler dans main())
static Future<void> initialize() async {
tz.initializeTimeZones();
const AndroidInitializationSettings androidInitSettings =
AndroidInitializationSettings('@mipmap/ic_launcher');
const InitializationSettings initSettings = InitializationSettings(
android: androidInitSettings,
iOS: DarwinInitializationSettings(),
);
await _notificationsPlugin.initialize(initSettings);
// Demande les permissions au lancement
await requestPermissions();
}
/// Demander les permissions (Android 13+ et iOS)
static Future<void> requestPermissions() async {
// Android 13+
final androidImplementation =
_notificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
await androidImplementation?.requestNotificationsPermission();
// iOS
final iosImplementation =
_notificationsPlugin.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>();
await iosImplementation?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
/// Planifie une notification 1h avant lévènement
static Future<void> scheduleEventNotification({
required String eventId,
required String title,
required String body,
required DateTime eventDate,
}) async {
final scheduledDate = eventDate.subtract(const Duration(hours: 1));
if (scheduledDate.isBefore(DateTime.now())) {
// Trop tard pour notifier
return;
}
await _notificationsPlugin.zonedSchedule(
eventId.hashCode, // identifiant unique pour lévènement
title,
body,
tz.TZDateTime.from(scheduledDate, tz.local),
const NotificationDetails(
android: AndroidNotificationDetails(
'events_channel',
'Events',
channelDescription: 'Favorite event notifications',
importance: Importance.high,
priority: Priority.high,
),
iOS: DarwinNotificationDetails(),
),
androidScheduleMode: AndroidScheduleMode
.inexactAllowWhileIdle, // évite l'erreur Exact Alarm
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.dateAndTime,
);
}
/// Annule une notification planifiée
static Future<void> cancel(String eventId) async {
await _notificationsPlugin.cancel(eventId.hashCode);
}
}

View File

@@ -0,0 +1,143 @@
{
"@@locale": "de",
"menu_list": "Veranstaltungsmenü",
"language": "Sprache",
"home": "Startseite",
"settings": "Einstellungen",
"update_profile": "Profil aktualisieren",
"about": "Über",
"log_out": "Abmelden",
"french": "Französisch",
"english": "Englisch",
"german": "Deutsch",
"select_language": "Sprache auswählen",
"search_item": "Nach Element suchen",
"search_tag": "Nach Schlagwörtern suchen",
"search_geographical": "Nach geografischer Zone suchen",
"show_date_field": "Datumsfelder anzeigen",
"hide_date_field": "Datumsfelder ausblenden",
"no_data": "Keine Daten verfügbar",
"search": "Suchen",
"no_events": "Keine Veranstaltungen für diesen Ort verfügbar.",
"start_date": "Anfangsdatum",
"end_date": "Enddatum",
"failed_suggestions": "Vorschläge konnten nicht geladen werden",
"error": "Fehler",
"password_different": "Ein anderes Passwort eingeben",
"create": "Erstellung",
"user_create": "Benutzer wurde erstellt",
"user_update": "Benutzer wurde aktualisiert",
"request_error": "Fehlerhafte Anfrage",
"incorrect_password": "Falsches Passwort",
"unknown_user": "Unbekannter Benutzer",
"disabled_user": "Benutzer deaktiviert",
"invalid_token": "Ungültiger Token",
"internal_error_server": "Interner Serverfehler",
"unknown_error_auth": "Unbekannter Authentifizierungsfehler",
"required_input": "Pflichtfeld",
"create_profile": "Profil erstellen",
"edit_pseudo": "Benutzernamen bearbeiten",
"password": "Passwort",
"enter_password": "Passwort eingeben",
"password_confirmed": "Passwort bestätigt",
"last_name": "Nachname",
"first_name": "Vorname",
"email": "E-Mail",
"edit_last_name": "Nachnamen bearbeiten",
"edit_first_name": "Vornamen bearbeiten",
"edit_email": "E-Mail-Adresse bearbeiten",
"birth_date": "Geburtsdatum",
"edit_birth": "Geburtsdatum bearbeiten",
"create_profile_button": "Profil erstellen",
"take_picture": "Foto aufnehmen",
"error_ia": "Google KI konnte das Bild nicht analysieren. Bitte ein anderes versuchen.",
"no_data_geo": "Keine geografischen Daten",
"response_status_update": "Statuscode-Antwort aktualisieren",
"error_token": "Token-Fehler",
"error_format": "Vom KI geliefertes Datenformat ist fehlerhaft",
"display_picture": "Bild anzeigen",
"analyze_image": "Bildanalyse läuft",
"loading_progress": "Ladefortschritt",
"error_event": "Veranstaltungsfehler",
"no_future_event": "Keine zukünftigen Veranstaltungen",
"error_user": "Benutzerfehler",
"empty_input": "Eingabefeld leer",
"info_event": "Veranstaltungsinfo",
"event_already": "Veranstaltung existiert bereits",
"picture_error": "Bildfehler",
"no_picture_published": "Kein Bild veröffentlicht",
"event_update": "Veranstaltung aktualisiert",
"location": "Ort",
"add_event": "Veranstaltung hinzufügen oder aktualisieren",
"edit_image": "Bilder bearbeiten",
"name": "Name",
"edit_event_name": "Veranstaltungsname bearbeiten",
"start_time": "Startzeit",
"end_time": "Endzeit",
"select_date": "Zum Auswählen eines Datums klicken",
"select_time": "Zum Auswählen einer Uhrzeit klicken",
"tag": "Schlagwörter",
"already_tag": "Dieses Schlagwort ist bereits vorhanden",
"enter_tag": "Ein Schlagwort eingeben",
"organizer": "Veranstalter",
"already_organiser": "Veranstalter bereits vorhanden",
"enter_organizer": "Veranstalter eingeben",
"description": "Beschreibung",
"describe_event": "Veranstaltung beschreiben",
"add": "Hinzufügen",
"different_password_error": "Passwörter stimmen nicht überein",
"update": "Aktualisieren",
"updated": "Aktualisiert",
"settings_updated": "Einstellungen aktualisiert",
"define_kilometer": "Kilometer definieren",
"email_sent": "E-Mail wurde gesendet",
"forgot_password": "Passwort vergessen",
"enter_email": "E-Mail eingeben",
"send_email": "E-Mail senden",
"invalid_cache": "Ungültiger Cache",
"item_date": "Datum : ",
"item_maps": "Karte : ",
"item_organizer": "Veranstalter : ",
"item_description": "Beschreibung : ",
"item_tags": "Schlagwörter : ",
"failed_auth": "Authentifizierung fehlgeschlagen",
"login_page": "Anmeldeseite",
"pseudo": "Benutzername",
"enter_existing_pseudo": "Vorhandenen Benutzernamen eingeben",
"remembr_me": "Angemeldet bleiben",
"new_user": "Neuer Benutzer? Konto erstellen",
"sign_in": "Anmelden",
"map_token": "Mapbox-Zugangstoken ist nicht verfügbar",
"geo_disabled": "Standortdienste sind deaktiviert.",
"permission_denied": "Standortberechtigungen wurden verweigert.",
"enable_permission": "Standortberechtigungen dauerhaft verweigert. Bitte in den Einstellungen aktivieren.",
"no_last_position": "Keine letzte bekannte Position verfügbar.",
"failed_location": "Standort konnte nicht ermittelt werden",
"failed_fetch": "Route konnte nicht abgerufen werden",
"invalid_coordinates_symbol": "Ungültige Koordinaten, Symbol kann nicht hinzugefügt werden.",
"error_symbol": "Fehler beim Hinzufügen des Symbols.",
"position_not_init": "Benutzerposition noch nicht initialisiert. Erneut versuchen.",
"invalid_coordinates": "Ungültige Koordinaten.",
"walking": "Zu Fuß",
"cycling": "Mit dem Fahrrad",
"driving": "Mit dem Auto",
"get_direction": "Wegbeschreibung und Markierungen anzeigen",
"missing_token": "Zugangstoken fehlt",
"geocoding_error": "Fehler bei der Geokodierung",
"no_found_place": "Kein Ort gefunden",
"upload_error": "Fehler beim Hochladen des Bildes",
"event_added": "Veranstaltung hinzugefügt",
"unknown_error": "Unbekannter Fehler",
"app_error": "Anwendungsfehler",
"at": "um",
"to_date": "bis",
"item_link": "Link : ",
"item_ticket": "Abendkarte : ",
"link": "Link",
"edit_link": "Linkereignis bearbeiten",
"ticket": "Abendkarte",
"edit_ticket": "Ticketlink bearbeiten",
"toogle_interest": "Fehler beim Umschalten des Interesses",
"error_update": "Fehler beim Update",
"count_interested": "Anzahl der Interessenten"
}

View File

@@ -0,0 +1,145 @@
{
"@@locale": "en",
"menu_list": "Event list menu",
"language": "Language",
"home": "Home",
"settings": "Settings",
"update_profile": "Update profile",
"about": "About",
"log_out": "Log out",
"french": "French",
"english": "English",
"german": "German",
"select_language": "Select language",
"search_item": "Search by item",
"search_tag": "Search by tags",
"search_geographical": "Search by geographical zone",
"show_date_field": "Show Date Fields",
"hide_date_field": "Hide Date Fields",
"no_data": "No data available",
"search": "Search",
"no_events": "No events available for this location.",
"start_date": "Start date",
"end_date": "End date",
"failed_suggestions": "Failed to load suggestions",
"error":"Error",
"password_different":"Must write a different password",
"create": "Creation",
"user_create": "Your user created",
"user_update": "Your user updated",
"request_error": "Poorly constructed query",
"incorrect_password": "Incorrect password",
"unknown_user": "Unknown user",
"disabled_user": "User disabled",
"invalid_token": "Invalid token",
"internal_error_server": "Internal error server",
"unknown_error_auth": "Unknown error authentification",
"required_input": "Required input",
"create_profile": "Create profile",
"edit_pseudo": "Edit pseudo",
"password":"Password",
"enter_password": "Enter the passord",
"password_confirmed": "Password confirmed",
"last_name": "Last name",
"first_name": "First name",
"email": "Mail",
"edit_last_name": "Edit name",
"edit_first_name": "Edit first name",
"edit_email": "Edit email address",
"birth_date": "Birth date",
"edit_birth": "Edit birth date",
"create_profile_button": "Create profile",
"take_picture": "Take a picture",
"error_ia": "Google AI failed to analyze picture. Retry with another one",
"no_data_geo": "No geographical data",
"response_status_update": "response status code update",
"error_token": "Token error",
"error_format": "Data format error given by AI",
"display_picture": "Display the Picture",
"analyze_image": "Image Analyze in progress",
"loading_progress": "Loading progress",
"error_event": "Event error",
"no_future_event": "No future event",
"error_user": "Error user",
"empty_input": "Empty input",
"info_event": "Event info",
"event_already": "Event already exists",
"picture_error": "Picture error",
"no_picture_published": "No picture published",
"event_update": "Event updated",
"location": "Location",
"add_event": "Add or Update a event",
"edit_image": "Edit pictures",
"name": "Name",
"edit_event_name": "Edit event name",
"start_time": "Start time",
"end_time": "End time",
"select_date": "Click to select a date",
"select_time": "Click to select a time",
"tag": "Tags",
"already_tag": "You have already this tags",
"enter_tag": "Enter a tag",
"organizer": "Organizer",
"already_organiser": "You have already a organizer",
"enter_organizer": "Enter a organizer",
"description": "Description",
"describe_event": "Describe event",
"add": "Add",
"update_profile": "Update profile",
"different_password_error": "Different password",
"update": "Update",
"updated": "Updated",
"settings_updated": "Settings updated",
"define_kilometer": "Define Kilometer",
"settings": "Settings",
"email_sent": "Email has been sent",
"forgot_password": "Forgot password",
"enter_email": "Enter the email",
"send_email": "Send email",
"invalid_cache": "Invalid cache",
"item_date": "Date : ",
"item_maps": "Maps : ",
"item_organizer": "Organizer : ",
"item_description": "Description : ",
"item_tags": "Tags : ",
"failed_auth": "Authentification failed",
"login_page": "Login page",
"pseudo": "Pseudo",
"enter_existing_pseudo": "Enter a existing pseudo",
"remembr_me": "Remember me",
"new_user": "New User? Create Account",
"sign_in": "Sign in",
"map_token": "Mapbox Access Token is not available",
"geo_disabled": "Location services are disabled.",
"permission_denied":"Location permissions are denied.",
"enable_permission": "Location permissions are permanently denied. Enable them in settings.",
"no_last_position": "No last known position available.",
"failed_location": "Failed to get user location",
"failed_fetch": "Failed to fetch the route",
"invalid_coordinates_symbol": "Invalid coordinates, cannot add symbol.",
"error_symbol": "Error when adding symbol.",
"position_not_init": "User position is not yet initialized. Try again.",
"invalid_coordinates": "Invalid coordinates.",
"walking": "Walking",
"cycling": "Cycling",
"driving": "Driving",
"get_direction": "Get Directions and Markers",
"missing_token": "Missing access token",
"geocoding_error": "Error when geocoding",
"no_found_place": "No found place",
"upload_error": "Error when image uploading",
"event_added": "Event added",
"unknown_error": "Unknown error",
"app_error": "Application error",
"at": "at",
"to_date": "to",
"item_link": "Link : ",
"item_ticket": "Ticket : ",
"link": "Link",
"edit_link": "Edit link name",
"ticket": "Ticket",
"edit_ticket": "Edit ticket link",
"toogle_interest": "Error toggle interest",
"error_update": "Error when updating",
"count_interested": "Interested people number"
}

View File

@@ -0,0 +1,146 @@
{
"@@locale": "fr",
"menu_list": "Liste d'évènement",
"language": "Langue",
"home": "Accueil",
"settings": "Paramètres",
"update_profile": "Modifier profil",
"about": "À propos",
"log_out": "Se déconnecter",
"french": "Français",
"english": "Anglais",
"german": "Allemand",
"select_language": "Selectionne la langue",
"search_item": "Recherche par item",
"search_tag": "Recherche par tags",
"search_geographical": "Recherche par zone géographique",
"show_date_field": "Afficher champ date",
"hide_date_field": "Cacher Date Fields",
"no_data": "Aucune donnée disponible",
"search": "Recherche",
"no_events": "Pas d'évènements dans cette localisation",
"start_date": "Date de début",
"end_date": "Date de fin",
"failed_suggestions": "Echec de chargement des suggestions",
"error":"Erreur",
"password_different":"Tu dois écrire un mot de passe different",
"create": "Création",
"user_create": "Votre utilisateur a été créé",
"user_update": "Votre utilisateur a été modifié",
"request_error": "Requête mal construite",
"incorrect_password": "Mot de passe incorrect",
"unknown_user": "Utilisateur inconnu",
"disabled_user": "Utilisateur désactivé",
"invalid_token": "Token invalide",
"internal_error_server": "Erreur interne de serveur",
"unknown_error_auth": "Problème d'authentification inconnu",
"required_input": "Champ requis",
"create_profile": "Creation profil",
"edit_pseudo": "Modifier le pseudo",
"password":"Mot de passe",
"enter_password": "Entrez le password",
"password_confirmed": "Confirmez le mot de passe",
"last_name": "Nom",
"first_name": "Prénom",
"email": "Email",
"edit_last_name": "Modifier le nom",
"edit_first_name": "Modifier le prénom",
"edit_email": "Modifier l'email",
"birth_date": "Date de naissance",
"edit_birth": "Modifier la date de naissance",
"create_profile_button": "Créer le profil",
"take_picture": "Take a picture",
"error_ia": "L'IA de Google n'a pas su analyser l'image. Recommencer avec une autre",
"no_data_geo": "Aucune donnée géographique",
"response_status_update": "Code du statut de réponse de la modification",
"error_token": "Erreur de token",
"error_format": "Erreur de format de donnée fourni par l'IA",
"display_picture": "Display the Picture",
"analyze_image": "Analyse de l'image en cours",
"loading_progress": "Chargement en cours",
"error_event": "Erreur de l'évènement",
"no_future_event": "Évènement non futur",
"error_user": "Erreur de l'utilisateur",
"empty_input": "Champ vide",
"info_event": "Event info",
"event_already": "Event already exists",
"picture_error": "Erreur image",
"no_picture_published": "Image non publiée",
"event_update": "Évènement modifié",
"location": "Lieu",
"add_event": "Ajouter ou modifier un évènement",
"edit_image": "Changer la photo",
"name": "Nom",
"edit_event_name": "Changer le nom de l'évènement",
"start_time": "Heure de début",
"end_time": "Heure de fin",
"select_date": "Cliquer pour selectionner une date",
"select_time": "Cliquer pour selectionner une heure",
"tag": "Tags",
"already_tag": "Tu as déjà entré ce tag",
"enter_tag": "Entrer un tag",
"organizer": "Organisateur",
"already_organiser": "Tu as déjà rentré cet organisateur",
"enter_organizer": "Entrer un organisateur",
"description": "Description",
"describe_event": "Décrire l'évènement",
"add": "Ajouter",
"update_profile": "Modifier le profil",
"different_password_error": "Mot de passe différent",
"update": "Mettre à jour",
"updated": "Mis à jour",
"settings_updated": "Paramètre mis à jour",
"define_kilometer": "Definir un kilomètre",
"settings": "Paramètres",
"email_sent": "Email a été envoyé",
"forgot_password": "Mot de passe oublié",
"enter_email": "Entrez l'email",
"send_email": "Send email",
"invalid_cache": "Cache invalide",
"item_date": "Date : ",
"item_maps": "Carte : ",
"item_organizer": "Organisateurs : ",
"item_description": "Description : ",
"item_tags": "Tags : ",
"failed_auth": "Échec de l'authenticaton",
"login_page": "Page d'authentification",
"pseudo": "Pseudo",
"enter_existing_pseudo": "Entrez un pseudo existant",
"remembr_me": "Se souvenir de moi",
"new_user": "Nouvel utilisateur ? Créer un compte",
"sign_in": "Se connecter",
"map_token": "Token d'accès de Mapbox n'est pas disponible",
"geo_disabled": "Les services de localisation sont désactivés.",
"permission_denied":"Les permissions de localisation sont refusées.",
"enable_permission": "Les permissions de localisation sont toujours désactivés. Il faut les désactiver",
"no_last_position": "Aucune position n'est pas disponible.",
"failed_location": "Échec de récupération des données geographique",
"failed_fetch": "Échec de récupération des routes",
"invalid_coordinates_symbol": "Coordonnées invalides. On ne peut pas ajouter le symbole",
"error_symbol": "Erreur lors de l'ajout du symbole",
"position_not_init": "Coordonnées non initialisées. Essaye encore.",
"invalid_coordinates": "Coordonnées invalides",
"walking": "Marche",
"cycling": "Vélo",
"driving": "Voiture",
"get_direction": "Get Directions and Markers",
"missing_token": "Token d'accès manquant",
"geocoding_error": "Erreur lors du geocodage",
"no_found_place": "Lieu introuvable",
"upload_error": "Erreur lors de l'upload d'image",
"event_added": "Évènement ajouté",
"unknown_error": "Erreur inconnue",
"app_error": "Erreur d'application",
"at": "à",
"to_date": "jusqu'à",
"item_link": "Lien : ",
"item_ticket": "Billet : ",
"link": "Lien",
"edit_link": "Editer le lien",
"ticket": "Billet",
"edit_ticket": "Editer le lien du billet",
"toogle_interest": "Erreur de bouton de changement",
"error_update": "Erreur lors de la mise à jour",
"count_interested": "Nombre de personne interessé"
}

View File

@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LocaleProvider with ChangeNotifier {
static const _localeKey = 'locale_code';
Locale _locale = const Locale('en');
Locale get locale => _locale;
LocaleProvider() {
_loadLocale();
}
Future<void> _loadLocale() async {
final prefs = await SharedPreferences.getInstance();
final code = prefs.getString(_localeKey);
if (code != null && L10n.all.contains(Locale(code))) {
_locale = Locale(code);
notifyListeners();
}
}
void setLocale(Locale locale) async {
if (!L10n.all.contains(locale)) return;
_locale = locale;
notifyListeners();
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_localeKey, locale.languageCode);
}
}
class L10n {
static final all = [
const Locale('en'),
const Locale('fr'),
const Locale('de')
];
}

View File

@@ -1,281 +1,36 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:provider/provider.dart';
import 'dart:convert';
import 'dart:io';
import 'pages/LoginDemo.dart';
import 'locale_provider.dart'; // <-- à adapter selon ton arborescence
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'classes/notification_service.dart';
//import 'MyHomePage.dart';
import 'pages/ListItemMenu.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
await NotificationService.initialize();
import 'classes/alert.dart';
import 'variable/globals.dart' as globals;
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(MyApp());
runApp(
ChangeNotifierProvider(
create: (_) => LocaleProvider(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final localeProvider = Provider.of<LocaleProvider>(
context); // écoute les changements de langue
return MaterialApp(
debugShowCheckedModeBanner: false,
locale: localeProvider.locale, // <-- utilise la locale courante
supportedLocales: L10n.all,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: LoginDemo(),
);
}
}
class LoginDemo extends StatefulWidget {
@override
_LoginDemoState createState() => _LoginDemoState();
}
class _LoginDemoState extends State<LoginDemo> with ShowErrorDialog {
TextEditingController inputPseudo = TextEditingController();
TextEditingController inputPassword = TextEditingController();
Future<void> _login(BuildContext context) async {
var url = Uri.parse("${globals.api}/token");
var pseudo = inputPseudo.text;
var password = inputPassword.text;
print("get login");
print(pseudo.isNotEmpty);
print(password.isNotEmpty);
if ((pseudo.isNotEmpty) && (password.isNotEmpty)) {
print(url);
try {
//String credentials = "${pseudo}:${password}";
//Codec<String, String> stringToBase64 = utf8.fuse(base64);
//String encoded = stringToBase64.encode(credentials);
var response = await http.post(url,
// headers: {
// HttpHeaders.authorizationHeader: 'Basic $encoded',
//}
headers: {
'accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
"username": "${pseudo}",
"password": "${password}"
});
print(response.statusCode);
if ((response.statusCode == 200) || (response.statusCode == 201)) {
SharedPreferences prefs = await SharedPreferences.getInstance();
var cookies = response.headers["set-cookie"].toString().split(";");
for (var cookie in cookies) {
var cookiesMany = cookie.split(",");
for (var cookie2 in cookiesMany) {
switch (cookie2.split("=")[0]) {
case "access_token":
{
prefs.setString("access_token", cookie2.split("=")[1]);
}
break;
default:
break;
}
}
}
Navigator.push(
context, MaterialPageRoute(builder: (_) => ListItemMenu()));
} else {
var text = "";
switch (response.statusCode) {
case 400:
{
text = "Requête mal construite";
}
break;
case 406:
{
text = "Mot de passe incorrect";
}
break;
case 404:
{
text = "Utilisateur inconnu";
}
break;
case 403:
{
text = "Utilisateur desactive";
}
break;
case 410:
{
text = "Token invalide";
}
break;
case 500:
{
text = "Probleme interne du serveur";
}
break;
default:
{
text = "Probleme d'authentification inconnu";
}
break;
}
showErrorDialog(context, text);
}
} catch (e) {
showErrorDialog(context, "${e}");
}
} else {
showErrorDialog(context, "Champ vide");
}
}
void start() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var access_token = prefs.getString("access_token") ?? "";
print("Get access token");
if (access_token.isNotEmpty) {
print("Appel HTTP");
var urlToken = Uri.parse("${globals.api}/token");
var responseToken = await http.get(urlToken,
headers: {HttpHeaders.cookieHeader: 'access_token: ${access_token}'});
print(responseToken.statusCode);
if (responseToken.statusCode == 200) {
print("route to item list");
Navigator.push(
context, MaterialPageRoute(builder: (_) => ListItemMenu()));
} else {
prefs.remove("access_token");
}
}
}
@override
void initState() {
_checkLocationPermission();
start();
super.initState();
}
Future<void> _checkLocationPermission() async {
PermissionStatus status = await Permission.location.status;
if (status.isGranted) {
print("Location permission granted");
} else if (status.isDenied) {
print("Location permission denied");
_requestLocationPermission();
} else if (status.isPermanentlyDenied) {
print("Location permission permanently denied");
openAppSettings();
}
}
// Request location permission
Future<void> _requestLocationPermission() async {
PermissionStatus status = await Permission.location.request();
if (status.isGranted) {
print("Location permission granted");
} else if (status.isDenied) {
print("Location permission denied");
} else if (status.isPermanentlyDenied) {
print("Location permission permanently denied");
openAppSettings();
}
}
// Open app settings to allow user to grant permission manually
Future<void> _openAppSettings() async {
bool opened = await openAppSettings();
if (opened) {
print("App settings opened");
} else {
print("Failed to open app settings");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text("Login Page"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 60.0),
child: Center(
child: Container(
width: 200,
height: 150,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(50.0)),
child: Image.asset('./images/flutter.png')),
),
),
Padding(
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
padding: EdgeInsets.symmetric(horizontal: 15),
child: TextField(
controller: inputPseudo,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Pseudo',
hintText: 'Enter pseudo existent'),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextField(
controller: inputPassword,
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Password',
hintText: 'Enter secure password'),
),
),
TextButton(
onPressed: () {
//TODO FORGOT PASSWORD SCREEN GOES HERE
},
child: Text(
'Forgot Password',
style: TextStyle(color: Colors.blue, fontSize: 15),
),
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () {
_login(context);
},
child: Text(
'Login',
style: TextStyle(color: Colors.white, fontSize: 25),
),
),
),
SizedBox(
height: 130,
),
Text('New User? Create Account')
],
),
),
);
}
}

View File

@@ -0,0 +1,335 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'dart:convert';
import 'dart:io';
import '../pages/LoginDemo.dart';
import '../classes/alert.dart';
import '../variable/globals.dart' as globals;
import '../classes/ad_helper.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; // Créé plus loin
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: AddProfile(),
);
}
}
class AddProfile extends StatefulWidget {
const AddProfile({super.key});
@override
_AddProfileState createState() => _AddProfileState();
}
class _AddProfileState extends State<AddProfile> with ShowAlertDialog {
BannerAd? _bannerAd;
TextEditingController inputUserName = TextEditingController();
TextEditingController inputName = TextEditingController();
TextEditingController inputFirstName = TextEditingController();
TextEditingController inputEmail = TextEditingController();
TextEditingController inputBirth = TextEditingController();
TextEditingController inputPassword = TextEditingController();
TextEditingController inputPasswordConfirmed = TextEditingController();
onTapFunctionDatePicker({required BuildContext context}) async {
DateTime? pickedDate = await showDatePicker(
context: context,
firstDate: DateTime(1900),
initialDate: DateTime.now(),
lastDate: DateTime(2104));
if (pickedDate == null) return;
inputBirth.text = DateFormat("dd/MM/yyyy").format(pickedDate);
}
convertNulltoEmptyString(var check) {
if (check == null) {
return "";
}
return check;
}
convertNulltoArray(List<String> check) {
if (check == null) {
return [];
}
return check;
}
String formatDate(String date) {
var splitedDate = date.split("/");
var day = splitedDate[0];
var month = splitedDate[1];
var year = splitedDate[2];
return "${year}-${month}-${day}";
}
Future<void> _createProfile(BuildContext context) async {
var username = inputUserName.text;
var firstName = inputFirstName.text;
var name = inputName.text;
var email = inputEmail.text;
var password = inputPassword.text;
var confirmedPassword = inputPasswordConfirmed.text;
var birth = DateTime.parse(formatDate(inputBirth.text));
if ((password.isNotEmpty) && (confirmedPassword.isNotEmpty)) {
if (password != confirmedPassword) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.password_different ??
"Must write a different password");
return;
}
}
var urlPut = Uri.parse("${globals.api}/mail");
var responsePost = await http.post(urlPut,
headers: {
HttpHeaders.acceptHeader: 'application/json, text/plain, */*',
HttpHeaders.contentTypeHeader: 'application/json'
},
body: jsonEncode({
'name': name,
'username': username,
'firstName': firstName,
'password': password,
'email': email,
'birth': birth.toString()
}));
print(responsePost.statusCode);
if (responsePost.statusCode == 200) {
showAlertDialog(
context,
AppLocalizations.of(context)?.create ?? "Creation",
AppLocalizations.of(context)?.user_create ?? "Your user created");
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (_) => LoginDemo()));
return;
}
final errorMessages = {
400: AppLocalizations.of(context)?.request_error ??
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
final text = errorMessages[responsePost.statusCode] ??
AppLocalizations.of(context)?.unknown_error_auth ??
"Unknown error auth";
showAlertDialog(
context, AppLocalizations.of(context)?.error ?? "Error", text);
}
@override
void initState() {
super.initState();
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
}
final _formKey = GlobalKey<FormState>();
String? _validateField(String? value) {
return value!.isEmpty
? AppLocalizations.of(context)?.required_input ?? 'Required input'
: null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(
AppLocalizations.of(context)?.create_profile ?? "Create profile"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!)),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputUserName,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Pseudo',
hintText: AppLocalizations.of(context)?.edit_pseudo ??
'Edit pseudo'),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputPassword,
validator: (value) => _validateField(value),
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.password ??
'Password',
hintText:
AppLocalizations.of(context)?.enter_password ??
'Enter the password'),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputPasswordConfirmed,
validator: (value) => _validateField(value),
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.password_confirmed ??
'Password confirmed',
hintText:
AppLocalizations.of(context)?.password_confirmed ??
'Password confirmed',
),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputName,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.last_name ??
'Last name',
hintText:
AppLocalizations.of(context)?.edit_last_name ??
'Edit last name'),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputFirstName,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.first_name ??
'First name',
hintText:
AppLocalizations.of(context)?.edit_first_name ??
'Edit first name'),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputEmail,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.email ?? 'Email',
hintText: AppLocalizations.of(context)?.edit_email ??
'Edit email'),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputBirth,
readOnly: true,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.birth_date ??
'Birth date',
hintText: AppLocalizations.of(context)?.edit_birth ??
'Edit birth date'),
onTap: () => onTapFunctionDatePicker(context: context)),
),
SizedBox(
height: 30,
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_createProfile(context);
}
},
child: Text(
AppLocalizations.of(context)?.create_profile_button ??
"Create profile",
style: TextStyle(color: Colors.white, fontSize: 25),
),
),
)
],
),
),
));
}
}

View File

@@ -1,11 +1,16 @@
import 'dart:async';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
import '../classes/MyDrawer.dart';
import 'DisplayPictureScreen.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; // Créé plus loin
Future<void> main() async {
// Ensure that plugin services are initialized so that `availableCameras()`
@@ -45,12 +50,14 @@ class Camera extends StatefulWidget {
class CameraState extends State<Camera> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;
final AuthService _authService = AuthService();
@override
void initState() {
super.initState();
// To display the current output from the Camera,
// create a CameraController.
_authService.checkTokenStatus(context);
_controller = CameraController(
// Get a specific camera from the list of available cameras.
@@ -63,8 +70,6 @@ class CameraState extends State<Camera> {
_initializeControllerFuture = _controller.initialize();
}
Future<void> getCamera() async {}
Future<void> pickImage() async {
final imagePicker = ImagePicker();
final pickedFile = await imagePicker.pickImage(source: ImageSource.gallery);
@@ -91,10 +96,13 @@ class CameraState extends State<Camera> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Take a picture')),
appBar: AppBar(
title: Text(AppLocalizations.of(context)?.take_picture ??
"Take a picture")),
// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
drawer: MyDrawer(),
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {

View File

@@ -0,0 +1,166 @@
import 'dart:async';
import 'dart:io';
import '../classes/events.dart';
import '../classes/MyDrawer.dart';
import 'package:image_picker/image_picker.dart';
import 'EditEvent.dart';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; // Créé
Future<void> main() async {
// Ensure that plugin services are initialized so that `availableCameras()`
// can be called before `runApp()`
WidgetsFlutterBinding.ensureInitialized();
// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();
// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;
Events? events;
runApp(
MaterialApp(
theme: ThemeData.dark(),
home: CameraEdit(
// Pass the appropriate camera to the TakePictureScreen widget.
camera: firstCamera,
events: events),
),
);
}
// A screen that allows users to take a picture using a given camera.
class CameraEdit extends StatefulWidget {
const CameraEdit({super.key, required this.camera, required this.events});
final Events? events;
final CameraDescription camera;
@override
CameraEditState createState() => CameraEditState();
}
class CameraEditState extends State<CameraEdit> {
late CameraController _controller;
late Future<void> _initializeControllerFuture;
final AuthService _authService = AuthService();
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
// To display the current output from the Camera,
// create a CameraController.
_controller = CameraController(
// Get a specific camera from the list of available cameras.
widget.camera,
// Define the resolution to use.
ResolutionPreset.medium,
);
// Next, initialize the controller. This returns a Future.
_initializeControllerFuture = _controller.initialize();
}
Future<void> pickImage() async {
final imagePicker = ImagePicker();
final pickedFile = await imagePicker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => EditEvent(
// Pass the automatically generated path to
// the DisplayPictureScreen widget.
events: widget.events,
imgPath: pickedFile.path,
),
),
);
}
}
@override
void dispose() {
// Dispose of the controller when the widget is disposed.
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)?.take_picture ??
'Take a picture')),
// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
drawer: MyDrawer(),
body: FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(_controller);
} else {
// Otherwise, display a loading indicator.
return const Center(child: CircularProgressIndicator());
}
},
),
floatingActionButton: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
FloatingActionButton(
onPressed: pickImage,
child: Icon(Icons.photo_library),
),
SizedBox(width: 40),
FloatingActionButton(
// Provide an onPressed callback.
onPressed: () async {
// Take the Picture in a try / catch block. If anything goes wrong,
// catch the error.
try {
// Ensure that the camera is initialized.
await _initializeControllerFuture;
// Attempt to take a picture and get the file `image`
// where it was saved.
final image = await _controller.takePicture();
if (!context.mounted) return;
// If the picture was taken, display it on a new screen.
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => EditEvent(
// Pass the automatically generated path to
// the DisplayPictureScreen widget.
events: widget.events,
imgPath: image.path,
),
),
);
} catch (e) {
// If an error occurs, log the error to the console.
print(e);
}
},
child: const Icon(Icons.camera_alt),
)
],
)));
}
}

View File

@@ -12,8 +12,19 @@ import 'UpdateEventImage.dart';
import 'dart:convert';
import '../variable/globals.dart' as globals;
import '../classes/MyDrawer.dart';
void main() {
import '../classes/ad_helper.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; // Créé
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
runApp(MyApp());
}
@@ -54,10 +65,22 @@ class DisplayPictureScreen extends StatefulWidget {
// A widget that displays the picture taken by the user.
class DisplayPictureScreenState extends State<DisplayPictureScreen>
with ShowDescImageAdd, ShowErrorDialog, TickerProviderStateMixin {
with ShowDescImageAdd, ShowAlertDialog, TickerProviderStateMixin {
BannerAd? _bannerAd;
final AuthService _authService = AuthService();
late AnimationController controller;
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
controller = AnimationController(
/// [AnimationController]s can be created with `vsync: this` because of
/// [TickerProviderStateMixin].
@@ -67,7 +90,6 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
setState(() {});
});
controller.repeat(reverse: false);
super.initState();
_getEventInfosFromImage();
}
@@ -80,8 +102,46 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
Future<void> displayError(String e) async {
print("problem gemini : ${e}");
showErrorDialog(context,
"L'IA de Google n'a pas su analyser l'image. Recommecer avec une autre");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? 'Error',
AppLocalizations.of(context)?.error_ia ??
'Google AI failed to analyze picture. Retry with another one');
}
void _showErrorDialog(BuildContext context, String title, String message) {
showAlertDialog(context, title, message);
}
Future<Map<String, dynamic>?> _fetchGeolocation(String place) async {
final apiKey = dotenv.env['PLACE_API_KEY'] ?? '';
final response = await http.get(Uri.parse(
'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${place}}&key=$apiKey'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['results'].isNotEmpty) {
return data['results'][0]['geometry']['location'];
}
}
return null;
}
Future<bool> _isDuplicateEvent(String accessToken,
Map<String, dynamic> jsonData, Map<String, dynamic> location) async {
final url = Uri.parse(
"${globals.api}/events/search?item=${jsonData["name"]}&date_event=${jsonData["start_date"]}"
"&min_lat=${location['lat']}&max_lat=${location['lat']}"
"&min_lon=${location['lng']}&max_lon=${location['lng']}");
final response = await http.get(url,
headers: {HttpHeaders.cookieHeader: 'access_token=$accessToken'});
if (response.statusCode == 200) {
final events = jsonDecode(utf8.decode(response.bodyBytes));
return events.isNotEmpty;
}
return false;
}
Future<void> searchEvents(String json, String imagePath) async {
@@ -94,15 +154,29 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
var name = jsonData["name"];
print("name : ${name}");
var place = jsonData["place"];
var date = jsonData["start_date"];
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var urlGet = Uri.parse("${globals.api}/events?name=${name}");
final location = await _fetchGeolocation(place);
if (location == null) {
_showErrorDialog(
context,
AppLocalizations.of(context)?.error ?? 'Error',
AppLocalizations.of(context)?.no_data_geo ??
'No geographical data');
return;
}
var responseGet = await http.get(urlGet,
headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'});
if (responseGet.statusCode == 200) {
var events = jsonDecode(utf8.decode(responseGet.bodyBytes));
final url = Uri.parse(
"${globals.api}/events/search?item=${name}&date_event=${date}"
"&min_lat=${location['lat']}&max_lat=${location['lat']}"
"&min_lon=${location['lng']}&max_lon=${location['lng']}");
final response = await http.get(url,
headers: {HttpHeaders.cookieHeader: 'access_token=$accessToken'});
if (response.statusCode == 200) {
var events = jsonDecode(utf8.decode(response.bodyBytes));
print("reponse http : ${events.length}");
if (events.length == 0) {
Navigator.push(
@@ -116,12 +190,24 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
MaterialPageRoute(
builder: (_) => ItemMenu(title: events[0]["id"])));
}
} else {
String error = AppLocalizations.of(context)?.response_status_update ??
'Response status update : ${response.statusCode}';
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? 'Error',
"${error} : ${response.statusCode}");
}
} else {
showErrorDialog(context, "Erreur de token");
showAlertDialog(context, AppLocalizations.of(context)?.error ?? 'Error',
AppLocalizations.of(context)?.error_token ?? "Token error");
}
} catch (e) {
showErrorDialog(context, "Erreur de format de donnée fourni par l'IA");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.error_format ??
"Data format error given by AI");
}
//showDescImageAddDialog(context, message);
@@ -138,7 +224,7 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
gemini
.textAndImage(
text:
"Peux-tu donner le nom, la date avec l'année actuelle ou d'une année future proche et le lieu de l'évènement sous format JSON (sans le caratère json au début de la chaine de caractère) avec les valeurs suivantes : name, place, description, tags (tableau sans espace), organizers (tableau), start_date et end_date (si le end_date est vide, alors donnez une valeur de six de plus par rapport à start_date) sous le format en YYYY-MM-DD HH:mm:ssZ",
"Peux-tu donner le nom, la date (si l'année n'est pas précisé, mettez l'année actuelle ou future) et le lieu de l'évènement sous format JSON (sans le caratère json au début de la chaine de caractère) avec les valeurs suivantes : name, place, description, tags (tableau sans espace), organizers (tableau), start_date et end_date (si le end_date est vide, alors donnez une valeur de six de plus par rapport à start_date) sous le format en YYYY-MM-DD HH:mm:ssZ",
images: [file.readAsBytesSync()],
modelName: "models/gemini-1.5-pro-latest")
.then((value) => searchEvents(
@@ -149,21 +235,33 @@ class DisplayPictureScreenState extends State<DisplayPictureScreen>
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Display the Picture')),
appBar: AppBar(
title: Text(AppLocalizations.of(context)?.display_picture ??
"Display The Picture")),
// The image is stored as a file on the device. Use the `Image.file`
// constructor with the given path to display the image.
drawer: MyDrawer(),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!)),
Text(
'Analyse de l\'image en cours',
AppLocalizations.of(context)?.analyze_image ??
'Image analyze in progress',
style: Theme.of(context).textTheme.titleLarge,
),
CircularProgressIndicator(
value: controller.value,
semanticsLabel: 'Loading progress',
semanticsLabel:
AppLocalizations.of(context)?.loading_progress ??
'Loading progress',
),
])));
}

View File

@@ -0,0 +1,921 @@
import 'package:covas_mobile/pages/CameraEdit.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:textfield_tags/textfield_tags.dart';
import 'dart:convert';
import 'dart:io';
import '../classes/events.dart';
import '../classes/MyDrawer.dart';
import 'package:camera/camera.dart';
import '../classes/alert.dart';
import '../classes/eventAdded.dart';
import '../variable/globals.dart' as globals;
import '../classes/ad_helper.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; // Créé
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Events? events;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: EditEvent(
events: events,
imgPath: "",
),
);
}
}
class EditEvent extends StatefulWidget {
const EditEvent({Key? key, required this.events, required this.imgPath})
: super(key: key);
final Events? events;
final String imgPath;
@override
_EditEventState createState() => _EditEventState();
}
class _EditEventState extends State<EditEvent>
with ShowAlertDialog, ShowEventDialog {
BannerAd? _bannerAd;
final AuthService _authService = AuthService();
TextEditingController inputName = TextEditingController();
TextEditingController inputTicket = TextEditingController();
TextEditingController inputLink = TextEditingController();
TextEditingController inputDate = TextEditingController();
TextEditingController inputDesc = TextEditingController();
TextEditingController inputGeo = TextEditingController();
TextEditingController startDatepicker = TextEditingController();
TextEditingController startTimepicker = TextEditingController();
TextEditingController endDatepicker = TextEditingController();
TextEditingController endTimepicker = TextEditingController();
final _stringTagController = StringTagController();
DateTime startDate = DateTime.now();
DateTime endDate = DateTime.now();
List<Map<String, dynamic>> suggestions = [];
String geographicalZone = "";
String imgUrl = "";
List<String> initialTags = [];
final _stringOrgaController = StringTagController();
List<String> initialOrga = [];
onTapFunctionDatePicker(
{required BuildContext context, required String position}) async {
DateTime date;
if ((startDatepicker.text.isEmpty) || (endDatepicker.text.isEmpty)) {
date = DateTime.now();
} else {
date = DateTime.parse(formatDate(startDatepicker.text));
if (position == "end") {
date = DateTime.parse(formatDate(endDatepicker.text));
}
}
DateTime? pickedDate = await showDatePicker(
context: context,
firstDate: date,
initialDate: date,
lastDate: DateTime(2104));
if (pickedDate == null) return;
if (position == "start") {
startDatepicker.text = DateFormat("dd/MM/yyyy").format(pickedDate);
}
if (position == "end") {
endDatepicker.text = DateFormat("dd/MM/yyyy").format(pickedDate);
}
}
onTapFunctionTimePicker(
{required BuildContext context, required String position}) async {
TimeOfDay time;
if ((startTimepicker.text.isEmpty) || (endTimepicker.text.isEmpty)) {
time = TimeOfDay.now();
} else {
DateTime date = new DateTime.now();
date = date.copyWith(
hour: int.parse(startTimepicker.text.split(":")[0]),
minute: int.parse(startTimepicker.text.split(":")[1]));
time = TimeOfDay.fromDateTime(date);
if (position == "end") {
date = date.copyWith(
hour: int.parse(endTimepicker.text.split(":")[0]),
minute: int.parse(endTimepicker.text.split(":")[1]));
time = TimeOfDay.fromDateTime(date);
}
}
TimeOfDay? pickedDate =
await showTimePicker(context: context, initialTime: time);
if (pickedDate == null) return;
if (position == "start") {
startTimepicker.text = pickedDate.format(context);
}
if (position == "end") {
endTimepicker.text = pickedDate.format(context);
}
}
convertNulltoEmptyString(var check) {
if (check == null) {
return "";
}
return check;
}
convertNulltoArray(List<String> check) {
if (check == null) {
return [];
}
return check;
}
String formatDate(String date) {
var splitedDate = date.split("/");
var day = splitedDate[0];
var month = splitedDate[1];
var year = splitedDate[2];
return "${year}-${month}-${day}";
}
Future<void> _updateEvent(BuildContext context) async {
if (!_isEventInFuture()) {
_showErrorDialog(
context,
AppLocalizations.of(context)?.error_event ?? "Event error",
AppLocalizations.of(context)?.no_future_event ?? "No future event");
return;
}
final accessToken = await _getAccessToken();
if (accessToken.isEmpty) {
_showErrorDialog(
context,
AppLocalizations.of(context)?.error_user ?? "User error",
AppLocalizations.of(context)?.empty_input ?? "Empty input");
return;
}
try {
await dotenv.load();
final geolocation = await _fetchGeolocation();
if (geolocation == null) {
_showErrorDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.no_data_geo ??
"No geographical data");
return;
}
if (await _isDuplicateEvent(accessToken, geolocation)) {
_showErrorDialog(
context,
AppLocalizations.of(context)?.info_event ?? "Event info",
AppLocalizations.of(context)?.event_already ??
"Event already exists");
return;
}
if (widget.imgPath.isNotEmpty) {
imgUrl = await _uploadImage(widget.imgPath);
if (imgUrl.isEmpty) {
_showErrorDialog(
context,
AppLocalizations.of(context)?.picture_error ?? "Error picture",
AppLocalizations.of(context)?.no_picture_published ??
"No picture published");
return;
}
}
await _updateEventData(accessToken, geolocation);
String message =
AppLocalizations.of(context)?.event_update ?? "Event updated";
showEventDialog(context, "${message} : ${inputName.text}");
} catch (e) {
_showErrorDialog(
context, AppLocalizations.of(context)?.error ?? "Error", "$e");
}
}
bool _isEventInFuture() {
DateTime startDateCompare = DateTime.parse(
"${formatDate(startDatepicker.text)}T${startTimepicker.text.replaceAll('-', ':')}");
return startDateCompare.isAfter(DateTime.now());
}
Future<String> _getAccessToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString("access_token") ?? "";
}
Future<Map<String, dynamic>?> _fetchGeolocation() async {
final apiKey = dotenv.env['PLACE_API_KEY'] ?? '';
final response = await http.get(Uri.parse(
'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${inputGeo.text}&key=$apiKey'));
if (response.statusCode == 200) {
final data = json.decode(response.body);
if (data['results'].isNotEmpty) {
return data['results'][0]['geometry']['location'];
}
}
return null;
}
Future<bool> _isDuplicateEvent(
String accessToken, Map<String, dynamic> location) async {
final url = Uri.parse(
"${globals.api}/events/search?item=${inputName.text}&date_event=${formatDate(startDatepicker.text)}"
"&min_lat=${location['lat']}&max_lat=${location['lat']}"
"&min_lon=${location['lng']}&max_lon=${location['lng']}");
final response = await http.get(url,
headers: {HttpHeaders.cookieHeader: 'access_token=$accessToken'});
if (response.statusCode == 200) {
final events = jsonDecode(utf8.decode(response.bodyBytes));
return events.isNotEmpty && events[0]["id"] != widget.events!.id;
}
return false;
}
Future<String> _uploadImage(String imagePath) async {
final params = {
'expiration': '15552000',
'key': dotenv.env["IMGBB_API_KEY"]
};
final url = Uri.parse('https://api.imgbb.com/1/upload')
.replace(queryParameters: params);
final image = File(imagePath);
final req = http.MultipartRequest('POST', url)
..fields['image'] = base64.encode(await image.readAsBytes());
final response = await http.Response.fromStream(await req.send());
return response.statusCode == 200
? json.decode(response.body)["data"]["url"]
: "";
}
Future<void> _updateEventData(
String accessToken, Map<String, dynamic> location) async {
final url = Uri.parse("${globals.api}/events/${widget.events!.id}");
final response = await http.put(url,
headers: {
HttpHeaders.cookieHeader: 'access_token=$accessToken',
HttpHeaders.acceptHeader: 'application/json, text/plain, */*',
HttpHeaders.contentTypeHeader: 'application/json'
},
body: jsonEncode({
'name': inputName.text,
'place': inputGeo.text,
'start_date':
"${formatDate(startDatepicker.text)}T${startTimepicker.text.replaceAll('-', ':')}",
'end_date':
"${formatDate(endDatepicker.text)}T${endTimepicker.text.replaceAll('-', ':')}",
'organizers':
List<String>.from(_stringOrgaController.getTags as List),
'latitude': location['lat'],
'longitude': location['lng'],
'description': inputDesc.text,
"imgUrl": imgUrl,
'link': inputLink.text,
'ticket': inputTicket.text,
"tags": List<String>.from(_stringTagController.getTags as List)
}));
if (response.statusCode != 200 && response.statusCode != 201) {
_handleErrorResponse(context, response.statusCode);
}
}
void _handleErrorResponse(BuildContext context, int statusCode) {
final messages = {
400: AppLocalizations.of(context)?.request_error ??
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
_showErrorDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
messages[statusCode] ??
AppLocalizations.of(context)?.unknown_error_auth ??
"Unknown error auth");
}
void _showErrorDialog(BuildContext context, String title, String message) {
showAlertDialog(context, title, message);
}
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
inputName.text = widget.events!.name ?? "";
inputTicket.text = widget.events!.ticket ?? "";
inputLink.text = widget.events!.link ?? "";
startDatepicker.text = DateFormat("dd/MM/yyyy")
.format(DateTime.parse(
widget.events!.startDate ?? DateTime.now().toString()))
.toString();
startTimepicker.text = DateFormat("HH:mm")
.format(DateTime.parse(
widget.events!.startDate ?? DateTime.now().toString()))
.toString();
endDatepicker.text = DateFormat("dd/MM/yyyy")
.format(
DateTime.parse(widget.events!.endDate ?? DateTime.now().toString()))
.toString();
endTimepicker.text = DateFormat("HH:mm")
.format(
DateTime.parse(widget.events!.endDate ?? DateTime.now().toString()))
.toString();
inputGeo.text = widget.events!.place ?? "";
imgUrl = widget.events!.imgUrl ?? "";
inputDesc.text = widget.events!.description ?? "";
initialTags = List<String>.from(widget.events!.tags as List);
initialOrga = List<String>.from(widget.events!.organizers as List);
}
final _formKey = GlobalKey<FormState>();
String? _validateField(String? value) {
return value!.isEmpty
? AppLocalizations.of(context)?.required_input ?? "Required input"
: null;
}
Future<void> searchSuggestions(String input) async {
await dotenv.load(fileName: ".env"); // Load .env file
final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
final url =
'https://api.mapbox.com/geocoding/v5/mapbox.places/${input}.json?access_token=${mapboxAccessToken}&types=poi,address,place';
var encoded = Uri.encodeFull(url);
final response = await http.get(Uri.parse(encoded));
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
suggestions = (data['features'] as List)
.map((feature) => {
'place_name': feature['place_name'],
'text': feature['text'],
'geometry': feature[
'geometry'], // Include geometry for latitude/longitude
})
.toList();
});
} else {
throw Exception('Failed to load suggestions');
}
}
Padding _buildGeographicalZoneSearchField() {
return Padding(
padding:
const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0),
child: Column(
children: [
TextField(
controller: inputGeo,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)?.location ?? "Location",
border: OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
inputGeo.clear(); // Clear the text field
geographicalZone = ''; // Reset the geographical zone state
suggestions.clear(); // Optionally clear suggestions
});
},
),
),
onChanged: (value) {
setState(() {
geographicalZone = value;
searchSuggestions(value);
});
},
),
if (suggestions.isNotEmpty)
Container(
height: 200,
decoration: BoxDecoration(
border: Border.all(color: Colors.blue),
borderRadius: BorderRadius.circular(8),
),
child: ListView.builder(
shrinkWrap: true,
itemCount: suggestions.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(suggestions[index]['text']),
subtitle: Text(suggestions[index]['place_name']),
onTap: () async {
print("suggestion tapped : ${suggestions[index]}");
final latitude =
suggestions[index]['geometry']['coordinates'][1];
final longitude =
suggestions[index]['geometry']['coordinates'][0];
setState(() {
geographicalZone = suggestions[index]['text'];
inputGeo.text = geographicalZone;
suggestions.clear();
});
},
);
},
),
),
],
),
);
}
Future<void> popCamera() async {
await availableCameras().then((value) => Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
CameraEdit(camera: value.first, events: widget.events))));
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
drawer: MyDrawer(),
appBar: AppBar(
title: Text(AppLocalizations.of(context)?.add_event ??
"Add or Update a event"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!)),
if (widget.imgPath.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 60.0),
child: Center(
child: Container(
width: 200,
height: 150,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(100.0)),
child: Image.file(File(widget.imgPath))),
),
),
if (widget.imgPath.isEmpty)
Padding(
padding: const EdgeInsets.only(top: 60.0),
child: Image.network(
imgUrl,
width: MediaQuery.of(context).size.width *
0.5, // 50% of screen width
height: MediaQuery.of(context).size.height * 0.5,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child; // The image has finished loading
}
return Center(
child: CircularProgressIndicator(),
);
},
errorBuilder: (BuildContext context, Object error,
StackTrace? stackTrace) {
return Center(
child: Icon(Icons.error,
size: MediaQuery.of(context).size.width * 0.1),
);
},
)),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: ElevatedButton.icon(
onPressed: popCamera,
icon: Icon(Icons.edit, size: 16), // Edit icon
label: Text(AppLocalizations.of(context)?.edit_image ??
"Edit pictures"), // Button text
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Button color
foregroundColor: Colors.white, // Text color
padding:
EdgeInsets.symmetric(horizontal: 10, vertical: 5),
),
),
),
Padding(
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputName,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.name ?? "Name",
hintText:
AppLocalizations.of(context)?.edit_event_name ??
"Edit event name"),
),
),
_buildGeographicalZoneSearchField(),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: startDatepicker,
readOnly: true,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.start_date ??
"Start date",
hintText: AppLocalizations.of(context)?.select_date ??
"Click to select a date"),
onTap: () => onTapFunctionDatePicker(
context: context, position: "start")),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: startTimepicker,
readOnly: true,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.start_time ??
"Start time",
hintText: AppLocalizations.of(context)?.select_time ??
"Click to select a time"),
onTap: () => onTapFunctionTimePicker(
context: context, position: "start")),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: endDatepicker,
readOnly: true,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.end_date ??
"End date",
hintText: AppLocalizations.of(context)?.select_time ??
"Click to select a date"),
onTap: () => onTapFunctionDatePicker(
context: context, position: "end")),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: endTimepicker,
readOnly: true,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.end_time ??
"End time",
hintText: AppLocalizations.of(context)?.select_time ??
"Click to select a time"),
onTap: () => onTapFunctionTimePicker(
context: context, position: "end")),
),
Padding(
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
child: TextFormField(
controller: inputLink,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.link ?? "Link",
hintText: AppLocalizations.of(context)?.edit_link ??
"Edit link event"),
),
),
Padding(
//padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
child: TextFormField(
controller: inputTicket,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.ticket ?? "Ticket",
hintText: AppLocalizations.of(context)?.edit_ticket ??
"Edit ticket link"),
),
),
TextFieldTags<String>(
textfieldTagsController: _stringTagController,
initialTags: initialTags,
textSeparators: const [' ', ','],
validator: (String tag) {
if (_stringTagController.getTags!.contains(tag)) {
return AppLocalizations.of(context)?.already_tag ??
"You have already entered this tag";
}
return null;
},
inputFieldBuilder: (context, inputFieldValues) {
return Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
child: TextField(
controller: inputFieldValues.textEditingController,
focusNode: inputFieldValues.focusNode,
onChanged: inputFieldValues.onTagChanged,
onSubmitted: inputFieldValues.onTagSubmitted,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.tag ?? 'Tags',
hintText: inputFieldValues.tags.isNotEmpty
? ''
: AppLocalizations.of(context)?.enter_tag ??
"Enter tag...",
errorText: inputFieldValues.error,
prefixIcon: inputFieldValues.tags.isNotEmpty
? SingleChildScrollView(
controller:
inputFieldValues.tagScrollController,
scrollDirection: Axis.vertical,
child: Padding(
padding: const EdgeInsets.only(
top: 8,
bottom: 8,
left: 8,
),
child: Wrap(
runSpacing: 4.0,
spacing: 4.0,
children: inputFieldValues.tags
.map((String tag) {
return Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(20.0),
),
color: Colors.blue,
),
margin:
const EdgeInsets.symmetric(
horizontal: 5.0),
padding:
const EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 5.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
child: Text(
'$tag',
style: const TextStyle(
color: Colors.white),
),
onTap: () {
//print("$tag selected");
},
),
const SizedBox(width: 4.0),
InkWell(
child: const Icon(
Icons.cancel,
size: 14.0,
color: Color.fromARGB(
255, 233, 233, 233),
),
onTap: () {
inputFieldValues
.onTagRemoved(tag);
},
)
],
),
);
}).toList()),
),
)
: null,
),
),
);
}),
TextFieldTags<String>(
textfieldTagsController: _stringOrgaController,
initialTags: initialOrga,
textSeparators: const [','],
validator: (String tag) {
if (_stringOrgaController.getTags!.contains(tag)) {
return AppLocalizations.of(context)
?.already_organiser ??
"You have already entered this organizer";
}
return null;
},
inputFieldBuilder: (context, inputFieldValues) {
return Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
child: TextField(
controller: inputFieldValues.textEditingController,
focusNode: inputFieldValues.focusNode,
onChanged: inputFieldValues.onTagChanged,
onSubmitted: inputFieldValues.onTagSubmitted,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.organizer ??
"Organizer",
hintText: inputFieldValues.tags.isNotEmpty
? ''
: AppLocalizations.of(context)
?.enter_organizer ??
"Enter a organizer",
errorText: inputFieldValues.error,
prefixIcon: inputFieldValues.tags.isNotEmpty
? SingleChildScrollView(
controller:
inputFieldValues.tagScrollController,
scrollDirection: Axis.vertical,
child: Padding(
padding: const EdgeInsets.only(
top: 8,
bottom: 8,
left: 8,
),
child: Wrap(
runSpacing: 4.0,
spacing: 4.0,
children: inputFieldValues.tags
.map((String tag) {
return Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(20.0),
),
color: Colors.blue,
),
margin:
const EdgeInsets.symmetric(
horizontal: 5.0),
padding:
const EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 5.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
child: Text(
'$tag',
style: const TextStyle(
color: Colors.white),
),
onTap: () {
//print("$tag selected");
},
),
const SizedBox(width: 4.0),
InkWell(
child: const Icon(
Icons.cancel,
size: 14.0,
color: Color.fromARGB(
255, 233, 233, 233),
),
onTap: () {
inputFieldValues
.onTagRemoved(tag);
},
)
],
),
);
}).toList()),
),
)
: null,
),
),
);
}),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextField(
controller: inputDesc,
keyboardType: TextInputType.multiline,
maxLines: 10,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.description ??
'Description',
hintText:
AppLocalizations.of(context)?.describe_event ??
'Describe event'),
),
),
SizedBox(
height: 30,
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_updateEvent(context);
}
},
child: Text(
AppLocalizations.of(context)?.add ?? 'Add',
style: TextStyle(color: Colors.white, fontSize: 25),
),
),
)
],
),
),
));
}
}

View File

@@ -0,0 +1,396 @@
import 'package:covas_mobile/classes/MyDrawer.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'dart:convert';
import 'dart:io';
import '../classes/MyDrawer.dart';
import '../pages/LoginDemo.dart';
import '../classes/alert.dart';
import '../classes/eventAdded.dart';
import '../variable/globals.dart' as globals;
import '../classes/ad_helper.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; // Créé
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: EditProfile(),
);
}
}
class EditProfile extends StatefulWidget {
const EditProfile({super.key});
@override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditProfile>
with ShowAlertDialog, ShowEventDialog {
BannerAd? _bannerAd;
final AuthService _authService = AuthService();
TextEditingController inputUserName = TextEditingController();
TextEditingController inputName = TextEditingController();
TextEditingController inputFirstName = TextEditingController();
TextEditingController inputEmail = TextEditingController();
TextEditingController inputBirth = TextEditingController();
TextEditingController inputPassword = TextEditingController();
TextEditingController inputPasswordConfirmed = TextEditingController();
onTapFunctionDatePicker({required BuildContext context}) async {
DateTime initialDate = DateTime.parse(formatDate(inputBirth.text));
DateTime? pickedDate = await showDatePicker(
context: context,
firstDate: DateTime(1900),
initialDate: initialDate,
lastDate: DateTime(2104));
if (pickedDate == null) return;
inputBirth.text = DateFormat("dd/MM/yyyy").format(pickedDate);
}
convertNulltoEmptyString(var check) {
if (check == null) {
return "";
}
return check;
}
convertNulltoArray(List<String> check) {
if (check == null) {
return [];
}
return check;
}
String formatDate(String date) {
var splitedDate = date.split("/");
var day = splitedDate[0];
var month = splitedDate[1];
var year = splitedDate[2];
return "${year}-${month}-${day}";
}
Future<void> _updateProfile(BuildContext context) async {
var username = inputUserName.text;
var firstName = inputFirstName.text;
var name = inputName.text;
var email = inputEmail.text;
var password = inputPassword.text;
var confirmedPassword = inputPasswordConfirmed.text;
var birth = DateTime.parse(formatDate(inputBirth.text));
if ((password.isNotEmpty) && (confirmedPassword.isNotEmpty)) {
if (password != confirmedPassword) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.different_password_error ??
"Different password");
return;
}
}
var urlPut = Uri.parse("${globals.api}/users/me");
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var responsePut = await http.put(urlPut,
headers: {
HttpHeaders.cookieHeader: 'access_token=${accessToken}',
HttpHeaders.acceptHeader: 'application/json, text/plain, */*',
HttpHeaders.contentTypeHeader: 'application/json'
},
body: jsonEncode({
'name': name,
'username': username,
'firstName': firstName,
'password': password,
'email': email,
'roles': '',
'birth': birth.toString()
}));
print(responsePut.statusCode);
if (responsePut.statusCode == 200) {
showEventDialog(context,
AppLocalizations.of(context)?.user_update ?? "Your user updated");
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (_) => EditProfile()));
return;
}
final messages = {
400: AppLocalizations.of(context)?.request_error ??
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
final text = messages[responsePut.statusCode] ??
AppLocalizations.of(context)?.unknown_error_auth ??
"Unknown error auth";
showAlertDialog(
context, AppLocalizations.of(context)?.error ?? "Error", text);
} else {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (_) => LoginDemo()));
}
}
Future<void> _getInfoProfile() async {
var urlGet = Uri.parse("${globals.api}/users/me");
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var responseGet = await http.get(urlGet, headers: {
HttpHeaders.cookieHeader: 'access_token=${accessToken}',
HttpHeaders.acceptHeader: 'application/json, text/plain, */*',
HttpHeaders.contentTypeHeader: 'application/json'
});
print(responseGet.statusCode);
if (responseGet.statusCode == 200) {
var body = json.decode(utf8.decode(responseGet.bodyBytes));
setState(() {
inputName.text = body["name"];
inputFirstName.text = body["firstName"];
inputUserName.text = body["username"];
inputEmail.text = body["email"];
inputBirth.text =
DateFormat("dd/MM/yyyy").format(DateTime.parse(body["birth"]));
});
return;
}
final messages = {
400: AppLocalizations.of(context)?.request_error ??
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
final text = messages[responseGet.statusCode] ??
AppLocalizations.of(context)?.unknown_error_auth ??
"Unknown error auth";
showAlertDialog(
context, AppLocalizations.of(context)?.error ?? "Error", text);
} else {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (_) => LoginDemo()));
}
}
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
_getInfoProfile();
}
final _formKey = GlobalKey<FormState>();
String? _validateField(String? value) {
return value!.isEmpty
? AppLocalizations.of(context)?.required_input ?? "Required input"
: null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(
AppLocalizations.of(context)?.update_profile ?? "Update profile"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
drawer: MyDrawer(),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!)),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputUserName,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.name,
hintText: AppLocalizations.of(context)?.edit_pseudo ??
"Edit pseudo"),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputPassword,
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.password ??
"Password",
hintText:
AppLocalizations.of(context)?.enter_password ??
"Enter a password"),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputPasswordConfirmed,
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.password_confirmed ??
"Must confirm password",
hintText:
AppLocalizations.of(context)?.password_confirmed ??
"Must confirm password"),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputName,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.last_name ??
"Last name",
hintText:
AppLocalizations.of(context)?.edit_last_name ??
"Edit last name"),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputFirstName,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.first_name ??
"First name",
hintText:
AppLocalizations.of(context)?.edit_first_name ??
"Edit first name"),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputEmail,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.email ?? "Email",
hintText: AppLocalizations.of(context)?.edit_email ??
"Edit email"),
),
),
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputBirth,
readOnly: true,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.birth_date,
hintText: AppLocalizations.of(context)?.edit_birth ??
"Click to select a birth date"),
onTap: () => onTapFunctionDatePicker(context: context)),
),
SizedBox(
height: 30,
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_updateProfile(context);
}
},
child: Text(
AppLocalizations.of(context)?.update_profile ??
"Update profile ",
style: TextStyle(color: Colors.white, fontSize: 25),
),
),
)
],
),
),
));
}
}

View File

@@ -0,0 +1,170 @@
import 'package:covas_mobile/classes/MyDrawer.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
import 'dart:io';
import '../classes/MyDrawer.dart';
import '../classes/alert.dart';
import '../classes/eventAdded.dart';
import '../classes/ad_helper.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; // Créé
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: EditSettings(),
);
}
}
class EditSettings extends StatefulWidget {
const EditSettings({super.key});
@override
_EditProfileState createState() => _EditProfileState();
}
class _EditProfileState extends State<EditSettings>
with ShowAlertDialog, ShowEventDialog {
BannerAd? _bannerAd;
final AuthService _authService = AuthService();
TextEditingController inputUserName = TextEditingController();
int? kilometer;
Future<void> getParameter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
var kilometer = prefs.getDouble("kilometer")?.toInt() ?? null;
});
}
Future<void> setParameter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if (kilometer != null) {
prefs.setDouble("kilometer", kilometer?.toDouble() ?? 50);
showAlertDialog(
context,
AppLocalizations.of(context)?.updated ?? "Updated",
AppLocalizations.of(context)?.settings_updated ?? "Settings updated");
}
}
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
getParameter();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(AppLocalizations.of(context)?.settings ?? "Settings"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
drawer: MyDrawer(),
body: Form(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!)),
Padding(
padding: const EdgeInsets.only(
left: 15.0,
right: 15.0,
top: 15.0,
bottom: 0.0,
),
child: DropdownButtonFormField<int>(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.define_kilometer ??
'Define kilometer',
),
value:
kilometer, // Set the initial selected value here, or leave as `null` if unselected.
items: [
DropdownMenuItem(
value: 5,
child: Text('5km'),
),
DropdownMenuItem(
value: 25,
child: Text('25km'),
),
DropdownMenuItem(
value: 50,
child: Text('50km'),
),
DropdownMenuItem(
value: 75,
child: Text('75km'),
),
DropdownMenuItem(
value: 100,
child: Text('100km'),
),
],
onChanged: (int? newValue) {
// Handle selection
kilometer = newValue;
},
),
),
SizedBox(
height: 30,
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () {},
child: Text(
AppLocalizations.of(context)?.update ?? "Update",
style: TextStyle(color: Colors.white, fontSize: 25),
),
),
)
],
),
),
));
}
}

View File

@@ -0,0 +1,176 @@
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'dart:convert';
import 'dart:io';
import '../main.dart';
import '../classes/alert.dart';
import '../variable/globals.dart' as globals;
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; //
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: PasswordForgot(),
);
}
}
class PasswordForgot extends StatefulWidget {
const PasswordForgot({super.key});
@override
_PasswordForgotState createState() => _PasswordForgotState();
}
class _PasswordForgotState extends State<PasswordForgot> with ShowAlertDialog {
TextEditingController inputEmail = TextEditingController();
convertNulltoEmptyString(var check) {
if (check == null) {
return "";
}
return check;
}
convertNulltoArray(List<String> check) {
if (check == null) {
return [];
}
return check;
}
String formatDate(String date) {
var splitedDate = date.split("/");
var day = splitedDate[0];
var month = splitedDate[1];
var year = splitedDate[2];
return "${year}-${month}-${day}";
}
Future<void> _forgotPassword(BuildContext context) async {
var email = inputEmail.text;
var urlPut = Uri.parse("${globals.api}/password/forgot");
var responsePost = await http.post(urlPut,
headers: {
HttpHeaders.acceptHeader: 'application/json, text/plain, */*',
HttpHeaders.contentTypeHeader: 'application/json'
},
body: jsonEncode({
'email': email,
}));
print(responsePost.statusCode);
if (responsePost.statusCode == 200) {
String message =
AppLocalizations.of(context)?.email_sent ?? "Email has been sent";
showAlertDialog(
context,
AppLocalizations.of(context)?.create ?? "Creation",
"${message} : ${email}");
return;
}
final messages = {
400: AppLocalizations.of(context)?.request_error ??
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
final text = messages[responsePost.statusCode] ??
AppLocalizations.of(context)?.unknown_error_auth ??
"Unknown error auth";
showAlertDialog(
context, AppLocalizations.of(context)?.error ?? "Error", text);
}
@override
void initState() {
super.initState();
}
final _formKey = GlobalKey<FormState>();
String? _validateField(String? value) {
return value!.isEmpty ? 'Champ requis' : null;
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(AppLocalizations.of(context)?.forgot_password ??
"Forgot password"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
left: 15.0, right: 15.0, top: 15, bottom: 0),
//padding: EdgeInsets.symmetric(horizontal: 15),
child: TextFormField(
controller: inputEmail,
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.email ?? 'Email',
hintText: AppLocalizations.of(context)?.enter_email ??
'Enter the email'),
),
),
SizedBox(
height: 30,
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_forgotPassword(context);
}
},
child: Text(
AppLocalizations.of(context)?.send_email ?? 'Send email',
style: TextStyle(color: Colors.white, fontSize: 25),
),
),
)
],
),
),
));
}
}

View File

@@ -14,12 +14,25 @@ import 'package:intl/date_symbol_data_local.dart';
import '../variable/globals.dart' as globals;
import '../classes/events.dart';
import '../classes/MyDrawer.dart';
import 'ListItemMenu.dart';
import 'MapboxPages.dart';
import 'ListItemByOrganizers.dart';
import 'EditEvent.dart';
void main() {
import '../classes/ad_helper.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; //
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
initializeDateFormatting("fr_FR", null).then((_) => (const MyApp()));
}
@@ -66,121 +79,110 @@ class ItemMenu extends StatefulWidget {
State<ItemMenu> createState() => _ItemMenuState();
}
class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
class _ItemMenuState extends State<ItemMenu> with ShowAlertDialog {
BannerAd? _bannerAd;
final AuthService _authService = AuthService();
String listUser = "";
String eventName = "";
String eventStartDate = "";
String eventDescription = "";
String eventTicket = "";
String eventLink = "";
String place = "";
String imgUrl = "";
List<String> tags = [];
List<String> organizers = [];
String id = "";
Events? events;
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
_getEventInfos();
}
Future<void> _getEventInfos() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
String formerName = "";
String formerDate = "";
String formerMap = "";
String formerImage = "";
String formerDesc = "";
List<String> formerTags = [];
List<String> formerOrga = [];
final prefs = await SharedPreferences.getInstance();
final accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var urlGet = Uri.parse("${globals.api}/events/${widget.title}");
var responseGet = await http.get(urlGet,
headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'});
stderr.writeln('Response Get status: ${responseGet.statusCode}');
if (responseGet.statusCode == 200) {
stderr.writeln('Username : ${responseGet.body}');
var events = jsonDecode(utf8.decode(responseGet.bodyBytes));
formerName = events["name"];
formerMap = "${events["place"]}";
formerDesc = events["description"];
formerTags = List<String>.from(events['tags'] as List);
formerOrga = List<String>.from(events['organizers'] as List);
final startDate = DateTime.parse(events["start_date"]);
final date = DateFormat.yMd().format(startDate);
final time = DateFormat.Hm().format(startDate);
final endDate = DateTime.parse(events["end_date"]);
final dateE = DateFormat.yMd().format(endDate);
final timeE = DateFormat.Hm().format(endDate);
if (events["imgUrl"] != null) {
formerImage = events["imgUrl"];
}
formerDate = "${date} ${time} à ${dateE} ${timeE}";
} else {
var text = "";
switch (responseGet.statusCode) {
case 400:
{
text = "Requête mal construite";
}
break;
case 406:
{
text = "Mot de passe incorrect";
}
break;
case 404:
{
text = "Utilisateur inconnu";
}
break;
case 403:
{
text = "Vous n'avez pas l'autorisation de faire cette action";
}
break;
case 410:
{
text = "Token invalide";
}
break;
case 500:
{
text = "Probleme interne du serveur";
}
break;
default:
{
text = "Probleme d'authentification inconnu";
}
break;
}
showErrorDialog(context, text);
}
} else {
showErrorDialog(context, "Cache invalide");
if (accessToken.isEmpty) {
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.invalid_cache ?? "Invalid cache");
return;
}
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
eventName = formerName;
eventStartDate = formerDate;
organizers = formerOrga;
place = formerMap;
imgUrl = formerImage;
eventDescription = formerDesc;
tags = formerTags;
});
final urlGet = Uri.parse("${globals.api}/events/${widget.title}");
final responseGet = await http.get(
urlGet,
headers: {HttpHeaders.cookieHeader: 'access_token=$accessToken'},
);
if (responseGet.statusCode == 200) {
final responseBody = utf8.decode(responseGet.bodyBytes);
events = Events.fromJson(jsonDecode(responseBody));
final locale = Provider.of<LocaleProvider>(context, listen: false)
.locale
?.toString() ??
'en_US';
final startDate =
DateTime.parse(events?.startDate ?? DateTime.now().toString());
//final date = DateFormat.yMd().format(startDate);
//final time = DateFormat.Hm().format(startDate);
final endDate =
DateTime.parse(events?.endDate ?? DateTime.now().toString());
String separator = AppLocalizations.of(context)?.at ?? "at";
final formattedStartDate =
DateFormat("EEEE d MMMM y '${separator}' HH:mm", locale)
.format(startDate);
final formattedEndDate =
DateFormat("EEEE d MMMM y '${separator}' HH:mm", locale)
.format(endDate);
String link = AppLocalizations.of(context)?.to_date ?? "to";
setState(() {
eventName = events?.name ?? "";
eventStartDate = "$formattedStartDate ${link} $formattedEndDate";
organizers = List<String>.from(events?.organizers ?? []);
place = events?.place ?? "";
imgUrl = events?.imgUrl ?? "";
eventLink = events?.link ?? "";
eventTicket = events?.ticket ?? "";
eventDescription = events?.description ?? "";
tags = List<String>.from(events?.tags ?? []);
});
} else {
final messages = {
400: AppLocalizations.of(context)?.request_error ??
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
final errorMessage = messages[responseGet.statusCode] ??
AppLocalizations.of(context)?.unknown_error_auth ??
"Unknown error auth";
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
errorMessage);
}
}
final _formKey = GlobalKey<FormState>();
@@ -194,22 +196,29 @@ class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("${eventName}", overflow: TextOverflow.ellipsis),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (_) => ListItemMenu()));
},
)),
body: SingleChildScrollView(
child: Column(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("${eventName}", overflow: TextOverflow.ellipsis),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (_) => ListItemMenu()));
},
)),
drawer: MyDrawer(),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!)),
Padding(
padding: const EdgeInsets.only(top: 60.0),
child: Image.network(
@@ -237,7 +246,7 @@ class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
Row(children: [
Icon(Icons.event),
Text(
"Date : ",
AppLocalizations.of(context)?.item_date ?? "Date : ",
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold),
)
]),
@@ -251,7 +260,7 @@ class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
Row(children: [
Icon(Icons.explore),
Text(
"Carte : ",
AppLocalizations.of(context)?.item_maps ?? "Maps : ",
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold),
)
]),
@@ -271,10 +280,38 @@ class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
maxLines: 3,
overflow: TextOverflow.ellipsis)))
]),
Row(children: [
Icon(Icons.link),
Text(
AppLocalizations.of(context)?.item_link ?? "Link : ",
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold),
)
]),
Row(
children: [
Flexible(
child:
Text("${eventLink}", style: TextStyle(fontSize: 15.0)))
],
),
Row(children: [
Icon(Icons.add_shopping_cart),
Text(
AppLocalizations.of(context)?.item_ticket ?? "Ticket : ",
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold),
)
]),
Row(
children: [
Flexible(
child: Text("${eventTicket}",
style: TextStyle(fontSize: 15.0)))
],
),
Row(children: [
Icon(Icons.group),
Text(
"Organisateurs : ",
AppLocalizations.of(context)?.item_organizer ?? "Organizers : ",
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold),
)
]),
@@ -327,7 +364,9 @@ class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
]),
Row(children: [
Icon(Icons.description),
Text("Description : ",
Text(
AppLocalizations.of(context)?.item_description ??
"Description : ",
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold))
]),
Row(children: [
@@ -339,7 +378,7 @@ class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
]),
Row(children: [
Icon(Icons.category),
Text("Tags : ",
Text(AppLocalizations.of(context)?.item_tags ?? "Tags : ",
style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold))
]),
Row(
@@ -394,6 +433,23 @@ class _ItemMenuState extends State<ItemMenu> with ShowErrorDialog {
],
)
],
)));
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => EditEvent(
events: events,
imgPath: "",
)),
);
},
backgroundColor: Colors.blue,
tooltip: AppLocalizations.of(context)?.search ?? 'Search',
child: const Icon(Icons.edit, color: Colors.white),
),
);
}
}

View File

@@ -10,10 +10,23 @@ import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
import '../variable/globals.dart' as globals;
import '../classes/MyDrawer.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; //
import '../classes/notification_service.dart';
// app starting point
void main() {
initializeDateFormatting("fr_FR", null).then((_) => (const MyApp()));
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeDateFormatting("fr_FR", null);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@@ -22,6 +35,14 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('fr', 'FR'),
],
home: const ListItemOrganizers(organizer: "default"),
debugShowCheckedModeBanner: false,
);
@@ -40,15 +61,78 @@ class ListItemOrganizers extends StatefulWidget {
// homepage state
class _MyHomePageState extends State<ListItemOrganizers> {
int _fetchCount = 0;
bool _isLoading = false;
late ScrollController _scrollController;
final AuthService _authService = AuthService();
void _incrementFetchCount() {
setState(() {
_fetchCount++;
});
}
void _scrollListener() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_incrementFetchCount();
_fetchData();
}
}
Future<void> _fetchData() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
await Future.delayed(Duration(seconds: 2));
getPosts(widget.organizer, count: _fetchCount);
setState(() {
_isLoading = false;
});
}
Future<Map<String, dynamic>> toggleInterested(String eventId) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
final url = Uri.parse("${globals.api}/events/${eventId}/interest");
if (accessToken.isNotEmpty) {
final response = await http.post(
url,
headers: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=$accessToken"
},
);
if (response.statusCode != 200) {
throw (AppLocalizations.of(context)?.toogle_interest ??
"Error toogle interest: ${response.statusCode}");
}
var event = json.decode(response.body);
return event;
}
return {"interested": false, "interested_count": 0};
}
// variable to call and store 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 {
await initializeDateFormatting("fr_FR", null);
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
final List<Events> body = [];
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: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=${accessToken}"
@@ -59,10 +143,25 @@ class _MyHomePageState extends State<ListItemOrganizers> {
return body;
}
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
// build function
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: MyDrawer(),
body: Center(
// FutureBuilder
child: FutureBuilder<List<Events>>(
@@ -77,7 +176,8 @@ class _MyHomePageState extends State<ListItemOrganizers> {
return buildPosts(posts);
} else {
// if no data, show simple Text
return const Text("No data available");
return Text(
AppLocalizations.of(context)?.no_data ?? "No data available");
}
},
),
@@ -87,27 +187,78 @@ class _MyHomePageState extends State<ListItemOrganizers> {
// function to display fetched data on screen
Widget buildPosts(List<Events> posts) {
String organizer =
AppLocalizations.of(context)?.item_organizer ?? "Organizer : ";
// ListView Builder to show data in a list
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("Organisateur : ${widget.organizer}",
title: Text("${organizer}${widget.organizer}",
overflow: TextOverflow.ellipsis),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
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) {
final post = posts[index];
final startDate = DateTime.parse(post.startDate!);
final date = DateFormat.yMd().format(startDate);
final time = DateFormat.Hm().format(startDate);
final locale = Provider.of<LocaleProvider>(context, listen: false)
.locale
?.toString() ??
'en_US';
final dateLongue =
DateFormat('EEEE d MMMM y', locale).format(startDate);
final countInterestedString =
AppLocalizations.of(context)?.count_interested ??
"Interested people number";
final countInterested =
"${countInterestedString} : ${post.interestedCount}";
return ListTile(
title: Text('${post.name!}'),
subtitle: Text('${post.place!}\n${date} ${time}'),
subtitle:
Text('${post.place!}\n${dateLongue}\n${countInterested}'),
trailing: IconButton(
onPressed: () async {
try {
final result = await toggleInterested(post.id!);
setState(() {
post.interested = result["interested"];
post.interestedCount = result["interested_count"];
});
if (result["interested"] == true) {
NotificationService.scheduleEventNotification(
eventId: post.id!,
title: "Rappel évènement",
body:
"Ton évènement '${post.name}' commence dans 1 heure !",
eventDate: DateTime.parse(post.startDate!),
);
} else {
NotificationService.cancel(post.id!);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)?.error_update ??
"Error when updating")),
);
}
},
icon: Icon(
post.interested ?? false
? Icons.favorite
: Icons.favorite_border,
color:
post.interested ?? false ? Colors.red : Colors.grey)),
onTap: () {
Navigator.push(
context,

View File

@@ -11,9 +11,23 @@ import 'package:intl/date_symbol_data_local.dart';
import '../variable/globals.dart' as globals;
import '../classes/MyDrawer.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; //
import '../classes/notification_service.dart';
// app starting point
void main() {
initializeDateFormatting("fr_FR", null).then((_) => (const MyApp()));
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeDateFormatting("fr_FR", null);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
@@ -22,6 +36,14 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('fr', 'FR'),
],
home: const ListItemTags(tags: "default"),
debugShowCheckedModeBanner: false,
);
@@ -41,13 +63,55 @@ class ListItemTags extends StatefulWidget {
class _MyHomePageState extends State<ListItemTags> {
// variable to call and store future list of posts
int _fetchCount = 0;
bool _isLoading = false;
late ScrollController _scrollController;
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
static Future<List<Events>> getPosts(tags) async {
static Future<List<Events>> getPosts(tags, {count = 0}) async {
await initializeDateFormatting("fr_FR", null);
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
final List<Events> body = [];
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: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=${accessToken}"
@@ -58,10 +122,50 @@ class _MyHomePageState extends State<ListItemTags> {
return body;
}
Future<Map<String, dynamic>> toggleInterested(String eventId) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
final url = Uri.parse("${globals.api}/events/${eventId}/interest");
if (accessToken.isNotEmpty) {
final response = await http.post(
url,
headers: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=$accessToken"
},
);
if (response.statusCode != 200) {
throw (AppLocalizations.of(context)?.toogle_interest ??
"Error toogle interest: ${response.statusCode}");
}
var event = json.decode(response.body);
return event;
}
return {"interested": false, "interested_count": 0};
}
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
_authService.checkTokenStatus(context);
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
// build function
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: MyDrawer(),
body: Center(
// FutureBuilder
child: FutureBuilder<List<Events>>(
@@ -76,7 +180,8 @@ class _MyHomePageState extends State<ListItemTags> {
return buildPosts(posts);
} else {
// if no data, show simple Text
return const Text("No data available");
return Text(
AppLocalizations.of(context)?.no_data ?? "No data available");
}
},
),
@@ -87,25 +192,75 @@ class _MyHomePageState extends State<ListItemTags> {
// function to display fetched data on screen
Widget buildPosts(List<Events> posts) {
// ListView Builder to show data in a list
String tag = AppLocalizations.of(context)?.item_tags ?? "Tags : ";
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text("Tags : ${widget.tags}", overflow: TextOverflow.ellipsis),
title: Text("${tag}${widget.tags}", overflow: TextOverflow.ellipsis),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
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) {
final post = posts[index];
final startDate = DateTime.parse(post.startDate!);
final date = DateFormat.yMd().format(startDate);
final time = DateFormat.Hm().format(startDate);
final locale = Provider.of<LocaleProvider>(context, listen: false)
.locale
?.toString() ??
'en_US';
final dateLongue =
DateFormat('EEEE d MMMM y', locale).format(startDate);
final countInterestedString =
AppLocalizations.of(context)?.count_interested ??
"Interested people number";
final countInterested =
"${countInterestedString} : ${post.interestedCount}";
return ListTile(
title: Text('${post.name!}'),
subtitle: Text('${post.place!}\n${date} ${time}'),
subtitle:
Text('${post.place!}\n${dateLongue}\n${countInterested}'),
trailing: IconButton(
onPressed: () async {
try {
final result = await toggleInterested(post.id!);
setState(() {
post.interested = result["interested"];
post.interestedCount = result["interested_count"];
});
if (result["interested"] == true) {
NotificationService.scheduleEventNotification(
eventId: post.id!,
title: "Rappel évènement",
body:
"Ton évènement '${post.name}' commence dans 1 heure !",
eventDate: DateTime.parse(post.startDate!),
);
} else {
NotificationService.cancel(post.id!);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)?.error_update ??
"Error when updating")),
);
}
},
icon: Icon(
post.interested ?? false
? Icons.favorite
: Icons.favorite_border,
color:
post.interested ?? false ? Colors.red : Colors.grey)),
onTap: () {
Navigator.push(
context,

View File

@@ -5,6 +5,7 @@ import 'dart:convert';
import 'dart:io';
import 'ItemMenu.dart';
import '../classes/events.dart';
import '../classes/MyDrawer.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart';
import 'package:intl/date_symbol_data_local.dart';
@@ -15,8 +16,25 @@ import 'package:permission_handler/permission_handler.dart';
import "Camera.dart";
import 'package:camera/camera.dart';
void main() {
initializeDateFormatting("fr_FR", null).then((_) => runApp(const MyApp()));
import '../classes/ad_helper.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; // Créé plus loin
import '../classes/notification_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
await initializeDateFormatting("fr_FR", null);
runApp(ChangeNotifierProvider(
create: (_) => LocaleProvider(),
child: const MyApp(),
));
}
class MyApp extends StatelessWidget {
@@ -24,8 +42,17 @@ class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final localeProvider = Provider.of<LocaleProvider>(context);
return MaterialApp(
home: const ListItemMenu(),
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [const Locale('fr', 'FR'), const Locale('en')],
locale: localeProvider.locale,
home: Builder(builder: (context) => ListItemMenu()),
debugShowCheckedModeBanner: false,
);
}
@@ -39,20 +66,35 @@ class ListItemMenu extends StatefulWidget {
}
class _MyHomePageState extends State<ListItemMenu> {
BannerAd? _bannerAd;
final AuthService _authService = AuthService();
late ScrollController _scrollController;
int _fetchCount = 0;
bool _isLoading = false;
Future<List<Events>> postsFuture = getPosts();
List<Events> filteredPosts = [];
String geographicalZone = '';
String itemName = '';
String itemTags = '';
String query = '';
List<Map<String, dynamic>> suggestions = [];
List<Map<String, dynamic>> suggestionsGeo = [];
List<Map<String, dynamic>> suggestionsItem = [];
List<Map<String, dynamic>> suggestionsTags = [];
TextEditingController inputGeo = TextEditingController();
TextEditingController startDatepicker = TextEditingController();
TextEditingController endDatepicker = TextEditingController();
TextEditingController inputItem = TextEditingController();
TextEditingController inputTags = TextEditingController();
bool showDateFields = false; // State to toggle date fields
bool showArrow = true;
bool showInputSearch = true;
bool showInputGeo = true;
bool showInputTag = true;
// Fetching events from API
static Future<List<Events>> getPosts() async {
await initializeDateFormatting("fr_FR");
PermissionStatus status = await Permission.location.status;
final List<Events> body = [];
var url = Uri.parse("${globals.api}/events");
@@ -84,9 +126,11 @@ class _MyHomePageState extends State<ListItemMenu> {
print('No last known position available.');
}
}
SharedPreferences prefs = await SharedPreferences.getInstance();
if (position != null) {
// Calculate the boundaries
double radiusInKm = 50;
double radiusInKm = prefs.getDouble("kilometer") ?? 50.0;
double latDistance = radiusInKm / 111.0;
double lonDistance =
radiusInKm / (111.0 * cos(position.latitude * pi / 180));
@@ -100,7 +144,7 @@ class _MyHomePageState extends State<ListItemMenu> {
"?min_lat=$minLat&max_lat=$maxLat"
"&min_lon=$minLon&max_lon=$maxLon&current_datetime=${currentDatetime.toString()}");
}
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
@@ -124,13 +168,59 @@ class _MyHomePageState extends State<ListItemMenu> {
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
void initState() {
super.initState();
_authService.checkTokenStatus(context);
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
// Initialize data fetch when the page loads
_getCurrentLocation();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
// Get the device's current location
Future<void> _getCurrentLocation() async {
PermissionStatus status = await Permission.location.status;
@@ -214,7 +304,6 @@ class _MyHomePageState extends State<ListItemMenu> {
}
} catch (e) {
fetchPostsByLocation();
print("Error getting city and country: $e");
}
}
@@ -240,7 +329,7 @@ class _MyHomePageState extends State<ListItemMenu> {
return '';
}
Future<void> searchSuggestions(String input) async {
Future<void> searchSuggestionsGeo(String input) async {
await dotenv.load(fileName: ".env"); // Load .env file
final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
@@ -251,85 +340,186 @@ class _MyHomePageState extends State<ListItemMenu> {
if (response.statusCode == 200) {
final data = json.decode(response.body);
setState(() {
suggestions = (data['features'] as List)
suggestionsGeo = (data['features'] as List)
.map((feature) => {
'place_name': feature['place_name'],
'geometry': feature[
'geometry'], // Include geometry for latitude/longitude
})
.toList();
if (suggestionsGeo.isNotEmpty) {
showArrow = false;
showInputSearch = false;
showInputTag = false;
}
});
} else {
throw Exception('Failed to load suggestions');
throw Exception(AppLocalizations.of(context)?.failed_suggestions ??
'Failed to load suggestions');
}
}
Future<Uri> getUrlForEvents() async {
final prefs = await SharedPreferences.getInstance();
final latitude = prefs.getDouble("city_lat") ?? 0.0;
final longitude = prefs.getDouble("city_long") ?? 0.0;
final radiusInKm = prefs.getDouble("kilometer") ?? 50.0;
String endpoint = "events";
String queryParameters = "";
if (latitude != 0.0 && longitude != 0.0) {
final latDistance = radiusInKm / 111.0;
final lonDistance = radiusInKm / (111.0 * cos(latitude * pi / 180));
final minLat = latitude - latDistance;
final maxLat = latitude + latDistance;
final minLon = longitude - lonDistance;
final maxLon = longitude + lonDistance;
endpoint = "events/search";
queryParameters =
"min_lat=$minLat&max_lat=$maxLat&min_lon=$minLon&max_lon=$maxLon";
}
final currentDate = DateTime.now();
String dateParameter = "current_datetime=${currentDate.toIso8601String()}";
if (startDatepicker.text.isNotEmpty || endDatepicker.text.isNotEmpty) {
endpoint = "events/search";
if (startDatepicker.text.isNotEmpty) {
final startDate = DateTime.parse(formatDate(startDatepicker.text));
dateParameter = "start_date=${startDate.toIso8601String()}";
}
if (endDatepicker.text.isNotEmpty) {
final endDate = DateTime.parse(formatDate(endDatepicker.text));
dateParameter += "&end_date=${endDate.toIso8601String()}";
}
}
if (inputItem.text.isNotEmpty) {
queryParameters += "&item=${inputItem.text}";
}
if (inputTags.text.isNotEmpty) {
queryParameters += "&tags=${inputTags.text}";
}
if (queryParameters.isNotEmpty) {
queryParameters = "$queryParameters&$dateParameter";
} else {
queryParameters = dateParameter;
}
int limit = 20 * (_fetchCount + 1);
return Uri.parse(
"${globals.api}/$endpoint?$queryParameters&limit=${limit}");
}
Future<void> searchSuggestionsByItem(String input) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var url = await getUrlForEvents();
final response = await http.get(url, headers: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=$accessToken"
});
if (response.statusCode == 200) {
final data = json.decode(utf8.decode(response.bodyBytes));
setState(() {
suggestionsItem = (data as List)
.map((feature) => {'name': feature['name']})
.toList();
if (suggestionsItem.isNotEmpty) {
showDateFields = false;
showArrow = false;
}
});
print("status code : ${response.statusCode}");
}
}
}
Future<void> searchSuggestionsByTag(String input) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var url = Uri.parse("${globals.api}/tags?name=${input}");
final response = await http.get(url, headers: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=$accessToken"
});
if (response.statusCode == 200) {
final data = json.decode(utf8.decode(response.bodyBytes));
setState(() {
suggestionsTags = (data as List)
.map((feature) => {'name': feature['name']})
.toList();
if (suggestionsTags.isNotEmpty) {
showInputGeo = false;
showInputSearch = false;
showArrow = false;
}
});
}
}
}
Future<Map<String, dynamic>> toggleInterested(String eventId) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
final url = Uri.parse("${globals.api}/events/${eventId}/interest");
if (accessToken.isNotEmpty) {
final response = await http.post(
url,
headers: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=$accessToken"
},
);
if (response.statusCode != 200) {
throw (AppLocalizations.of(context)?.toogle_interest ??
"Error toogle interest: ${response.statusCode}");
}
var event = json.decode(response.body);
return event;
}
return {"interested": false, "interested_count": 0};
}
Future<void> fetchPostsByLocation() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
double latitude = prefs.getDouble("city_lat") ?? 0.0;
double longitude = prefs.getDouble("city_long") ?? 0.0;
String stringParameter = "";
String dateParameter = "current_datetime";
if ((latitude != 0.0) && (longitude != 0.0)) {
// Calculate the boundaries
double radiusInKm = 50;
double latDistance = radiusInKm / 111.0;
double lonDistance = radiusInKm / (111.0 * cos(latitude * pi / 180));
double minLat = latitude - latDistance;
double maxLat = latitude + latDistance;
double minLon = longitude - lonDistance;
double maxLon = longitude + lonDistance;
stringParameter = "min_lat=$minLat&max_lat=$maxLat"
"&min_lon=$minLon&max_lon=$maxLon";
}
DateTime currentDate = DateTime.now();
dateParameter = "&current_datetime=${currentDate.toString()}";
if (startDatepicker.text.isNotEmpty) {
var date = DateTime.parse(formatDate(startDatepicker.text));
dateParameter = "&date_event=" + date.toString();
}
if (endDatepicker.text.isNotEmpty) {
var date = DateTime.parse(formatDate(endDatepicker.text));
dateParameter = "&date_event=" + date.toString();
}
if ((startDatepicker.text.isNotEmpty) &&
(endDatepicker.text.isNotEmpty)) {
var startDate = DateTime.parse(formatDate(startDatepicker.text));
var endDate = DateTime.parse(formatDate(endDatepicker.text));
dateParameter = "&start_date=" +
startDate.toString() +
"&end_date=" +
endDate.toString();
}
stringParameter = stringParameter + dateParameter;
if (inputItem.text.isNotEmpty) {
stringParameter = stringParameter + "&item=${inputItem.text}";
}
print("stringParameter : ${stringParameter}");
var url = Uri.parse("${globals.api}/events/search?${stringParameter}");
var url = await getUrlForEvents();
final response = await http.get(url, headers: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=$accessToken"
});
print("status code : ${response.statusCode}");
if (response.statusCode == 200) {
final List<dynamic> body = json.decode(utf8.decode(response.bodyBytes));
print("results fetch : ${body}");
print("fetch count : ${_fetchCount}");
// Update state after getting the response
setState(() {
if (body.isNotEmpty) {
// If we have results, map them to Events
filteredPosts = body
.map((e) => Events.fromJson(e as Map<String, dynamic>))
.toList();
} else {
// If no results, clear filteredPosts
filteredPosts.clear();
int counter = filteredPosts.length;
// If we have results, map them to Events
filteredPosts = body
.map((e) => Events.fromJson(e as Map<String, dynamic>))
.toList();
if (counter == filteredPosts.length) {
_fetchCount--;
}
});
} else {
@@ -359,10 +549,10 @@ class _MyHomePageState extends State<ListItemMenu> {
Padding _buildDateField(String position) {
TextEditingController datePicker = startDatepicker;
String hintText = "Date de début";
String hintText = AppLocalizations.of(context)?.start_date ?? "Start date";
if (position == "end") {
datePicker = endDatepicker;
hintText = "Date de fin";
hintText = AppLocalizations.of(context)?.end_date ?? "End date";
}
return Padding(
padding: const EdgeInsets.all(8.0),
@@ -372,55 +562,48 @@ class _MyHomePageState extends State<ListItemMenu> {
readOnly: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () async {
setState(() {
datePicker.text = '';
});
fetchPostsByLocation();
},
),
suffixIcon: datePicker.text.isEmpty
? null
: IconButton(
icon: const Icon(Icons.clear),
onPressed: () async {
setState(() {
datePicker.text = '';
});
fetchPostsByLocation();
},
),
hintText: hintText),
onTap: () =>
onTapFunctionDatePicker(context: context, position: position)),
);
}
Padding _buildGeographicalZoneSearchField() {
Widget _buildSearchField({
required TextEditingController controller,
required String labelText,
required Function(String) onChanged,
required Function() onClear,
required List<Map<String, dynamic>> suggestions,
required Function(Map<String, dynamic>) onSuggestionTap,
}) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
controller: inputGeo,
controller: controller,
decoration: InputDecoration(
labelText: 'Search by geographical zone',
labelText: labelText,
border: OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () async {
SharedPreferences prefs =
await SharedPreferences.getInstance();
prefs.remove("city_lat");
prefs.remove("city_long");
setState(() {
inputGeo.clear(); // Clear the text field
geographicalZone = ''; // Reset the geographical zone state
suggestions.clear(); // Optionally clear suggestions
fetchPostsByLocation();
/// Clear the filtered posts
});
},
),
suffixIcon: controller.text.isEmpty
? null
: IconButton(
icon: const Icon(Icons.clear),
onPressed: () => onClear(),
),
),
onChanged: (value) {
setState(() {
geographicalZone = value;
searchSuggestions(value);
});
},
onChanged: onChanged,
),
if (suggestions.isNotEmpty)
Container(
@@ -433,25 +616,11 @@ class _MyHomePageState extends State<ListItemMenu> {
shrinkWrap: true,
itemCount: suggestions.length,
itemBuilder: (context, index) {
final suggestion = suggestions[index];
return ListTile(
title: Text(suggestions[index]['place_name']),
onTap: () async {
final latitude =
suggestions[index]['geometry']['coordinates'][1];
final longitude =
suggestions[index]['geometry']['coordinates'][0];
setState(() {
geographicalZone = suggestions[index]['place_name'];
inputGeo.text = geographicalZone;
suggestions.clear();
});
SharedPreferences prefs =
await SharedPreferences.getInstance();
prefs.setDouble("city_lat", latitude);
prefs.setDouble("city_long", longitude);
await fetchPostsByLocation();
},
title: Text(
suggestion['name'] ?? suggestion['place_name'] ?? ''),
onTap: () => onSuggestionTap(suggestion),
);
},
),
@@ -461,35 +630,6 @@ class _MyHomePageState extends State<ListItemMenu> {
);
}
Padding _buildItemZoneSearchField() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
TextField(
controller: inputItem,
decoration: InputDecoration(
labelText: 'Search by item',
border: OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
inputItem.clear();
});
fetchPostsByLocation();
},
),
),
onSubmitted: (value) {
fetchPostsByLocation();
},
),
],
),
);
}
Future<void> popCamera() async {
await availableCameras().then((value) => Navigator.push(context,
MaterialPageRoute(builder: (_) => Camera(camera: value.first))));
@@ -497,45 +637,216 @@ class _MyHomePageState extends State<ListItemMenu> {
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
final localeProvider = Provider.of<LocaleProvider>(context);
return Scaffold(
appBar: AppBar(
title: const Text("Item list menu"),
title: Text(loc?.menu_list ?? "Item list menu"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
drawer: MyDrawer(),
body: Column(
children: [
_buildItemZoneSearchField(),
if (showDateFields) _buildGeographicalZoneSearchField(),
if (showDateFields) _buildDateField("start"),
if (showDateFields) _buildDateField("end"),
IconButton(
onPressed: () {
setState(() {
showDateFields = !showDateFields; // Toggle visibility
});
},
icon: Icon(
showDateFields
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Colors.blue,
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!),
),
if (showInputSearch)
_buildSearchField(
controller: inputItem,
labelText: loc?.search_item ?? "Search by item",
onChanged: (value) {
_fetchCount = 0;
if (value.isNotEmpty) {
setState(() {
itemName = value;
searchSuggestionsByItem(value);
});
} else {
setState(() {
inputItem.clear();
itemName = '';
suggestionsItem.clear();
showDateFields = true;
showArrow = true;
});
fetchPostsByLocation();
}
},
onClear: () {
_fetchCount = 0;
setState(() {
inputItem.clear();
itemName = '';
suggestionsItem.clear();
showDateFields = true;
showArrow = true;
});
fetchPostsByLocation();
},
suggestions: suggestionsItem,
onSuggestionTap: (suggestion) async {
_fetchCount = 0;
setState(() {
itemName = suggestion['name'];
inputItem.text = itemName;
suggestionsItem.clear();
showDateFields = true;
showArrow = true;
});
await fetchPostsByLocation();
},
),
if ((showDateFields) && (showInputTag))
_buildSearchField(
controller: inputTags,
labelText: loc?.search_tag ?? "Search by tags",
onChanged: (value) {
_fetchCount = 0;
if (value.isNotEmpty) {
setState(() {
itemTags = value;
searchSuggestionsByTag(value);
});
} else {
setState(() {
inputTags.clear();
showArrow = true;
showInputSearch = true;
showInputGeo = true;
itemTags = '';
});
fetchPostsByLocation();
}
},
onClear: () {
_fetchCount = 0;
setState(() {
inputTags.clear();
});
fetchPostsByLocation();
},
suggestions: suggestionsTags,
onSuggestionTap: (suggestion) async {
_fetchCount = 0;
setState(() {
itemTags = suggestion['name'];
inputTags.text = itemTags;
suggestionsTags.clear();
showArrow = true;
showInputSearch = true;
showInputGeo = true;
});
await fetchPostsByLocation();
},
),
if ((showDateFields) && (showArrow))
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(child: _buildDateField("start")),
Flexible(child: _buildDateField("end"))
]),
if ((showDateFields) && (showInputGeo))
_buildSearchField(
controller: inputGeo,
labelText:
loc?.search_geographical ?? 'Search by geographical zone',
onChanged: (value) async {
_fetchCount = 0;
if (value.isNotEmpty) {
setState(() {
geographicalZone = value;
searchSuggestionsGeo(value);
});
} else {
final prefs = await SharedPreferences.getInstance();
prefs.remove("city_lat");
prefs.remove("city_long");
setState(() {
inputGeo.clear();
geographicalZone = '';
suggestionsGeo.clear();
showArrow = true;
showInputSearch = true;
showInputTag = true;
});
fetchPostsByLocation();
}
},
onClear: () async {
_fetchCount = 0;
final prefs = await SharedPreferences.getInstance();
prefs.remove("city_lat");
prefs.remove("city_long");
setState(() {
inputGeo.clear();
geographicalZone = '';
suggestionsGeo.clear();
showArrow = true;
showInputSearch = true;
showInputTag = true;
});
fetchPostsByLocation();
},
suggestions: suggestionsGeo,
onSuggestionTap: (suggestion) async {
_fetchCount = 0;
final latitude = suggestion['geometry']['coordinates'][1];
final longitude = suggestion['geometry']['coordinates'][0];
setState(() {
geographicalZone = suggestion['place_name'];
inputGeo.text = geographicalZone;
suggestionsGeo.clear();
showArrow = true;
showInputSearch = true;
showInputTag = true;
});
final prefs = await SharedPreferences.getInstance();
prefs.setDouble("city_lat", latitude);
prefs.setDouble("city_long", longitude);
await fetchPostsByLocation();
},
),
if (showArrow)
IconButton(
onPressed: () {
setState(() {
showDateFields = !showDateFields; // Toggle visibility
});
},
icon: Icon(
showDateFields
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: Colors.blue,
),
tooltip: showDateFields
? loc?.show_date_field ?? 'Show Date Fields'
: loc?.hide_date_field ?? 'Hide Date Fields',
),
tooltip: showDateFields ? 'Show Date Fields' : 'Hide Date Fields',
),
Expanded(
child: FutureBuilder<List<Events>>(
future: postsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasData) {
final posts = snapshot.data!;
final displayedPosts =
filteredPosts.isEmpty ? posts : filteredPosts;
return buildPosts(displayedPosts);
} else {
return const Text("No data available");
return Center(
child: Text(AppLocalizations.of(context)?.no_data ??
"No data available"),
);
}
},
),
@@ -545,7 +856,7 @@ class _MyHomePageState extends State<ListItemMenu> {
floatingActionButton: FloatingActionButton(
onPressed: popCamera,
backgroundColor: Colors.blue,
tooltip: 'Recherche',
tooltip: loc?.search ?? 'Recherche',
child: const Icon(Icons.photo_camera, color: Colors.white),
),
);
@@ -553,28 +864,81 @@ class _MyHomePageState extends State<ListItemMenu> {
// Function to display fetched data on screen
Widget buildPosts(List<Events> posts) {
print("posts : ${posts}");
print("filteredposts : ${filteredPosts}");
final displayedPosts = filteredPosts;
print("results ${displayedPosts}");
// If filteredPosts is empty, show a message saying no data is available
if (displayedPosts.isEmpty) {
return const Center(
child: Text('No events available for this location.',
return Center(
child: Text(
AppLocalizations.of(context)?.no_events ??
'No events available for this location.',
style: TextStyle(fontSize: 18, color: Colors.grey)),
);
}
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) {
if (index >= displayedPosts.length) {
return _isLoading
? Center(child: CircularProgressIndicator())
: SizedBox.shrink();
}
final post = displayedPosts[index];
final startDate = DateTime.parse(post.startDate!);
final date = DateFormat.yMd().format(startDate);
final time = DateFormat.Hm().format(startDate);
//final date = DateFormat.yMd().format(startDate);
//final time = DateFormat.Hm().format(startDate);
final locale =
Provider.of<LocaleProvider>(context).locale?.toString() ??
'en_US';
final dateLongue =
DateFormat('EEEE d MMMM y', locale).format(startDate);
final countInterestedString =
AppLocalizations.of(context)?.count_interested ??
"Interested people number";
final countInterested =
"${countInterestedString} : ${post.interestedCount}";
return ListTile(
title: Text('${post.name!}'),
subtitle: Text('${post.place!}\n${date} ${time}'),
subtitle: Text('${post.place!}\n${dateLongue}\n${countInterested}'),
trailing: IconButton(
onPressed: () async {
try {
final result = await toggleInterested(post.id!);
setState(() {
post.interested = result["interested"];
post.interestedCount = result["interested_count"];
});
if (result["interested"] == true) {
NotificationService.scheduleEventNotification(
eventId: post.id!,
title: "Rappel évènement",
body:
"Ton évènement '${post.name}' commence dans 1 heure !",
eventDate: DateTime.parse(post.startDate!),
);
} else {
NotificationService.cancel(post.id!);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context)?.error_update ??
"Error when updating")),
);
}
},
icon: Icon(
post.interested ?? false
? Icons.favorite
: Icons.favorite_border,
color:
post.interested ?? false ? Colors.red : Colors.grey)),
onTap: () {
Navigator.push(
context,

View File

@@ -0,0 +1,169 @@
import 'package:flutter/material.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:permission_handler/permission_handler.dart';
import '../classes/auth_service.dart';
import '../pages/ListItemMenu.dart';
import '../pages/AddProfile.dart';
import '../pages/ForgotPassword.dart';
import '../classes/alert.dart';
import '../classes/ad_helper.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; //
class LoginDemo extends StatefulWidget {
@override
_LoginDemoState createState() => _LoginDemoState();
}
class _LoginDemoState extends State<LoginDemo> with ShowAlertDialog {
BannerAd? _bannerAd;
TextEditingController inputPseudo = TextEditingController();
TextEditingController inputPassword = TextEditingController();
final AuthService _authService = AuthService();
bool _rememberMe = false;
Future<void> _login(BuildContext context) async {
final pseudo = inputPseudo.text;
final password = inputPassword.text;
if (pseudo.isEmpty || password.isEmpty) {
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.empty_input ?? "Empty input");
return;
}
bool success =
await _authService.login(pseudo, password, rememberMe: _rememberMe);
if (success) {
Navigator.push(
context, MaterialPageRoute(builder: (_) => ListItemMenu()));
} else {
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.failed_auth ?? "Authentication failed");
}
}
@override
void initState() {
super.initState();
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
_checkLocationPermission();
_checkLoginStatus();
}
Future<void> _checkLoginStatus() async {
bool loggedIn = await _authService.isLoggedIn();
if (loggedIn) {
Navigator.push(
context, MaterialPageRoute(builder: (_) => ListItemMenu()));
}
}
Future<void> _checkLocationPermission() async {
PermissionStatus status = await Permission.location.status;
if (!status.isGranted) {
await Permission.location.request();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(AppLocalizations.of(context)?.login_page ?? "Login Page"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15),
child: TextField(
controller: inputPseudo,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: AppLocalizations.of(context)?.pseudo ?? 'Pseudo',
hintText:
AppLocalizations.of(context)?.enter_existing_pseudo ??
'Enter a existing pseudo',
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 15, vertical: 15),
child: TextField(
controller: inputPassword,
obscureText: true,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText:
AppLocalizations.of(context)?.password ?? "Password",
hintText: AppLocalizations.of(context)?.enter_password ??
"Enter the password",
),
),
),
CheckboxListTile(
title: Text(
AppLocalizations.of(context)?.remembr_me ?? "Remember me"),
value: _rememberMe,
onChanged: (newValue) {
setState(() {
_rememberMe = newValue ?? false;
});
},
),
TextButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (_) => PasswordForgot()));
},
child: Text(
AppLocalizations.of(context)?.forgot_password ??
'Forgot Password',
style: TextStyle(color: Colors.blue, fontSize: 15)),
),
Container(
height: 50,
width: 250,
decoration: BoxDecoration(
color: Colors.blue, borderRadius: BorderRadius.circular(20)),
child: TextButton(
onPressed: () => _login(context),
child: Text(AppLocalizations.of(context)?.sign_in ?? 'Sign in',
style: TextStyle(color: Colors.white, fontSize: 25)),
),
),
SizedBox(height: 130),
InkWell(
child: Text(AppLocalizations.of(context)?.new_user ??
'New User? Create Account'),
onTap: () {
Navigator.push(
context, MaterialPageRoute(builder: (_) => AddProfile()));
},
),
],
),
),
);
}
}

View File

@@ -10,6 +10,13 @@ import 'package:geolocator/geolocator.dart'; // For getting the user's location
import '../classes/alert.dart'; // Assuming this contains your error dialog code.
import '../variable/globals.dart' as globals;
import '../classes/MyDrawer.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; //
void main() async {
await dotenv.load(fileName: ".env"); // Load .env file
@@ -42,7 +49,9 @@ class MapboxPages extends StatefulWidget {
State<MapboxPages> createState() => _MapboxPagesState();
}
class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
class _MapboxPagesState extends State<MapboxPages> with ShowAlertDialog {
final AuthService _authService = AuthService();
late MapboxMapController mapController;
late String mapboxAccessToken;
List<LatLng> routeCoordinates = [];
@@ -57,13 +66,19 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
@override
void initState() {
super.initState();
_authService.checkTokenStatus(context);
_getUserLocation();
}
void _initToken() {
mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
if (mapboxAccessToken.isEmpty) {
showErrorDialog(context, "Mapbox Access Token is not available.");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.map_token ??
"Map Access Token is not available.");
}
}
@@ -88,42 +103,40 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
_handleErrorResponse(responseGet.statusCode);
}
} else {
showErrorDialog(context, "Invalid cache.");
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.invalid_cache ?? "Invalid cache.");
}
}
void _handleErrorResponse(int statusCode) {
String text;
switch (statusCode) {
case 400:
text = "Bad Request.";
break;
case 406:
text = "Incorrect Password.";
break;
case 404:
text = "User Not Found.";
break;
case 403:
text = "Action not permitted.";
break;
case 410:
text = "Invalid Token.";
break;
case 500:
text = "Internal Server Error.";
break;
default:
text = "Unknown error.";
}
showErrorDialog(context, text);
final messages = {
400: AppLocalizations.of(context)?.request_error ??
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
final errorMessage = messages[statusCode] ??
AppLocalizations.of(context)?.unknown_error_auth ??
"Unknown error auth";
showAlertDialog(
context, AppLocalizations.of(context)?.error ?? "Error", errorMessage);
}
Future<void> _getUserLocation() async {
try {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
showErrorDialog(context, "Location services are disabled.");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.geo_disabled ??
"Location services are disabled.");
return;
}
@@ -131,14 +144,21 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
showErrorDialog(context, "Location permissions are denied.");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.permission_denied ??
"Location permissions are denied.");
return;
}
}
if (permission == LocationPermission.deniedForever) {
showErrorDialog(context,
"Location permissions are permanently denied. Enable them in settings.");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.enable_permission ??
"Location permissions are permanently denied. Enable them in settings.");
return;
}
const LocationSettings locationSettings = LocationSettings(
@@ -149,17 +169,23 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
locationSettings: locationSettings);
} on LocationServiceDisabledException {
// Handle location services disabled
print('Location services are disabled.');
position = await Geolocator.getLastKnownPosition();
if (position == null) {
print('No last known position available.');
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.no_last_position ??
"No last known position available..");
}
} catch (e) {
// Handle other errors
print('Failed to get location: $e');
position = await Geolocator.getLastKnownPosition();
if (position == null) {
print('No last known position available.');
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.no_last_position ??
"No last known position available");
}
}
if (position != null) {
@@ -171,7 +197,11 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
_initToken();
_getEventInfo();
} catch (e) {
showErrorDialog(context, "Failed to get user location: $e");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.failed_location ??
"Failed to get user location");
}
}
@@ -191,16 +221,17 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
}).toList();
});
} else {
showErrorDialog(
context, "Failed to fetch the route: ${response.statusCode}");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.failed_fetch ??
"Failed to fetch the route");
}
}
// Called when the map is created
void _onStyleLoaded() async {
// Log the map controller and coordinates
print("Mapbox controller initialized: $mapController");
print("lat - long : $latitude - $longitude");
// Check if the mapController is really initialized
if (mapController != null) {
@@ -220,20 +251,30 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
);
// Debugging symbol options
print("Adding symbol with options: $symbolOptions");
// Add symbol to map
mapController!.addSymbol(symbolOptions);
} else {
print("Error: Invalid coordinates, cannot add symbol.");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.invalid_coordinates_symbol ??
"Error: Invalid coordinates, cannot add symbol.");
}
} catch (e) {
// Handle any exception that occurs when adding the symbol
print("Error when adding symbol: $e");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.error_symbol ??
"Error when adding symbol.");
}
} else {
print(
"Error: MapboxMapController is null at the time of symbol addition");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.error_symbol ??
"Error when adding symbol.");
}
}
@@ -244,8 +285,11 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
currentRouteLine = null;
}
if (!isUserPositionInitialized) {
showErrorDialog(
context, "User position is not yet initialized. Try again.");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.position_not_init ??
"User position is not yet initialized. Try again.");
return;
}
@@ -284,7 +328,11 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
_zoomToFitRoute(routeCoordinates);
}
} else {
showErrorDialog(context, "Invalid coordinates or user position.");
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.invalid_coordinates ??
"Invalid coordinates or user position.");
}
}
@@ -338,7 +386,7 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
children: [
Icon(Icons.directions_walk, color: Colors.blue),
SizedBox(width: 8),
Text('Walking'),
Text(AppLocalizations.of(context)?.walking ?? 'Walking'),
],
),
),
@@ -348,7 +396,7 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
children: [
Icon(Icons.directions_bike, color: Colors.green),
SizedBox(width: 8),
Text('Cycling'),
Text(AppLocalizations.of(context)?.cycling ?? 'Cycling'),
],
),
),
@@ -358,7 +406,7 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
children: [
Icon(Icons.directions_car, color: Colors.red),
SizedBox(width: 8),
Text('Driving'),
Text(AppLocalizations.of(context)?.driving ?? 'Driving'),
],
),
),
@@ -371,6 +419,7 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
)
],
),
drawer: MyDrawer(),
body: Stack(
children: [
isLoading
@@ -392,7 +441,8 @@ class _MapboxPagesState extends State<MapboxPages> with ShowErrorDialog {
child: FloatingActionButton(
onPressed: _drawRouteAndMarkers,
child: Icon(Icons.directions),
tooltip: 'Get Directions and Markers',
tooltip: AppLocalizations.of(context)?.get_direction ??
'Get Directions and Markers',
),
),
],

View File

@@ -1,20 +1,33 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:uuid/uuid.dart';
import 'package:intl/intl.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:textfield_tags/textfield_tags.dart';
import '../classes/MyDrawer.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'ItemMenu.dart';
import '../classes/alert.dart';
import '../classes/eventAdded.dart';
import '../variable/globals.dart' as globals;
void main() {
import '../classes/ad_helper.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import '../classes/auth_service.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart'; //
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await MobileAds.instance.initialize();
runApp(MyApp());
}
@@ -42,7 +55,10 @@ class UpdateeventImage extends StatefulWidget {
}
class _UpdateeventImageState extends State<UpdateeventImage>
with ShowErrorDialog, ShowEventDialog {
with ShowAlertDialog, ShowEventDialog {
BannerAd? _bannerAd;
final AuthService _authService = AuthService();
TextEditingController inputName = TextEditingController();
TextEditingController inputDate = TextEditingController();
@@ -137,7 +153,7 @@ class _UpdateeventImageState extends State<UpdateeventImage>
}
Future<void> _updateEvent(BuildContext context) async {
var url = Uri.parse("${globals.api}/token");
// Gather inputs
var name = inputName.text;
var place = inputGeo.text;
var description = inputDesc.text;
@@ -146,142 +162,163 @@ class _UpdateeventImageState extends State<UpdateeventImage>
List<String>.from(_stringOrgaController.getTags as List);
var startDateFormat = formatDate(startDatepicker.text);
DateTime startDateCompare = DateTime.parse(startDateFormat);
DateTime dateNow = DateTime.now();
var endDateFormat = formatDate(endDatepicker.text);
var startDate =
"${startDateFormat}T${startTimepicker.text.replaceAll('-', ':')}";
var endDate = "${endDateFormat}T${endTimepicker.text.replaceAll('-', ':')}";
print("start date : ${startDate}");
print("end date : ${endDate}");
if (!startDateCompare.isAfter(dateNow)) {
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.no_future_event ?? "No future event");
return;
}
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
try {
await dotenv.load();
final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
print("place non encoded : ${place}");
final url =
'https://api.mapbox.com/geocoding/v5/mapbox.places/${place}.json?access_token=${mapboxAccessToken}&types=poi,address,place';
var encoded = Uri.encodeFull(url);
print("encoded : ${encoded}");
final response = await http.get(Uri.parse(encoded));
if (response.statusCode == 200) {
final data = json.decode(response.body);
print("data : ${data}");
if (data['features'].isNotEmpty) {
place = data['features'][0]['place_name'];
final coordinates = data['features'][0]['geometry']['coordinates'];
final longitude = coordinates[0]; // Longitude
final latitude = coordinates[1]; // Latitude
final params = {
'expiration': '15552000',
'key': dotenv.env["IMGBB_API_KEY"],
};
print("Post Img");
final urlPost = Uri.parse('https://api.imgbb.com/1/upload')
.replace(queryParameters: params);
File image = File(widget.imagePath);
Uint8List _bytes = await image.readAsBytes();
String _base64String = base64.encode(_bytes);
final req = http.MultipartRequest('POST', urlPost)
..fields['image'] = _base64String;
final stream = await req.send();
final res = await http.Response.fromStream(stream);
final status = res.statusCode;
print("code status imgbb ${status}");
if (status == 200) {
var body = json.decode(utf8.decode(res.bodyBytes));
String imgUrl = body["data"]["url"];
//String credentials = "${pseudo}:${password}";
//Codec<String, String> stringToBase64 = utf8.fuse(base64);
//String encoded = stringToBase64.encode(credentials);
var urlPut = Uri.parse("${globals.api}/events");
var responsePut = await http.put(urlPut,
headers: {
HttpHeaders.cookieHeader: 'access_token=${accessToken}',
HttpHeaders.acceptHeader:
'application/json, text/plain, */*',
HttpHeaders.contentTypeHeader: 'application/json'
},
body: jsonEncode({
'name': name,
'place': place,
'start_date': startDate,
'end_date': endDate,
'organizers': organizers,
'latitude': latitude,
'longitude': longitude,
'description': description,
"imgUrl": imgUrl,
"tags": tags
}));
print(responsePut.statusCode);
if ((responsePut.statusCode == 200) ||
(responsePut.statusCode == 201)) {
showEventDialog(context, "Evenement ${name} ajoute");
} else {
var text = "";
switch (responsePut.statusCode) {
case 400:
{
text = "Requête mal construite";
}
break;
case 406:
{
text = "Mot de passe incorrect";
}
break;
case 404:
{
text = "Utilisateur inconnu";
}
break;
case 403:
{
text = "Utilisateur desactive";
}
break;
case 410:
{
text = "Token invalide";
}
break;
case 500:
{
text = "Probleme interne du serveur";
}
break;
default:
{
text = "Probleme d'authentification inconnu";
}
break;
}
showErrorDialog(context, text);
}
} else {
print("imgbb error : ${status}");
}
} else {
showErrorDialog(context, "Aucune donnée geographique");
}
} else {
showErrorDialog(context, "Mapbox non accessible");
}
} catch (e) {
showErrorDialog(context, "${e}");
}
} else {
showErrorDialog(context, "Champ vide");
if (accessToken.isEmpty) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.missing_token ??
"Missing access token");
return;
}
try {
await dotenv.load();
final ApiTokenGoogle = dotenv.env['PLACE_API_KEY'] ?? '';
// Searchbox API for geocoding the place (No session token)
final searchboxUrl = Uri.parse(
'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${place}&key=${ApiTokenGoogle}');
// Perform the request
final searchboxResponse = await http.get(searchboxUrl);
if (searchboxResponse.statusCode != 200) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.geocoding_error ??
"Error when geocoding");
return;
}
final searchboxData = json.decode(searchboxResponse.body);
if (searchboxData['results'].isEmpty) {
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.no_found_place ?? "No found place");
return;
}
// Extract place details from the searchbox response
final firstFeature = searchboxData['results'][0];
place = firstFeature["formatted_address"];
final coordinates = firstFeature['geometry']['location'];
final longitude = coordinates["lng"];
final latitude = coordinates["lat"];
// Check if a similar event exists
final eventsUrl = Uri.parse(
"${globals.api}/events/search?item=$name&date_event=$startDate");
final eventsResponse = await http.get(eventsUrl, headers: {
HttpHeaders.cookieHeader: 'access_token=$accessToken',
});
if (eventsResponse.statusCode == 200) {
final events = json.decode(utf8.decode(eventsResponse.bodyBytes));
if (events.isNotEmpty) {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ItemMenu(title: events[0]["id"]),
),
);
return;
}
}
// Upload image to imgbb
final imgbbUrl = Uri.parse(
'https://api.imgbb.com/1/upload?expiration=15552000&key=${dotenv.env["IMGBB_API_KEY"]}');
File image = File(widget.imagePath);
Uint8List imageBytes = await image.readAsBytes();
String base64Image = base64.encode(imageBytes);
final imgbbRequest = http.MultipartRequest('POST', imgbbUrl)
..fields['image'] = base64Image;
final imgbbResponse =
await http.Response.fromStream(await imgbbRequest.send());
if (imgbbResponse.statusCode != 200) {
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.upload_error ??
"Error when image uploading");
return;
}
final imgbbData = json.decode(imgbbResponse.body);
final imgUrl = imgbbData['data']['url'];
// Create or update the event
final eventUrl = Uri.parse("${globals.api}/events");
final eventResponse = await http.put(
eventUrl,
headers: {
HttpHeaders.cookieHeader: 'access_token=$accessToken',
HttpHeaders.acceptHeader: 'application/json, text/plain, */*',
HttpHeaders.contentTypeHeader: 'application/json',
},
body: jsonEncode({
'name': name,
'place': place,
'start_date': startDate,
'end_date': endDate,
'organizers': organizers,
'latitude': latitude,
'longitude': longitude,
'description': description,
'imgUrl': imgUrl,
'tags': tags,
}),
);
if (eventResponse.statusCode == 200 || eventResponse.statusCode == 201) {
String event_message =
AppLocalizations.of(context)?.event_added ?? "Event added";
showEventDialog(context, "$event_message : $name");
} else {
handleHttpError(eventResponse.statusCode, context);
}
} catch (e) {
showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error",
AppLocalizations.of(context)?.app_error ?? "Error application");
}
}
// Utility function to handle HTTP errors
void handleHttpError(int statusCode, BuildContext context) {
final messages = {
400: AppLocalizations.of(context)?.request_error ??
"Poorly constructed query",
406: AppLocalizations.of(context)?.incorrect_password ??
"Incorrect password",
404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user",
403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user",
410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token",
500: AppLocalizations.of(context)?.internal_error_server ??
"Internal error server"
};
showAlertDialog(
context,
AppLocalizations.of(context)?.error ?? "Error",
messages[statusCode] ??
AppLocalizations.of(context)?.unknown_error ??
"Unknown error");
}
void start() async {
@@ -307,35 +344,46 @@ class _UpdateeventImageState extends State<UpdateeventImage>
@override
void initState() {
start();
super.initState();
_authService.checkTokenStatus(context);
AdHelper.createBannerAd(() => setState(() {})).then((ad) {
setState(() {
_bannerAd = ad;
});
});
start();
}
final _formKey = GlobalKey<FormState>();
String? _validateField(String? value) {
return value!.isEmpty ? 'Champ requis' : null;
return value!.isEmpty
? AppLocalizations.of(context)?.required_input ?? 'Required input'
: null;
}
Future<void> searchSuggestions(String input) async {
await dotenv.load(fileName: ".env"); // Load .env file
final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? '';
final url =
'https://api.mapbox.com/geocoding/v5/mapbox.places/${input}.json?access_token=${mapboxAccessToken}&types=poi,address,place';
var encoded = Uri.encodeFull(url);
final response = await http.get(Uri.parse(encoded));
print("response code suggesttion : ${response.statusCode}");
final ApiTokenGoogle = dotenv.env['PLACE_API_KEY'] ?? '';
// Define the Searchbox API URL
final searchboxUrl = Uri.parse(
'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${input}&key=${ApiTokenGoogle}');
// Perform the request
final response = await http.get(searchboxUrl);
if (response.statusCode == 200) {
final data = json.decode(response.body);
print("data suggestion : ${data}");
setState(() {
suggestions = (data['features'] as List)
// Map the results to extract name and full_address
suggestions = (data['results'] as List)
.map((feature) => {
'place_name': feature['place_name'],
'text': feature['text'],
'geometry': feature[
'geometry'], // Include geometry for latitude/longitude
'name': feature['name'],
'formatted_address': feature[
'formatted_address'] // Adjusted to match the data structure
})
.toList();
});
@@ -353,7 +401,7 @@ class _UpdateeventImageState extends State<UpdateeventImage>
TextField(
controller: inputGeo,
decoration: InputDecoration(
labelText: 'Lieu',
labelText: AppLocalizations.of(context)?.location ?? 'Location',
border: OutlineInputBorder(),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
@@ -385,17 +433,14 @@ class _UpdateeventImageState extends State<UpdateeventImage>
itemCount: suggestions.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(suggestions[index]['text']),
subtitle: Text(suggestions[index]['place_name']),
title: Text(suggestions[index]['name']),
subtitle: Text(suggestions[index]['formatted_address']),
onTap: () async {
print("suggestion tapped : ${suggestions[index]}");
final latitude =
suggestions[index]['geometry']['coordinates'][1];
final longitude =
suggestions[index]['geometry']['coordinates'][0];
setState(() {
geographicalZone = suggestions[index]['text'];
geographicalZone =
suggestions[index]['formatted_address'];
inputGeo.text = geographicalZone;
suggestions.clear();
});
@@ -414,15 +459,23 @@ class _UpdateeventImageState extends State<UpdateeventImage>
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text("Add or Update a event"),
title: Text(AppLocalizations.of(context)?.add_event ??
"Add or Update a event"),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
drawer: MyDrawer(),
body: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
_bannerAd == null
? SizedBox.shrink()
: SizedBox(
height: _bannerAd!.size.height.toDouble(),
width: _bannerAd!.size.width.toDouble(),
child: AdWidget(ad: _bannerAd!)),
Padding(
padding: const EdgeInsets.only(top: 60.0),
child: Center(
@@ -442,8 +495,10 @@ class _UpdateeventImageState extends State<UpdateeventImage>
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Nom',
hintText: 'Modifier le nom de l\'évènement'),
labelText: AppLocalizations.of(context)?.name ?? "Name",
hintText:
AppLocalizations.of(context)?.edit_event_name ??
"Edit event name"),
),
),
_buildGeographicalZoneSearchField(),
@@ -457,8 +512,10 @@ class _UpdateeventImageState extends State<UpdateeventImage>
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Date de debut',
hintText: 'Cliquez ici pour selectionner une date'),
labelText: AppLocalizations.of(context)?.start_date ??
"Start date",
hintText: AppLocalizations.of(context)?.select_date ??
"Click to select a date"),
onTap: () => onTapFunctionDatePicker(
context: context, position: "start")),
),
@@ -472,8 +529,10 @@ class _UpdateeventImageState extends State<UpdateeventImage>
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Heure de debut',
hintText: 'Cliquez ici pour selectionner une heure'),
labelText: AppLocalizations.of(context)?.start_time ??
"Start time",
hintText: AppLocalizations.of(context)?.select_time ??
"Click to select a time"),
onTap: () => onTapFunctionTimePicker(
context: context, position: "start")),
),
@@ -487,8 +546,10 @@ class _UpdateeventImageState extends State<UpdateeventImage>
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Date de fin',
hintText: 'Cliquez ici pour selectionner une date'),
labelText: AppLocalizations.of(context)?.end_date ??
"End date",
hintText: AppLocalizations.of(context)?.select_date ??
"Click to select a date"),
onTap: () => onTapFunctionDatePicker(
context: context, position: "end")),
),
@@ -502,8 +563,10 @@ class _UpdateeventImageState extends State<UpdateeventImage>
validator: (value) => _validateField(value),
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Heure de fin',
hintText: 'Cliquez ici pour selectionner une heure'),
labelText: AppLocalizations.of(context)?.end_time ??
"End time",
hintText: AppLocalizations.of(context)?.select_time ??
"Click to select a time"),
onTap: () => onTapFunctionTimePicker(
context: context, position: "end")),
),
@@ -513,7 +576,8 @@ class _UpdateeventImageState extends State<UpdateeventImage>
textSeparators: const [' ', ','],
validator: (String tag) {
if (_stringTagController.getTags!.contains(tag)) {
return 'Tu as deja rentre ce tag';
return AppLocalizations.of(context)?.already_tag ??
'You have already entered this tag';
}
return null;
},
@@ -528,10 +592,12 @@ class _UpdateeventImageState extends State<UpdateeventImage>
onSubmitted: inputFieldValues.onTagSubmitted,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Tags',
labelText:
AppLocalizations.of(context)?.tag ?? 'Tags',
hintText: inputFieldValues.tags.isNotEmpty
? ''
: "Enter tag...",
: AppLocalizations.of(context)?.enter_tag ??
"Enter tag...",
errorText: inputFieldValues.error,
prefixIcon: inputFieldValues.tags.isNotEmpty
? SingleChildScrollView(
@@ -608,7 +674,9 @@ class _UpdateeventImageState extends State<UpdateeventImage>
textSeparators: const [','],
validator: (String tag) {
if (_stringOrgaController.getTags!.contains(tag)) {
return 'Cet organisateur est déjà rentré';
return AppLocalizations.of(context)
?.already_organiser ??
'You have already entered this organizer';
}
return null;
},
@@ -623,10 +691,14 @@ class _UpdateeventImageState extends State<UpdateeventImage>
onSubmitted: inputFieldValues.onTagSubmitted,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Organisateurs',
labelText:
AppLocalizations.of(context)?.organizer ??
'Organizers',
hintText: inputFieldValues.tags.isNotEmpty
? ''
: "Enter un organisateur...",
: AppLocalizations.of(context)
?.enter_organizer ??
"Enter un organisateur...",
errorText: inputFieldValues.error,
prefixIcon: inputFieldValues.tags.isNotEmpty
? SingleChildScrollView(
@@ -707,8 +779,11 @@ class _UpdateeventImageState extends State<UpdateeventImage>
maxLines: 10,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Description',
hintText: 'Décrire l\'evènement'),
labelText: AppLocalizations.of(context)?.description ??
'Description',
hintText:
AppLocalizations.of(context)?.describe_event ??
'Describe the event'),
),
),
SizedBox(
@@ -727,7 +802,7 @@ class _UpdateeventImageState extends State<UpdateeventImage>
}
},
child: Text(
'Ajouter',
AppLocalizations.of(context)?.add_event ?? 'Add',
style: TextStyle(color: Colors.white, fontSize: 25),
),
),

View File

@@ -6,15 +6,19 @@ import FlutterMacOS
import Foundation
import file_selector_macos
import flutter_local_notifications
import geolocator_apple
import path_provider_foundation
import shared_preferences_foundation
import url_launcher_macos
import webview_flutter_wkwebview
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin"))
}

View File

@@ -9,6 +9,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -89,6 +105,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
cross_file:
dependency: transitive
description:
@@ -121,6 +145,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.0"
dbus:
dependency: transitive
description:
name: dbus
sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev"
source: hosted
version: "0.7.11"
dio:
dependency: transitive
description:
@@ -137,6 +169,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -230,6 +278,35 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35"
url: "https://pub.dev"
source: hosted
version: "17.2.4"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af
url: "https://pub.dev"
source: hosted
version: "4.0.1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66"
url: "https://pub.dev"
source: hosted
version: "7.2.0"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@@ -304,6 +381,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.3"
google_mobile_ads:
dependency: "direct main"
description:
name: google_mobile_ads
sha256: "0d4a3744b5e8ed1b8be6a1b452d309f811688855a497c6113fc4400f922db603"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
http:
dependency: "direct main"
description:
@@ -512,6 +597,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.6"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
path:
dependency: "direct main"
description:
@@ -640,6 +733,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev"
source: hosted
version: "3.9.1"
provider:
dependency: "direct main"
description:
name: provider
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
shared_preferences:
dependency: "direct main"
description:
@@ -773,6 +882,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.1"
timezone:
dependency: "direct main"
description:
name: timezone
sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
typed_data:
dependency: transitive
description:
@@ -877,6 +994,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
webview_flutter:
dependency: transitive
description:
name: webview_flutter
sha256: "889a0a678e7c793c308c68739996227c9661590605e70b1f6cf6b9a6634f7aec"
url: "https://pub.dev"
source: hosted
version: "4.10.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: "512c26ccc5b8a571fd5d13ec994b7509f142ff6faf85835e243dde3538fdc713"
url: "https://pub.dev"
source: hosted
version: "4.3.2"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: d937581d6e558908d7ae3dc1989c4f87b786891ab47bb9df7de548a151779d8d
url: "https://pub.dev"
source: hosted
version: "2.10.0"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: d183aa3d0fbc1f4d0715ce06c5a44cad636598c3340ae8d359fbc61b4016fb60
url: "https://pub.dev"
source: hosted
version: "3.18.3"
xdg_directories:
dependency: transitive
description:

View File

@@ -29,10 +29,15 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
flutter_local_notifications: ^17.2.0
timezone: ^0.9.4
cupertino_icons: ^1.0.2
http: ^1.2.1
shared_preferences: ^2.2.3
@@ -50,6 +55,9 @@ dependencies:
permission_handler: ^11.3.1
url_launcher: ^6.3.1
mapbox_gl: ^0.16.0
google_mobile_ads: ^5.3.1
encrypt_shared_preferences: ^0.8.8
provider: ^6.1.2 # ou la dernière version
dev_dependencies:
flutter_test:
@@ -67,6 +75,7 @@ dev_dependencies:
# The following section is specific to Flutter packages.
flutter:
generate: true
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in

45
covas_mobile_new/.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "05db9689081f091050f01aed79f04dce0c750154"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: android
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: ios
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: linux
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: macos
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: web
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
- platform: windows
create_revision: 05db9689081f091050f01aed79f04dce0c750154
base_revision: 05db9689081f091050f01aed79f04dce0c750154
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@@ -0,0 +1,16 @@
# covas_mobile_new
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.

View File

@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
covas_mobile_new/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,44 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.covas_mobile_new"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.covas_mobile_new"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,45 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="covas_mobile_new"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package com.example.covas_mobile_new
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View File

@@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")

34
covas_mobile_new/ios/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@@ -0,0 +1,616 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@@ -0,0 +1,13 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Covas Mobile New</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>covas_mobile_new</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

View File

@@ -0,0 +1,209 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'dart:io';
import '../pages/EditProfile.dart';
import '../pages/EditSettings.dart';
import '../pages/ListItemMenu.dart';
import 'alert.dart';
import '../variable/globals.dart' as globals;
import '../pages/LoginDemo.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv
import 'package:encrypt_shared_preferences/provider.dart';
import 'package:provider/provider.dart';
import '../locale_provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class MyDrawer extends StatelessWidget with ShowAlertDialog {
Future<void> logout(BuildContext context) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
if (accessToken.isNotEmpty) {
var url = Uri.parse("${globals.api}/token");
try {
var response = await http.delete(url, headers: {
"Content-Type": "application/json",
HttpHeaders.cookieHeader: "access_token=${accessToken}"
});
print("Status code logout ${response.statusCode}");
if (response.statusCode == 200) {
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(
context,
MaterialPageRoute(builder: (_) => LoginDemo()),
(route) => false, // Remove all previous routes
);
} else {
String errorMessage;
switch (response.statusCode) {
case 400:
errorMessage = "Bad Request: Please check your input.";
break;
case 401:
errorMessage = "Unauthorized: Invalid credentials.";
break;
case 403:
errorMessage = "Forbidden: You don't have permission.";
break;
case 404:
errorMessage = "Not Found: The resource was not found.";
break;
case 500:
errorMessage =
"Server Error: Something went wrong on the server.";
break;
default:
errorMessage = "Unexpected Error: ${response.statusCode}";
break;
}
print(errorMessage);
showAlertDialog(context, "Error", errorMessage);
}
} catch (e) {
print("Error: $e");
showAlertDialog(
context, "Error", "An error occurred. Please try again.");
}
} else {
showAlertDialog(context, "Error", "Invalid token.");
}
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
final localeProvider = Provider.of<LocaleProvider>(context);
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
// Drawer Header
DrawerHeader(
decoration: BoxDecoration(
color: Colors.blue,
),
child: Text(
'Menu',
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
),
),
// Drawer Items
ListTile(
leading: Icon(Icons.home),
title: Text(loc?.home ?? "Home"),
onTap: () {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (_) => ListItemMenu()));
/// Close the drawer
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text(loc?.settings ?? 'Settings'),
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => EditSettings())); // Close the drawer
},
),
ListTile(
leading: Icon(Icons.account_circle),
title: Text(loc?.update_profile ?? 'Update profile'),
onTap: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => EditProfile())); // Close the drawer
},
),
ListTile(
leading: Icon(Icons.language),
title: Text(loc?.language ?? 'Language'),
onTap: () {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text(loc?.select_language ?? 'Select Language'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.flag),
title: Text(loc?.french ?? 'Français'),
onTap: () {
Provider.of<LocaleProvider>(context, listen: false)
.setLocale(const Locale('fr'));
Navigator.of(context).pop();
},
),
ListTile(
leading: Icon(Icons.flag_outlined),
title: Text(loc?.english ?? 'English'),
onTap: () {
Provider.of<LocaleProvider>(context, listen: false)
.setLocale(const Locale('en'));
Navigator.of(context).pop();
},
),
ListTile(
leading: Icon(Icons.flag_outlined),
title: Text(loc?.german ?? 'German'),
onTap: () {
Provider.of<LocaleProvider>(context, listen: false)
.setLocale(const Locale('de'));
Navigator.of(context).pop();
},
),
],
),
),
);
},
),
ListTile(
leading: Icon(Icons.info),
title: Text(loc?.about ?? 'About'),
onTap: () {
showAlertDialog(context, loc?.about ?? 'About',
"Version 0.0.1"); // Close the drawer
},
),
ListTile(
leading: Icon(Icons.logout),
title: Text(loc?.log_out ?? 'Log out'),
onTap: () async {
logout(context);
// Close the drawer
},
),
],
),
);
}
}

View File

@@ -0,0 +1,27 @@
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
class AdHelper {
static Future<BannerAd> createBannerAd(Function setStateCallback) async {
await dotenv.load(fileName: ".env");
final adUnitId = dotenv.env['AD_UNIT_ID'] ?? '';
BannerAd bannerAd = BannerAd(
adUnitId: adUnitId,
size: AdSize.banner,
request: AdRequest(),
listener: BannerAdListener(
onAdLoaded: (ad) {
setStateCallback(() {});
},
onAdFailedToLoad: (ad, error) {
print('Banner Ad failed to load: $error');
ad.dispose();
},
),
);
bannerAd.load();
return bannerAd;
}
}

View File

@@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'events.dart';
import '../variable/globals.dart' as globals;
import 'package:http/http.dart' as http;
import 'dart:io';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
mixin ShowDescImageAdd<T extends StatefulWidget> on State<T> {
Future<void> addEvents(var events) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var accessToken = prefs.getString("access_token") ?? "";
List<String> send = ["toto"];
if (accessToken.isNotEmpty) {
var urlPut = Uri.parse("${globals.api}/events");
print("start date : ${events["start_date"]}");
var responsePut = await http.put(urlPut,
headers: {
HttpHeaders.cookieHeader: 'access_token=${accessToken}',
HttpHeaders.acceptHeader: 'application/json, text/plain, */*',
HttpHeaders.contentTypeHeader: 'application/json'
},
body: jsonEncode({
'name': events["name"],
'place': events["place"],
'start_date': events['date'],
'end_date': events['date'],
'organizers': send,
'latitude': '0.0',
'longitude': '0.0',
}));
print("http put code status : ${responsePut.statusCode}");
print("http put body : ${responsePut.body}");
}
}
void showDescImageAddDialog(BuildContext context, var events) {
// Create AlertDialog
String name = events['name'];
AlertDialog dialog = AlertDialog(
title: Text("Ajouter un evenement"),
content: Text("${name} n'a pas été trouvé. Voulez-vous l'ajouter ? "),
actions: [
TextButton(
child: Text("Annuler"),
onPressed: () {
Navigator.of(context).pop("Yes, Of course!"); // Return value
}),
TextButton(
child: Text("Oui"),
onPressed: () {
addEvents(events); // Return value
}),
],
);
// Call showDialog function to show dialog.
Future futureValue = showDialog(
context: context,
builder: (BuildContext context) {
return dialog;
});
}
}

View File

@@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
mixin ShowAlertDialog {
void showAlertDialog(BuildContext context, String title, String text) {
// Create AlertDialog
AlertDialog dialog = AlertDialog(
title: Text(title),
content: Text(text),
actions: [
ElevatedButton(
child: Text("OK"),
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20),
textStyle:
TextStyle(fontSize: 15, fontWeight: FontWeight.normal)),
onPressed: () {
Navigator.of(context).pop("Yes, Of course!"); // Return value
}),
],
);
// Call showDialog function to show dialog.
Future futureValue = showDialog(
context: context,
builder: (BuildContext context) {
return dialog;
});
}
}

Some files were not shown because too many files have changed in this diff Show More