diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 360feb0..7cf154c 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } android { - namespace = "com.example.yogibook_app" + namespace = "it.yogasoul.yogibook" compileSdk = flutter.compileSdkVersion ndkVersion = flutter.ndkVersion @@ -21,7 +21,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId = "com.example.yogibook_app" + applicationId = "it.yogasoul.yogibook" // 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 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 28791f8..379517b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png index db77bb4..8da004c 100644 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png index 17987b7..54193dc 100644 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 09d4391..ed00ccc 100644 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index d5f1c8d..5bc74fe 100644 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 4d6372e..ba5ee02 100644 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/assets/icon/app_icon.png b/assets/icon/app_icon.png new file mode 100644 index 0000000..7114ec2 Binary files /dev/null and b/assets/icon/app_icon.png differ diff --git a/assets/icon/olcapp_icon.png b/assets/icon/olcapp_icon.png new file mode 100644 index 0000000..52823fd Binary files /dev/null and b/assets/icon/olcapp_icon.png differ diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8b106e5..f31154d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -368,7 +368,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.yogibookApp; + PRODUCT_BUNDLE_IDENTIFIER = it.yogasoul.yogibook; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -384,7 +384,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.yogibookApp.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = it.yogasoul.yogibook.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -401,7 +401,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.yogibookApp.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = it.yogasoul.yogibook.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -416,7 +416,7 @@ CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.example.yogibookApp.RunnerTests; + PRODUCT_BUNDLE_IDENTIFIER = it.yogasoul.yogibook.RunnerTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; @@ -427,7 +427,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -484,7 +484,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; @@ -547,7 +547,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.yogibookApp; + PRODUCT_BUNDLE_IDENTIFIER = it.yogasoul.yogibook; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -569,7 +569,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.yogibookApp; + PRODUCT_BUNDLE_IDENTIFIER = it.yogasoul.yogibook; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index d36b1fa..d0d98aa 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,122 +1 @@ -{ - "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" - } -} +{"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":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"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":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@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"}} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index dc9ada4..e9383c9 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 7353c41..24ff9ee 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 797d452..460ecfe 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index 6ed2d93..f557302 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cd7b00..711031f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index fe73094..d0d0ba4 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index 321773c..398aa9f 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 797d452..460ecfe 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index 502f463..fd3d166 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index 0ec3034..ea94c4d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000..c564cd9 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 0000000..e9b3504 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..a7b00dc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..3be12d0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index 0ec3034..ea94c4d 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index e9f5fea..59852c1 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..8da004c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..5bc74fe Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index 84ac32a..ff9a7e5 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 8953cba..a9d8012 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index 0467bf1..2fd7a26 100644 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/lib/config/app_school.dart b/lib/config/app_school.dart new file mode 100644 index 0000000..f2ce9f4 --- /dev/null +++ b/lib/config/app_school.dart @@ -0,0 +1,12 @@ +import '../models/school.dart'; + +/// Scuola unica dell'app (YogaSoul). +/// Il DB non ha il concetto di "scuola": questo oggetto serve solo alle +/// pagine che mostrano nome/indirizzo. Definito qui una volta sola così +/// login e splash (e chiunque altro) usano lo stesso dato. +const School kYogaSoulSchool = School( + id: 1, + name: 'YogaSoul', + logo: null, + addressFull: 'via Valassina 62/B Seregno - Sala Contesto Yoga', +); diff --git a/lib/models/school.dart b/lib/models/school.dart index 35e45d4..dee95a2 100644 --- a/lib/models/school.dart +++ b/lib/models/school.dart @@ -4,7 +4,12 @@ class School { final String? logo; final String? addressFull; - School({required this.id, required this.name, this.logo, this.addressFull}); + const School({ + required this.id, + required this.name, + this.logo, + this.addressFull, + }); factory School.fromJson(Map j) { return School( diff --git a/lib/screens/login_page.dart b/lib/screens/login_page.dart index 05aabd9..f23076f 100644 --- a/lib/screens/login_page.dart +++ b/lib/screens/login_page.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import '../services/vanguard_api.dart'; +import '../services/auth_storage.dart'; import '../models/school.dart'; +import '../config/app_school.dart'; import 'home_page.dart'; class LoginPage extends StatefulWidget { @@ -20,15 +22,6 @@ class _LoginPageState extends State { String error = ''; bool _obscurePassword = true; - // Scuola unica (YogaSoul). Il DB non ha il concetto di scuola: - // questo oggetto serve solo a soddisfare le pagine che mostrano nome/indirizzo. - static final School _yogaSoulSchool = School( - id: 1, - name: 'YogaSoul', - logo: null, - addressFull: 'via Valassina 62/B Seregno - Sala Contesto Yoga', - ); - @override void dispose() { emailController.dispose(); @@ -48,6 +41,9 @@ class _LoginPageState extends State { password: passwordController.text.trim(), ); + // Se "ricordami" è attivo, salva il nuovo token in cassaforte. + await AuthStorage.instance.saveTokenIfRemember(token); + if (!mounted) return; // Bypass SelectSchoolPage: scuola unica, si va dritti alla Home. @@ -56,7 +52,7 @@ class _LoginPageState extends State { MaterialPageRoute( builder: (_) => HomePage( token: token, - school: _yogaSoulSchool, + school: kYogaSoulSchool, userFirstName: null, ), ), diff --git a/lib/screens/settings_page.dart b/lib/screens/settings_page.dart index 8b13789..10815ac 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -1 +1,238 @@ +import 'package:flutter/material.dart'; +import '../models/school.dart'; +import '../services/auth_storage.dart'; +import '../widgets/yogibook_background.dart'; + +class SettingsPage extends StatefulWidget { + final String token; + final School school; + final String? userFirstName; + + const SettingsPage({ + super.key, + required this.token, + required this.school, + this.userFirstName, + }); + + @override + State createState() => _SettingsPageState(); +} + +class _SettingsPageState extends State { + static const Color kGreen = Color(0xFF10B981); + + final _auth = AuthStorage.instance; + + bool _loading = true; + bool _rememberMe = false; + bool _biometric = false; + bool _biometricAvailableOnDevice = false; + + // evita doppi tap durante il salvataggio + bool _busy = false; + + @override + void initState() { + super.initState(); + _load(); + } + + Future _load() async { + final remember = await _auth.isRememberMeEnabled(); + final bio = await _auth.isBiometricEnabled(); + final canBio = await _auth.canUseBiometrics(); + if (!mounted) return; + setState(() { + _rememberMe = remember; + _biometric = bio; + _biometricAvailableOnDevice = canBio; + _loading = false; + }); + } + + void _snack(String msg) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); + } + + // "Ricordami": se acceso salva SUBITO il token corrente in cassaforte, + // così funziona già da questa sessione (non solo dal prossimo login). + Future _onRememberChanged(bool value) async { + if (_busy) return; + setState(() => _busy = true); + try { + await _auth.setRememberMe(value); + if (value) { + // salva il token che questa pagina ha in mano + await _auth.saveTokenIfRemember(widget.token); + } + // spegnere ricordami spegne anche la biometria (dipendenza) + final bioNow = await _auth.isBiometricEnabled(); + if (!mounted) return; + setState(() { + _rememberMe = value; + _biometric = bioNow; + }); + _snack( + value ? 'Accesso rapido attivato.' : 'Accesso rapido disattivato.', + ); + } finally { + if (mounted) setState(() => _busy = false); + } + } + + Future _onBiometricChanged(bool value) async { + if (_busy) return; + + // non attivabile senza impronte registrate / hardware + if (value && !_biometricAvailableOnDevice) { + _snack( + 'Nessuna impronta o volto registrati sul dispositivo. ' + 'Configurali nelle impostazioni del telefono, poi riprova.', + ); + return; + } + + setState(() => _busy = true); + try { + await _auth.setBiometric(value); + if (!mounted) return; + setState(() => _biometric = value); + _snack( + value + ? 'Sblocco biometrico attivato.' + : 'Sblocco biometrico disattivato.', + ); + } finally { + if (mounted) setState(() => _busy = false); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + centerTitle: true, + title: const Text( + 'Impostazioni', + style: TextStyle(fontWeight: FontWeight.w800, fontSize: 18), + ), + ), + body: YogibookBackground( + child: SafeArea( + top: false, + child: _loading + ? const Center(child: CircularProgressIndicator()) + : ListView( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 24), + children: [ + _sectionTitle('Accesso'), + const SizedBox(height: 10), + _accessCard(), + const SizedBox(height: 16), + _infoNote(), + ], + ), + ), + ), + ); + } + + Widget _sectionTitle(String t) => Text( + t, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w900), + ); + + Widget _accessCard() { + // la biometria è attivabile solo se "ricordami" è ON + final biometricEnabled = _rememberMe; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(18), + boxShadow: const [ + BoxShadow( + blurRadius: 18, + color: Color(0x12000000), + offset: Offset(0, 10), + ), + ], + ), + child: Column( + children: [ + SwitchListTile( + activeColor: kGreen, + value: _rememberMe, + onChanged: _busy ? null : _onRememberChanged, + secondary: const Icon(Icons.vpn_key_rounded), + title: const Text( + 'Ricordami', + style: TextStyle(fontWeight: FontWeight.w800), + ), + subtitle: const Text( + 'Resta connesso su questo dispositivo senza reinserire la password.', + ), + ), + const Divider(height: 1), + SwitchListTile( + activeColor: kGreen, + value: _biometric && biometricEnabled, + onChanged: (_busy || !biometricEnabled) + ? null + : _onBiometricChanged, + secondary: Icon( + Icons.fingerprint_rounded, + color: biometricEnabled ? null : Colors.black26, + ), + title: Text( + 'Sblocco con impronta / volto', + style: TextStyle( + fontWeight: FontWeight.w800, + color: biometricEnabled ? null : Colors.black38, + ), + ), + subtitle: Text( + biometricEnabled + ? 'Chiedi la biometria all\'avvio per accedere.' + : 'Attiva prima "Ricordami" per usare lo sblocco biometrico.', + style: TextStyle(color: biometricEnabled ? null : Colors.black38), + ), + ), + ], + ), + ); + } + + Widget _infoNote() { + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: const Color(0xFFF6F6FB), + borderRadius: BorderRadius.circular(14), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon(Icons.info_outline, size: 20, color: Colors.black45), + const SizedBox(width: 10), + Expanded( + child: Text( + _biometricAvailableOnDevice + ? 'Il token di accesso è salvato in forma cifrata sul dispositivo. ' + 'Con lo sblocco biometrico attivo, viene usato solo dopo il riconoscimento.' + : 'Questo dispositivo non ha impronta o volto configurati: ' + 'lo sblocco biometrico non è disponibile, ma "Ricordami" funziona lo stesso.', + style: const TextStyle(fontSize: 12, color: Colors.black54), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/splash_page.dart b/lib/screens/splash_page.dart index 71f9881..52439eb 100644 --- a/lib/screens/splash_page.dart +++ b/lib/screens/splash_page.dart @@ -1,7 +1,10 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import '../config/app_school.dart'; +import '../services/auth_storage.dart'; import 'login_page.dart'; +import 'home_page.dart'; class SplashPage extends StatefulWidget { const SplashPage({super.key}); @@ -17,16 +20,19 @@ class _SplashPageState extends State { Timer? _timer; int _seconds = 5; // countdown iniziale + // Evita doppie navigazioni: countdown, tap e sblocco possono + // altrimenti innescare più push. + bool _navigated = false; + @override void initState() { super.initState(); - // 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(); + _finishSplash(); } }); } @@ -37,8 +43,45 @@ class _SplashPageState extends State { super.dispose(); } + // Fine dello splash: prova lo sblocco, poi instrada. + Future _finishSplash() async { + if (_navigated) return; + + final outcome = await AuthStorage.instance.tryUnlockAtStartup(); + if (!mounted || _navigated) return; + + switch (outcome.result) { + case UnlockResult.success: + _navigated = true; + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (_) => HomePage( + token: outcome.token!, + school: kYogaSoulSchool, + userFirstName: null, + ), + ), + ); + break; + case UnlockResult.noSavedLogin: + case UnlockResult.biometricFailed: + case UnlockResult.biometricUnavailable: + _goToLogin(); + break; + } + } + + // Tap sullo schermo: salta il countdown ma passa comunque dallo sblocco. + void _onTapSkip() { + if (_navigated) return; + _timer?.cancel(); + _finishSplash(); + } + void _goToLogin() { - if (!mounted) return; + if (_navigated || !mounted) return; + _navigated = true; Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const LoginPage()), @@ -48,9 +91,8 @@ class _SplashPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - // tocca lo schermo per saltare subito al login body: GestureDetector( - onTap: _goToLogin, + onTap: _onTapSkip, child: Stack( fit: StackFit.expand, children: [ @@ -69,7 +111,7 @@ class _SplashPageState extends State { // Logo YogaSoul Image.asset( 'assets/images/yogasoul_logo.png', - height: 90, + height: 72, fit: BoxFit.contain, ), @@ -82,13 +124,11 @@ class _SplashPageState extends State { vertical: 16, ), decoration: BoxDecoration( - // velo bianco semitrasparente: stacca il testo dal cielo color: Colors.white.withOpacity(0.42), borderRadius: BorderRadius.circular(20), ), child: Column( children: [ - // Titolo riga 1 (scuro) const Text( 'Il tuo spazio.', textAlign: TextAlign.center, @@ -99,7 +139,6 @@ class _SplashPageState extends State { color: Color(0xFF1F1F1F), ), ), - // Titolo riga 2 (rosso bordeaux) const Text( 'La tua energia.', textAlign: TextAlign.center, @@ -113,7 +152,6 @@ class _SplashPageState extends State { const SizedBox(height: 14), - // Sottotitolo const Text( 'Ti diamo il benvenuto in YogiBook,\n' 'l’app della scuola YogaSoul — Seregno.\n' @@ -141,13 +179,22 @@ class _SplashPageState extends State { bottom: 30, child: Column( children: [ - Image.asset( - 'assets/images/yogibook_logo.png', - height: 40, - fit: BoxFit.contain, + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 10, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.75), + borderRadius: BorderRadius.circular(16), + ), + child: 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, diff --git a/lib/services/auth_storage.dart b/lib/services/auth_storage.dart index 8b13789..73fb473 100644 --- a/lib/services/auth_storage.dart +++ b/lib/services/auth_storage.dart @@ -1 +1,167 @@ +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:local_auth/local_auth.dart'; +import 'package:local_auth/error_codes.dart' as auth_error; +import 'package:flutter/services.dart'; +/// Esito dello sblocco all'avvio. +enum UnlockResult { + /// Nessun login salvato: mostra la schermata di login normale. + noSavedLogin, + + /// Token disponibile e pronto all'uso: entra diretto. + success, + + /// C'è un login salvato ma la biometria è fallita/annullata: + /// resta sul login, l'utente può accedere con password. + biometricFailed, + + /// Biometria richiesta ma non disponibile (niente impronte / hardware): + /// fallback a login con password (scelta B). + biometricUnavailable, +} + +/// Custode unico di token + preferenze di accesso (ricordami + biometria). +/// +/// Non è un provider: lo si interroga in momenti precisi +/// (avvio nello splash, login, logout). Vedi nota architetturale nel piano. +class AuthStorage { + AuthStorage._(); + static final AuthStorage instance = AuthStorage._(); + + // Su Android forza l'uso di EncryptedSharedPreferences (più robusto). + static const _secure = FlutterSecureStorage( + aOptions: AndroidOptions(encryptedSharedPreferences: true), + ); + + final LocalAuthentication _localAuth = LocalAuthentication(); + + // Chiavi interne della cassaforte. + static const _kToken = 'auth_token'; + static const _kRemember = 'pref_remember_me'; + static const _kBiometric = 'pref_biometric'; + + // --------------------------------------------------------------------------- + // PREFERENZE + // --------------------------------------------------------------------------- + + /// "Ricordami" attivo? (default: false) + Future isRememberMeEnabled() async { + return (await _secure.read(key: _kRemember)) == 'true'; + } + + /// Biometria attiva? (default: false) + Future isBiometricEnabled() async { + return (await _secure.read(key: _kBiometric)) == 'true'; + } + + /// Imposta "ricordami". Se lo spegni, cancella anche il token salvato: + /// senza ricordami non ha senso tenere un token in cassaforte. + Future setRememberMe(bool value) async { + await _secure.write(key: _kRemember, value: value ? 'true' : 'false'); + if (!value) { + await _secure.delete(key: _kToken); + // Biometria senza token salvato non ha nulla da proteggere: spegnila. + await _secure.write(key: _kBiometric, value: 'false'); + } + } + + /// Imposta la biometria. Ha senso solo se "ricordami" è attivo. + Future setBiometric(bool value) async { + await _secure.write(key: _kBiometric, value: value ? 'true' : 'false'); + } + + // --------------------------------------------------------------------------- + // TOKEN + // --------------------------------------------------------------------------- + + /// Salva il token SOLO se "ricordami" è attivo. Chiamato dopo il login. + Future saveTokenIfRemember(String token) async { + if (await isRememberMeEnabled()) { + await _secure.write(key: _kToken, value: token); + } + } + + /// Cancella tutto: token + preferenze. Chiamato al logout. + Future clear() async { + await _secure.delete(key: _kToken); + await _secure.delete(key: _kRemember); + await _secure.delete(key: _kBiometric); + } + + /// Lettura grezza del token (senza biometria). Uso interno. + Future _readToken() => _secure.read(key: _kToken); + + // --------------------------------------------------------------------------- + // BIOMETRIA + // --------------------------------------------------------------------------- + + /// Il dispositivo può fare biometria E ha almeno un metodo registrato? + Future canUseBiometrics() async { + try { + final supported = await _localAuth.isDeviceSupported(); + if (!supported) return false; + final canCheck = await _localAuth.canCheckBiometrics; + if (!canCheck) return false; + final enrolled = await _localAuth.getAvailableBiometrics(); + return enrolled.isNotEmpty; + } on PlatformException { + return false; + } + } + + /// Chiede l'impronta/volto. true = passata, false = fallita/annullata. + Future _promptBiometric() async { + try { + return await _localAuth.authenticate( + localizedReason: 'Sblocca YogiBook per accedere al tuo account', + options: const AuthenticationOptions( + biometricOnly: true, // niente fallback al PIN del telefono (scelta B) + stickyAuth: true, // regge se l'app va in background durante il prompt + ), + ); + } on PlatformException catch (e) { + // Casi tipici: nessuna impronta registrata, hardware assente, troppi tentativi. + if (e.code == auth_error.notAvailable || + e.code == auth_error.notEnrolled || + e.code == auth_error.lockedOut || + e.code == auth_error.permanentlyLockedOut) { + return false; + } + return false; + } + } + + // --------------------------------------------------------------------------- + // SBLOCCO ALL'AVVIO (chiamato dallo splash) + // --------------------------------------------------------------------------- + + /// Decide cosa fare all'avvio dell'app. + /// Restituisce l'esito; il token (se disponibile) è in [tokenOut]. + Future<({UnlockResult result, String? token})> tryUnlockAtStartup() async { + // 1. Ricordami spento o nessun token → login normale. + if (!await isRememberMeEnabled()) { + return (result: UnlockResult.noSavedLogin, token: null); + } + final token = await _readToken(); + if (token == null || token.isEmpty) { + return (result: UnlockResult.noSavedLogin, token: null); + } + + // 2. Biometria spenta → entra diretto col token salvato. + if (!await isBiometricEnabled()) { + return (result: UnlockResult.success, token: token); + } + + // 3. Biometria accesa ma non disponibile → fallback a password (scelta B). + if (!await canUseBiometrics()) { + return (result: UnlockResult.biometricUnavailable, token: null); + } + + // 4. Biometria accesa e disponibile → chiedi l'impronta. + final ok = await _promptBiometric(); + if (ok) { + return (result: UnlockResult.success, token: token); + } + return (result: UnlockResult.biometricFailed, token: null); + } +} diff --git a/lib/widgets/app_drawer.dart b/lib/widgets/app_drawer.dart index 8804215..ef71e22 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 '../screens/settings_page.dart'; import 'package:url_launcher/url_launcher.dart'; class AppDrawer extends StatelessWidget { @@ -166,6 +167,24 @@ class AppDrawer extends StatelessWidget { }, ), + ListTile( + leading: const Icon(Icons.settings_rounded), + title: const Text('Impostazioni'), + onTap: () { + Navigator.of(context).pop(); + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => SettingsPage( + token: token, + school: school, + userFirstName: userFirstName, + ), + ), + ); + }, + ), + const Divider(height: 1), ListTile( diff --git a/pubspec.lock b/pubspec.lock index 92eb26e..1b9d7c4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,22 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: be169cf6ac481e052c4538715d88841d567150dfe1df38aaec76461a4e7b39f2 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -73,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + change_app_package_name: + dependency: "direct dev" + description: + name: change_app_package_name + sha256: "8e43b754fe960426904d77ed4c62fa8c9834deaf6e293ae40963fa447482c4c5" + url: "https://pub.dev" + source: hosted + version: "1.5.0" characters: dependency: transitive description: @@ -81,6 +105,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -198,6 +238,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" + url: "https://pub.dev" + source: hosted + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -341,6 +389,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: "1976370a4df3091bb0f72409c187ad1f9132a818bc6b95ca59c0bae1c75c688e" + url: "https://pub.dev" + source: hosted + version: "4.9.2" image_picker: dependency: "direct main" description: @@ -421,6 +477,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" leak_tracker: dependency: transitive description: @@ -605,6 +669,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e + url: "https://pub.dev" + source: hosted + version: "6.5.2" provider: dependency: "direct main" description: @@ -850,6 +922,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.10.4 <4.0.0" flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index 10aa006..8dd347f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -50,6 +50,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + change_app_package_name: ^1.5.0 + flutter_launcher_icons: ^0.14.4 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is @@ -103,3 +105,13 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/to/font-from-package + +flutter_launcher_icons: + android: true + ios: true + image_path: "assets/icon/app_icon.png" + # Adaptive icon Android: sfondo bianco + soggetto in primo piano. + adaptive_icon_background: "#FFFFFF" + adaptive_icon_foreground: "assets/icon/app_icon.png" + # iOS non ammette trasparenza: rimuove il canale alpha automaticamente. + remove_alpha_ios: true