diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0deed65..28791f8 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + LaunchScreen UIMainStoryboardFile Main + NSFaceIDUsageDescription + Usiamo Face ID per accedere rapidamente e in sicurezza al tuo account YogiBook. UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/lib/screens/lessons_page.dart b/lib/screens/lessons_page.dart index cfffeb5..b77ff5d 100644 --- a/lib/screens/lessons_page.dart +++ b/lib/screens/lessons_page.dart @@ -8,6 +8,7 @@ import 'home_page.dart'; import 'meditation_page.dart'; import '../widgets/app_drawer.dart'; import '../widgets/app_bottom_nav.dart'; +import 'package:url_launcher/url_launcher.dart'; import '../widgets/yogibook_background.dart'; import 'reschedule_page.dart'; import 'orders_page.dart'; @@ -179,40 +180,83 @@ class _LessonsPageState extends State { ), ), - // Bottone "Programma lezioni" (solo se ci sono ticket residui) - if (summary != null && summary!.toSchedule > 0) - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), - child: SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - style: ElevatedButton.styleFrom( - backgroundColor: const Color(0xFF10B981), - padding: const EdgeInsets.symmetric(vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - icon: const Icon(Icons.add, color: Colors.white), - label: Text( - 'Programma lezioni (${summary!.toSchedule} disponibili)', - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.w800, - ), - ), - onPressed: () async { - await Navigator.push( - context, - MaterialPageRoute( - builder: (_) => OrdersPage(token: widget.token), + // Bottoni azione: Programma (condizionale) + Acquista (sempre) + Padding( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Row( + children: [ + if (summary != null && summary!.toSchedule > 0) ...[ + Expanded( + child: ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFF4F46E5), + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), ), - ); - _load(); - }, + icon: const Icon(Icons.add, color: Colors.white), + label: Text( + 'Programma (${summary!.toSchedule})', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w800, + ), + ), + onPressed: () async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (_) => OrdersPage(token: widget.token), + ), + ); + _load(); + }, + ), + ), + const SizedBox(width: 8), + ], + Expanded( + child: ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: const Color(0xFFF59E0B), + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), + ), + ), + icon: const Icon( + Icons.shopping_bag_outlined, + color: Colors.white, + ), + label: const Text( + 'Acquista pacchetti', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w800, + ), + ), + onPressed: () async { + final uri = Uri.parse( + 'https://yogasoul.it/abbonamenti-prezzi-yoga-seregno/', + ); + if (!await launchUrl( + uri, + mode: LaunchMode.externalApplication, + )) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Impossibile aprire il link'), + ), + ); + } + }, + ), ), - ), + ], ), + ), Expanded( child: loading diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/screens/settings_page.dart @@ -0,0 +1 @@ + diff --git a/lib/screens/splash_page.dart b/lib/screens/splash_page.dart index 29c7d74..71f9881 100644 --- a/lib/screens/splash_page.dart +++ b/lib/screens/splash_page.dart @@ -15,12 +15,20 @@ class _SplashPageState extends State { static const Color kRed = Color(0xFFB01E28); Timer? _timer; + int _seconds = 5; // countdown iniziale @override void initState() { super.initState(); - // dopo ~2.5s vai al login - _timer = Timer(const Duration(milliseconds: 5000), _goToLogin); + // countdown 5 → 0, poi vai al login + _timer = Timer.periodic(const Duration(seconds: 1), (t) { + if (!mounted) return; + setState(() => _seconds--); + if (_seconds <= 0) { + t.cancel(); + _goToLogin(); + } + }); } @override @@ -126,20 +134,39 @@ class _SplashPageState extends State { ), ), - // ---- Indicatore caricamento in basso - const Positioned( + // ---- Logo YogiBook + countdown in basso + Positioned( left: 0, right: 0, - bottom: 34, - child: Center( - child: SizedBox( - width: 26, - height: 26, - child: CircularProgressIndicator( - strokeWidth: 2.4, - valueColor: AlwaysStoppedAnimation(Colors.white), + bottom: 30, + child: Column( + children: [ + Image.asset( + 'assets/images/yogibook_logo.png', + height: 40, + fit: BoxFit.contain, ), - ), + const SizedBox(height: 14), + // Countdown numerico dentro un cerchio + Container( + width: 40, + height: 40, + alignment: Alignment.center, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.white.withValues(alpha: 0.25), + border: Border.all(color: Colors.white, width: 1.5), + ), + child: Text( + '$_seconds', + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.w900, + fontSize: 18, + ), + ), + ), + ], ), ), ], diff --git a/lib/services/auth_storage.dart b/lib/services/auth_storage.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/services/auth_storage.dart @@ -0,0 +1 @@ + diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index f3136e6..8804215 100644 --- a/lib/widgets/app_drawer.dart +++ b/lib/widgets/app_drawer.dart @@ -9,6 +9,7 @@ import '../screens/medical_certificates_page.dart'; import '../screens/lessons_page.dart'; import '../screens/orders_detail_page.dart'; import '../screens/account_page.dart'; +import 'package:url_launcher/url_launcher.dart'; class AppDrawer extends StatelessWidget { final String token; @@ -33,6 +34,16 @@ class AppDrawer extends StatelessWidget { return '${ApiConfig.scheme}://${ApiConfig.host}/public/userarea/$raw'; } + Future _openUrl(BuildContext context, String url) async { + final uri = Uri.parse(url); + final ok = await launchUrl(uri, mode: LaunchMode.externalApplication); + if (!ok && context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Impossibile aprire il link.')), + ); + } + } + Future _logout(BuildContext context) async { final ok = await showDialog( context: context, @@ -157,6 +168,29 @@ class AppDrawer extends StatelessWidget { const Divider(height: 1), + ListTile( + leading: const Icon(Icons.privacy_tip_outlined), + title: const Text('Cookie e Privacy Policy'), + onTap: () { + Navigator.of(context).pop(); + _openUrl(context, 'https://yogasoul.it/cookie-policy/'); + }, + ), + + ListTile( + leading: const Icon(Icons.description_outlined), + title: const Text('Condizioni generali'), + onTap: () { + Navigator.of(context).pop(); + _openUrl( + context, + 'https://yogasoul.it/condizioni-generali-di-vendita/', + ); + }, + ), + + const Divider(height: 1), + ListTile( leading: const Icon(Icons.logout), title: const Text('Logout'), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d8a4062..2cffa44 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -17,6 +18,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 04f81f4..aae2b5c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_linux file_selector_linux + flutter_secure_storage_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 793a31b..137af01 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,7 +8,9 @@ import Foundation import audioplayers_darwin import file_picker import file_selector_macos +import flutter_secure_storage_macos import google_sign_in_ios +import local_auth_darwin import path_provider_foundation import shared_preferences_foundation import url_launcher_macos @@ -17,7 +19,9 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AudioplayersDarwinPlugin.register(with: registry.registrar(forPlugin: "AudioplayersDarwinPlugin")) FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) + LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 2f36b05..92eb26e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -219,6 +219,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.33" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" + url: "https://pub.dev" + source: hosted + version: "9.2.4" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709 + url: "https://pub.dev" + source: hosted + version: "3.1.2" flutter_test: dependency: "direct dev" description: flutter @@ -365,6 +413,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" leak_tracker: dependency: transitive description: @@ -397,6 +453,46 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 + url: "https://pub.dev" + source: hosted + version: "1.0.56" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" + url: "https://pub.dev" + source: hosted + version: "1.6.1" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.dev" + source: hosted + version: "1.0.11" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 23ad179..10aa006 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -44,6 +44,8 @@ dependencies: mime: ^2.0.0 provider: ^6.1.5+1 shared_preferences: ^2.2.3 + flutter_secure_storage: ^9.2.2 + local_auth: ^2.3.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 6e77110..ed71399 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,8 @@ #include #include +#include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -15,6 +17,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + LocalAuthPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("LocalAuthPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 804cebd..cc9bca1 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,8 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows file_selector_windows + flutter_secure_storage_windows + local_auth_windows url_launcher_windows )