diff --git a/.metadata b/.metadata index 784ce129..2f7f3282 100644 --- a/.metadata +++ b/.metadata @@ -4,8 +4,8 @@ # This file should be version controlled and should not be manually edited. version: - revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3" - channel: "stable" + revision: "e7da16df76cd239ec9ed07f0d0caf78154e63986" + channel: "master" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - - platform: web - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + create_revision: e7da16df76cd239ec9ed07f0d0caf78154e63986 + base_revision: e7da16df76cd239ec9ed07f0d0caf78154e63986 + - platform: android + create_revision: e7da16df76cd239ec9ed07f0d0caf78154e63986 + base_revision: e7da16df76cd239ec9ed07f0d0caf78154e63986 # User provided section diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 00000000..54481788 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,54 @@ +{ + "project_info": { + "project_number": "454225435556", + "project_id": "nancy-app-cca62", + "storage_bucket": "nancy-app-cca62.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:454225435556:android:2595b518d049a37da43f31", + "android_client_info": { + "package_name": "com.nancy.app" + } + }, + "oauth_client": [ + { + "client_id": "454225435556-1uhsoveijds7eogkl6vc78l51v26oaul.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.nancy.app", + "certificate_hash": "4335f5cde84455e76069780f4e1ec6dd0308ba1b" + } + }, + { + "client_id": "454225435556-l2kos64fi84t12eac3ulqrbrdnepnrv8.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCOyvD91rLLZ0nUTxC70b5uM2pR2KIsxrU" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "454225435556-7knndu3td7cso0tkndh1dvou6g4ib7re.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "454225435556-8jkgu2a15hau84dfr71mh61dh0c29mf3.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.nancy.app" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/get_flutter_fire/MainActivity.kt b/android/app/src/main/kotlin/com/example/get_flutter_fire/MainActivity.kt new file mode 100644 index 00000000..f4e5ba83 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/get_flutter_fire/MainActivity.kt @@ -0,0 +1,5 @@ +package com.nancy.app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/assets/images/1.png b/assets/images/1.png new file mode 100644 index 00000000..926e3df9 Binary files /dev/null and b/assets/images/1.png differ diff --git a/assets/images/2.png b/assets/images/2.png new file mode 100644 index 00000000..51430256 Binary files /dev/null and b/assets/images/2.png differ diff --git a/assets/images/3.png b/assets/images/3.png new file mode 100644 index 00000000..d9121581 Binary files /dev/null and b/assets/images/3.png differ diff --git a/assets/images/4.png b/assets/images/4.png new file mode 100644 index 00000000..7892c679 Binary files /dev/null and b/assets/images/4.png differ diff --git a/assets/images/5.jpg b/assets/images/5.jpg new file mode 100644 index 00000000..69a8031e Binary files /dev/null and b/assets/images/5.jpg differ diff --git a/assets/images/5.png b/assets/images/5.png new file mode 100644 index 00000000..02555d57 Binary files /dev/null and b/assets/images/5.png differ diff --git a/assets/images/6.png b/assets/images/6.png new file mode 100644 index 00000000..a3926d64 Binary files /dev/null and b/assets/images/6.png differ diff --git a/assets/images/6.webp b/assets/images/6.webp new file mode 100644 index 00000000..22e5b8f3 Binary files /dev/null and b/assets/images/6.webp differ diff --git a/assets/images/dark1.jpg b/assets/images/dark1.jpg new file mode 100644 index 00000000..b9429528 Binary files /dev/null and b/assets/images/dark1.jpg differ diff --git a/assets/images/dark2.jpg b/assets/images/dark2.jpg new file mode 100644 index 00000000..fb67afb6 Binary files /dev/null and b/assets/images/dark2.jpg differ diff --git a/assets/images/dark3.jpg b/assets/images/dark3.jpg new file mode 100644 index 00000000..580c651c Binary files /dev/null and b/assets/images/dark3.jpg differ diff --git a/assets/images/dashboard.jpg b/assets/images/dashboard.jpg new file mode 100644 index 00000000..c6a7cb4f Binary files /dev/null and b/assets/images/dashboard.jpg differ diff --git a/assets/images/image1.jpg b/assets/images/image1.jpg new file mode 100644 index 00000000..6f320a0d Binary files /dev/null and b/assets/images/image1.jpg differ diff --git a/assets/images/image2.jpg b/assets/images/image2.jpg new file mode 100644 index 00000000..f58e24a5 Binary files /dev/null and b/assets/images/image2.jpg differ diff --git a/assets/images/image3.jpg b/assets/images/image3.jpg new file mode 100644 index 00000000..686175f3 Binary files /dev/null and b/assets/images/image3.jpg differ diff --git a/assets/images/luxury1.jpg b/assets/images/luxury1.jpg new file mode 100644 index 00000000..a2d18660 Binary files /dev/null and b/assets/images/luxury1.jpg differ diff --git a/assets/images/luxury2.jpg b/assets/images/luxury2.jpg new file mode 100644 index 00000000..305d7b5a Binary files /dev/null and b/assets/images/luxury2.jpg differ diff --git a/desktop.ini b/desktop.ini new file mode 100644 index 00000000..d957fd18 --- /dev/null +++ b/desktop.ini @@ -0,0 +1,4 @@ +[ViewState] +Mode= +Vid= +FolderType=Generic diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..403426ba --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"dart":{"lib/firebase_options.dart":{"projectId":"nancy-app-cca62","configurations":{"android":"1:454225435556:android:2595b518d049a37da43f31","ios":"1:454225435556:ios:b249b0bcf5076f58a43f31","macos":"1:454225435556:ios:b249b0bcf5076f58a43f31","web":"1:454225435556:web:63b16213373ebe01a43f31","windows":"1:454225435556:web:4f937be6d358770da43f31"}}}}}} \ No newline at end of file diff --git a/ios/Flutter/Generated.xcconfig b/ios/Flutter/Generated.xcconfig new file mode 100644 index 00000000..299e0ed1 --- /dev/null +++ b/ios/Flutter/Generated.xcconfig @@ -0,0 +1,14 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=E:\flutter +FLUTTER_APPLICATION_PATH=E:\get-flutter\get-flutter-fire +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_TARGET=lib\main.dart +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 +EXCLUDED_ARCHS[sdk=iphoneos*]=armv7 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/ios/Flutter/flutter_export_environment.sh b/ios/Flutter/flutter_export_environment.sh new file mode 100644 index 00000000..7704712d --- /dev/null +++ b/ios/Flutter/flutter_export_environment.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=E:\flutter" +export "FLUTTER_APPLICATION_PATH=E:\get-flutter\get-flutter-fire" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_TARGET=lib\main.dart" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/ios/Runner/GeneratedPluginRegistrant.h b/ios/Runner/GeneratedPluginRegistrant.h new file mode 100644 index 00000000..7a890927 --- /dev/null +++ b/ios/Runner/GeneratedPluginRegistrant.h @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GeneratedPluginRegistrant_h +#define GeneratedPluginRegistrant_h + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface GeneratedPluginRegistrant : NSObject ++ (void)registerWithRegistry:(NSObject*)registry; +@end + +NS_ASSUME_NONNULL_END +#endif /* GeneratedPluginRegistrant_h */ diff --git a/ios/Runner/GeneratedPluginRegistrant.m b/ios/Runner/GeneratedPluginRegistrant.m new file mode 100644 index 00000000..40ba5dd7 --- /dev/null +++ b/ios/Runner/GeneratedPluginRegistrant.m @@ -0,0 +1,105 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#import "GeneratedPluginRegistrant.h" + +#if __has_include() +#import +#else +@import desktop_webview_auth; +#endif + +#if __has_include() +#import +#else +@import file_picker; +#endif + +#if __has_include() +#import +#else +@import firebase_analytics; +#endif + +#if __has_include() +#import +#else +@import firebase_auth; +#endif + +#if __has_include() +#import +#else +@import firebase_core; +#endif + +#if __has_include() +#import +#else +@import firebase_dynamic_links; +#endif + +#if __has_include() +#import +#else +@import firebase_remote_config; +#endif + +#if __has_include() +#import +#else +@import firebase_storage; +#endif + +#if __has_include() +#import +#else +@import google_sign_in_ios; +#endif + +#if __has_include() +#import +#else +@import image_picker_ios; +#endif + +#if __has_include() +#import +#else +@import path_provider_foundation; +#endif + +#if __has_include() +#import +#else +@import sqflite; +#endif + +#if __has_include() +#import +#else +@import webview_flutter_wkwebview; +#endif + +@implementation GeneratedPluginRegistrant + ++ (void)registerWithRegistry:(NSObject*)registry { + [DesktopWebviewAuthPlugin registerWithRegistrar:[registry registrarForPlugin:@"DesktopWebviewAuthPlugin"]]; + [FilePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FilePickerPlugin"]]; + [FLTFirebaseAnalyticsPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseAnalyticsPlugin"]]; + [FLTFirebaseAuthPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseAuthPlugin"]]; + [FLTFirebaseCorePlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseCorePlugin"]]; + [FLTFirebaseDynamicLinksPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseDynamicLinksPlugin"]]; + [FLTFirebaseRemoteConfigPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseRemoteConfigPlugin"]]; + [FLTFirebaseStoragePlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseStoragePlugin"]]; + [FLTGoogleSignInPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTGoogleSignInPlugin"]]; + [FLTImagePickerPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTImagePickerPlugin"]]; + [PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]]; + [SqflitePlugin registerWithRegistrar:[registry registrarForPlugin:@"SqflitePlugin"]]; + [FLTWebViewFlutterPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTWebViewFlutterPlugin"]]; +} + +@end diff --git a/lib/app/modules/cart/controllers/cart_controller.dart b/lib/app/modules/cart/controllers/cart_controller.dart index c938ec4c..469d2888 100644 --- a/lib/app/modules/cart/controllers/cart_controller.dart +++ b/lib/app/modules/cart/controllers/cart_controller.dart @@ -1,23 +1,34 @@ import 'package:get/get.dart'; +import 'package:get_flutter_fire/models/cart_item.dart' as model; // Use alias for the model CartItem +import '../../products/controllers/products_controller.dart'; class CartController extends GetxController { - //TODO: Implement CartController + final ProductsController productsController = Get.find(); - final count = 0.obs; - @override - void onInit() { - super.onInit(); - } + RxList cartItems = [].obs; + + double get total => cartItems.fold(0, (sum, item) => sum + (item.product.price * item.quantity.value)); + + int get itemCount => cartItems.fold(0, (sum, item) => sum + item.quantity.value); - @override - void onReady() { - super.onReady(); + void removeItem(model.CartItem item) { + cartItems.remove(item); + productsController.saveCartItems(); } - @override - void onClose() { - super.onClose(); + void updateQuantity(model.CartItem item, int quantity) { + if (quantity > 0) { + item.quantity.value = quantity; + productsController.saveCartItems(); + } else { + removeItem(item); + } } - void increment() => count.value++; + void buyItems() { + Get.snackbar('Success', 'Items purchased successfully!'); + cartItems.clear(); + productsController.saveCartItems(); + } } + diff --git a/lib/app/modules/cart/views/cart_view.dart b/lib/app/modules/cart/views/cart_view.dart index 3e048c79..905dad92 100644 --- a/lib/app/modules/cart/views/cart_view.dart +++ b/lib/app/modules/cart/views/cart_view.dart @@ -1,27 +1,96 @@ import 'package:flutter/material.dart'; - import 'package:get/get.dart'; -import 'package:get_flutter_fire/app/routes/app_pages.dart'; -import '../../../widgets/screen_widget.dart'; -import '../../../../services/auth_service.dart'; +import '../../../widgets/cartProduct.dart'; +//import '../../settings/controllers/settings_controller.dart'; import '../controllers/cart_controller.dart'; +import '../../../../services/auth_service.dart'; // Import your AuthService class CartView extends GetView { - const CartView({super.key}); + CartView({super.key}); + + //final SettingsController themeController = Get.find(); + final AuthService authService = Get.find(); // Get the AuthService + @override Widget build(BuildContext context) { - return ScreenWidget( - appBar: AppBar( - title: Text('${AuthService.to.userName} Cart'), - centerTitle: true, - ), - body: const Center( - child: Text( - 'CartView is working', - style: TextStyle(fontSize: 20), - ), - ), - screen: screen!, + return Scaffold( + backgroundColor: Colors.white, + body: Obx(() { + if (controller.cartItems.isEmpty) { + return Center( + child: Text( + 'Cart is empty', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ); + } else { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ListView.builder( + itemCount: controller.cartItems.length, + itemBuilder: (context, index) { + return CartCard(cartItem: controller.cartItems[index]); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Total: \₹${controller.total.toStringAsFixed(2)}'), + Text('Number of items: ${controller.itemCount}'), + ], + ), + Spacer(), + ElevatedButton( + onPressed: () async { + if (authService.isLoggedInValue) { + // User is logged in, proceed with the purchase + controller.buyItems(); + } else { + // User is not logged in, show sign-in dialog + bool? result = await authService.guest(); + + if (result == true) { + // User chose to proceed as a guest, do nothing (don't buy items) + Get.snackbar( + 'Purchase not completed', + 'You chose to proceed as a guest. Please sign in to complete the purchase.', + snackPosition: SnackPosition.BOTTOM, + ); + } else if (result == false) { + // User chose to sign in now, wait for sign in to complete + if (authService.isLoggedInValue) { + // If the user successfully signs in, proceed with the purchase + controller.buyItems(); + } else { + // User canceled or failed sign-in, no action needed + Get.snackbar( + 'Sign-In Required', + 'Please sign in to complete the purchase.', + snackPosition: SnackPosition.BOTTOM, + ); + } + } + } + }, + child: Text('Buy'), + ), + ], + ), + ), + ], + ), + ); + } + }), ); } } diff --git a/lib/app/modules/dashboard/controllers/dashboard_controller.dart b/lib/app/modules/dashboard/controllers/dashboard_controller.dart index 24d91a16..ac19fbb7 100644 --- a/lib/app/modules/dashboard/controllers/dashboard_controller.dart +++ b/lib/app/modules/dashboard/controllers/dashboard_controller.dart @@ -1,12 +1,23 @@ import 'dart:async'; - import 'package:get/get.dart'; class DashboardController extends GetxController { final now = DateTime.now().obs; + + // Product lists + final RxList> products = >[].obs; + final RxList> trendingProducts = + >[].obs; + final RxList> luxuryPerfumes = >[].obs; + final RxList> darkPerfumes = >[].obs; + @override - void onReady() { - super.onReady(); + void onInit() { + super.onInit(); + fetchProducts(); + fetchTrendingProducts(); + fetchLuxuryPerfumes(); + fetchDarkPerfumes(); Timer.periodic( const Duration(seconds: 1), (timer) { @@ -14,4 +25,79 @@ class DashboardController extends GetxController { }, ); } + + void fetchProducts() { + products.assignAll([ + { + 'name': 'Women\'s Perfume Collection', + 'image': 'assets/images/1.png', + 'price': '\₹2000', + }, + { + 'name': 'Men\'s Perfume Collection', + 'image': 'assets/images/2.png', + 'price': '\₹4000', + }, + { + 'name': 'Unisex', + 'image': 'assets/images/3.png', + 'price': '\₹5600', + }, + { + 'name': 'Luxury Perfume Collection', + 'image': 'assets/images/4.png', + 'price': '\₹7600', + }, + ]); + } + + void fetchTrendingProducts() { + trendingProducts.assignAll([ + { + 'name': 'Eternal Blossom', + 'image': 'assets/images/image3.jpg', + 'price': '\₹2000', + }, + { + 'name': 'Crimson Desire', + 'image': 'assets/images/image2.jpg', + 'price': '\₹5600', + }, + ]); + } + + void fetchLuxuryPerfumes() { + luxuryPerfumes.assignAll([ + { + 'name': 'Luxury Night Perfume', + 'image': 'assets/images/luxury1.jpg', + 'price': '\₹3000', + }, + { + 'name': 'Elegant Women\'s Perfume', + 'image': 'assets/images/luxury2.jpg', + 'price': '\₹7600', + }, + ]); + } + + void fetchDarkPerfumes() { + darkPerfumes.assignAll([ + { + 'name': 'Dark Mystique', + 'image': 'assets/images/dark1.jpg', + 'price': '\₹3500', + }, + { + 'name': 'Midnight Essence', + 'image': 'assets/images/dark2.jpg', + 'price': '\₹4000', + }, + { + 'name': 'Noir Elegance', + 'image': 'assets/images/dark3.jpg', + 'price': '\₹4500', + }, + ]); + } } diff --git a/lib/app/modules/dashboard/views/dashboard_view.dart b/lib/app/modules/dashboard/views/dashboard_view.dart index f475030f..4c7ad884 100644 --- a/lib/app/modules/dashboard/views/dashboard_view.dart +++ b/lib/app/modules/dashboard/views/dashboard_view.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; - import '../controllers/dashboard_controller.dart'; class DashboardView extends GetView { @@ -9,18 +8,230 @@ class DashboardView extends GetView { @override Widget build(BuildContext context) { return Scaffold( - body: Center( - child: Obx( - () => Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'DashboardView is working', - style: TextStyle(fontSize: 20), - ), - Text('Time: ${controller.now.value.toString()}'), - ], + appBar: AppBar( + title: const Text('Solitaire Perfume'), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + 'assets/images/image1.jpg', + width: double.infinity, + height: 150, + fit: BoxFit.cover, + ), + const SizedBox(height: 10), + _buildTrendingProducts(), + const SizedBox(height: 20), + _buildSolitairePerfumeRange(), + const SizedBox(height: 20), + _buildLuxuryPerfumeCollection(), + const SizedBox(height: 20), + _buildDarkPerfumes(), + ], + ), + ), + ); + } + + Widget _buildTrendingProducts() { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Perfumes You Must Try', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + SizedBox( + height: 255, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: controller.trendingProducts.length, + itemBuilder: (context, index) { + return _buildProductCardWithRating(controller.trendingProducts[index]); + }, + ), + ), + ], + ), + ); + } + + Widget _buildSolitairePerfumeRange() { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Solitaire Perfume Range', + style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + SizedBox( + height: 255, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: controller.products.length, + itemBuilder: (context, index) { + return _buildProductCard(controller.products[index]); + }, + ), + ), + ], + ), + ); + } + + Widget _buildLuxuryPerfumeCollection() { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Luxury Perfume Collection', + style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), + const SizedBox(height: 10), + SizedBox( + height: 255, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: controller.products.length, + itemBuilder: (context, index) { + return _buildProductCard(controller.products[index]); + }, + ), + ), + ], + ), + ); + } + + Widget _buildDarkPerfumes() { + return Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Dark Perfumes Collection', + style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 10), + SizedBox( + height: 255, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: controller.darkPerfumes.length, + itemBuilder: (context, index) { + return _buildProductCard(controller.darkPerfumes[index]); + }, + ), + ), + ], + ), + ); + } + + Widget _buildProductCard(Map product) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + elevation: 4, + margin: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 150, + width: 180, + child: Image.asset( + product['image']!, + fit: BoxFit.cover, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product['name']!, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 5), + Text( + product['price']!, + style: const TextStyle( + fontSize: 14, + color: Color.fromARGB(255, 92, 2, 86), + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildProductCardWithRating(Map product) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + elevation: 4, + margin: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 150, + width: 180, + child: Image.asset( + product['image']!, + fit: BoxFit.cover, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product['name']!, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + const SizedBox(height: 5), + Row( + children: const [ + Icon(Icons.star, color: Colors.amber, size: 16), + Icon(Icons.star, color: Colors.amber, size: 16), + Icon(Icons.star, color: Colors.amber, size: 16), + Icon(Icons.star, color: Colors.amber, size: 16), + Icon(Icons.star_border, color: Colors.amber, size: 16), + ], + ), + const SizedBox(height: 5), + Text( + product['price']!, + style: const TextStyle( + fontSize: 14, + color: Color.fromARGB(255, 92, 2, 86), + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], ), ), ); diff --git a/lib/app/modules/login/controllers/login_controller.dart b/lib/app/modules/login/controllers/login_controller.dart index 5178fec9..8e143acf 100644 --- a/lib/app/modules/login/controllers/login_controller.dart +++ b/lib/app/modules/login/controllers/login_controller.dart @@ -1,4 +1,6 @@ +import 'package:firebase_auth/firebase_auth.dart' as fba; import 'package:get/get.dart'; +import 'package:get_flutter_fire/models/screens.dart'; import '../../../../services/auth_service.dart'; @@ -6,6 +8,10 @@ class LoginController extends GetxController { static AuthService get to => Get.find(); final Rx showReverificationButton = Rx(false); + final Rxn credential = Rxn(); + final RxString verificationId = ''.obs; + final RxString smsCode = ''.obs; + final RxString selectedCountryCode = '+91'.obs; // Default country code bool get isRobot => AuthService.to.robot.value == true; @@ -17,4 +23,34 @@ class LoginController extends GetxController { bool get isRegistered => AuthService.to.registered.value || AuthService.to.isEmailVerified; + + void sendVerificationMail({fba.EmailAuthCredential? emailAuth}) { + AuthService.to.sendVerificationMail(emailAuth: emailAuth); + } + + void guestlogin() { + AuthService.to.guestlogin(); + } + + void verifyPhoneNumber(String phoneNumber) { + final fullPhoneNumber = '$selectedCountryCode$phoneNumber'; + AuthService.to.pnVerify( + fullPhoneNumber, + (String verificationId) { + this.verificationId.value = verificationId; + }, + (String userId) { + // Handle successful verification + Get.offAllNamed(Screen.HOME.route); + }, + ); + } + + void signInWithSmsCode() { + if (verificationId.isNotEmpty && smsCode.isNotEmpty) { + AuthService.to.pnsignin(verificationId.value, smsCode.value); + } else { + Get.snackbar('Error', 'Verification ID or SMS code is empty'); + } + } } diff --git a/lib/app/modules/login/views/login_view.dart b/lib/app/modules/login/views/login_view.dart index 00c3af3f..d38bdee1 100644 --- a/lib/app/modules/login/views/login_view.dart +++ b/lib/app/modules/login/views/login_view.dart @@ -35,6 +35,19 @@ class LoginView extends GetView { return Obx(() => loginScreen(context)); } + Widget footerBuilder(Rx show, Rxn credential) { + return Column( + children: [ + LoginWidgets.footerBuilder(EmailLinkButton(show, credential)), + const SizedBox(height: 16), // Add spacing between the terms and the button + ElevatedButton( + onPressed: () => controller.guestlogin(), + child: const Text('Anonymous login'), + ), + ], + ); + } + Widget subtitleBuilder(context, action) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), @@ -44,10 +57,6 @@ class LoginView extends GetView { ); } - Widget footerBuilder(Rx show, Rxn credential) { - return LoginWidgets.footerBuilder(EmailLinkButton(show, credential)); - } - Widget loginScreen(BuildContext context) { Widget ui; if (!controller.isLoggedIn) { @@ -57,6 +66,7 @@ class LoginView extends GetView { providers: [ GoogleProvider(clientId: DefaultFirebaseOptions.webClientId), MyEmailAuthProvider(), + PhoneAuthProvider(), ], showAuthActionSwitch: !controller.isRegistered, showPasswordVisibilityToggle: true, @@ -72,6 +82,8 @@ class LoginView extends GetView { ui = RegisterScreen( providers: [ MyEmailAuthProvider(), + PhoneAuthProvider(), + ], showAuthActionSwitch: !controller.isAnon, //if Anon only SignUp showPasswordVisibilityToggle: true, @@ -159,4 +171,4 @@ class EmailLinkButton extends StatelessWidget { .sendVerificationMail(emailAuth: credential.value), child: const Text('Resend Verification Mail'))))); } -} +} \ No newline at end of file diff --git a/lib/app/modules/product_details/controllers/product_details_controller.dart b/lib/app/modules/product_details/controllers/product_details_controller.dart index d894e10c..e1f930c3 100644 --- a/lib/app/modules/product_details/controllers/product_details_controller.dart +++ b/lib/app/modules/product_details/controllers/product_details_controller.dart @@ -1,13 +1,109 @@ import 'package:get/get.dart'; +import '../../cart/controllers/cart_controller.dart'; +import '../../../../models/product.dart'; +import '../../../../models/cart_item.dart'; // Ensure this path is correct +import 'package:flutter/material.dart'; class ProductDetailsController extends GetxController { final String productId; + final CartController cartController = Get.find(); + + // Local product data + final Map> products = { + '1': { + 'name': 'Chanel Perfume for Women', + 'price': 2000.0, + 'sellingPrice': 1800.0, + 'description': 'Luxury Chanel Perfume.', + 'productImage': [ + 'https://i.ibb.co/PQW82z4/1.png', + ], + }, + '2': { + 'name': 'Men\'s Exclusive Perfume', + 'price': 4000.0, + 'sellingPrice': 3500.0, + 'description': 'Exclusive Men\'s Perfume.', + 'productImage': [ + 'https://i.ibb.co/n1nG68M/4.png', + ], + }, + '3': { + 'name': 'Unisex Perfume', + 'price': 5600.0, + 'sellingPrice': 5000.0, + 'description': 'Versatile unisex fragrance.', + 'productImage': [ + 'https://i.ibb.co/WgntTLS/3.png', + ], + }, + '4': { + 'name': 'Elegant Women\'s Perfume', + 'price': 7600.0, + 'sellingPrice': 7000.0, + 'description': 'Elegant fragrance for women.', + 'productImage': [ + 'https://i.ibb.co/72h1Zzp/2.png', + ], + }, + '5': { + 'name': 'Luxury Night Perfume', + 'price': 3000.0, + 'sellingPrice': 2700.0, + 'description': 'Luxurious night fragrance.', + 'productImage': ['https://i.ibb.co/NVjdssC/5.png'], + }, + '6': { + 'name': 'Fresh Citrus Perfume', + 'price': 1500.0, + 'sellingPrice': 1300.0, + 'description': 'Refreshing citrus scent.', + 'productImage': ['https://i.ibb.co/GnphSbv/6.png'], + }, + }; + + // Observable product details and loading status + var productDetails = {}.obs; + var isLoading = true.obs; ProductDetailsController(this.productId); + @override void onInit() { super.onInit(); Get.log('ProductDetailsController created with id: $productId'); + fetchProductDetails(); + } + + // Method to fetch product details + void fetchProductDetails() { + isLoading.value = true; + // Simulate a delay for fetching data + Future.delayed(const Duration(seconds: 1), () { + if (products.containsKey(productId)) { + productDetails.value = products[productId]!; + } else { + productDetails.value = {}; + } + isLoading.value = false; + }); + } + + void addToCart() { + final product = productDetails.value; + final cartItem = CartItem( + product: Product.fromJson({ + 'id': productId, + 'name': product['name'], + 'price': product['price'], + 'productImage': product['productImage'][0], + 'category': 'N/A', + 'description': product['description'], + }), + quantity: RxInt(1), // Use a regular integer if RxInt is not supported + ); + cartController.cartItems.add(cartItem); // Ensure cartItems is of type List + Get.snackbar('Success', 'Product added to cart'); } @override diff --git a/lib/app/modules/product_details/views/product_details_view.dart b/lib/app/modules/product_details/views/product_details_view.dart index c9290724..1b7ac049 100644 --- a/lib/app/modules/product_details/views/product_details_view.dart +++ b/lib/app/modules/product_details/views/product_details_view.dart @@ -1,7 +1,6 @@ +import 'dart:async'; import 'package:flutter/material.dart'; - import 'package:get/get.dart'; - import '../controllers/product_details_controller.dart'; class ProductDetailsView extends GetWidget { @@ -9,19 +8,181 @@ class ProductDetailsView extends GetWidget { @override Widget build(BuildContext context) { + final PageController pageController = PageController(); + return Scaffold( - body: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'ProductDetailsView is working', - style: TextStyle(fontSize: 20), + body: Obx(() { + if (controller.isLoading.value) { + return Center(child: CircularProgressIndicator()); + } + + if (controller.productDetails.isEmpty) { + return Center( + child: Text( + 'Failed to load product details', + style: TextStyle(fontSize: 16, color: const Color.fromARGB(255, 226, 36, 188)), + ), + ); + } + + final product = controller.productDetails; + final productImages = (product['productImage'] as List?) ?? []; + + Timer.periodic(Duration(seconds: 3), (Timer timer) { + if (pageController.hasClients) { + int nextPage = (pageController.page?.round() ?? 0) + 1; + if (nextPage == productImages.length) { + nextPage = 0; + } + pageController.animateToPage( + nextPage, + duration: Duration(milliseconds: 300), + curve: Curves.easeIn, + ); + } + }); + + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Product Images Carousel + Container( + height: 300, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 2, + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Stack( + children: [ + PageView.builder( + controller: pageController, + itemCount: productImages.length, + itemBuilder: (context, index) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.network( + productImages[index], + fit: BoxFit.cover, + width: double.infinity, + ), + ), + ); + }, + scrollDirection: Axis.horizontal, + ), + Positioned( + bottom: 16, + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + productImages.length, + (index) => Container( + margin: const EdgeInsets.symmetric(horizontal: 4), + width: 8, + height: 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: const Color.fromARGB(255, 218, 125, 184).withOpacity(index == 0 ? 1 : 0.5), + ), + ), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 16), + // Product Details + Text( + product['name'] ?? 'N/A', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Text( + 'Description: ', + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + Expanded( + child: Text( + product['description'] ?? 'No description available', + style: TextStyle( + fontSize: 14, + color: Colors.black87, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + const SizedBox(height: 16), + // Add to Cart Button + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + controller.addToCart(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: const Color.fromARGB(255, 244, 210, 242), + padding: EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text( + 'Add to Cart', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + ), + ), + const SizedBox(height: 12), + // Buy Now Button (commented out) + // SizedBox( + // width: double.infinity, + // child: ElevatedButton( + // onPressed: () {}, + // style: ElevatedButton.styleFrom( + // backgroundColor: const Color.fromARGB(255, 244, 210, 242), + // padding: EdgeInsets.symmetric(vertical: 12), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // ), + // child: Text( + // 'Buy Now', + // style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + // ), + // ), + // ), + ], ), - Text('ProductId: ${controller.productId}') - ], - ), - ), + ), + ); + }), ); } } diff --git a/lib/app/modules/products/controllers/products_controller.dart b/lib/app/modules/products/controllers/products_controller.dart index 118c7dc8..69d34480 100644 --- a/lib/app/modules/products/controllers/products_controller.dart +++ b/lib/app/modules/products/controllers/products_controller.dart @@ -1,23 +1,112 @@ import 'package:get/get.dart'; - -import '../../../../models/product.dart'; +import 'package:get_flutter_fire/app/modules/settings/controllers/settings_controller.dart'; +import 'package:get_storage/get_storage.dart'; +import '../../../../models/product.dart'; // Ensure this path matches your project structure +// import '../../../../controllers/settings_controller.dart'; // Import SettingsController class ProductsController extends GetxController { final products = [].obs; + final filteredProducts = [].obs; + final selectedCategory = ''.obs; + final cartItems = [].obs; + final trendingProducts = [].obs; + final box = GetStorage(); // Initialize GetStorage for saving cart items + final filters = ['All', 'Male', 'Female', 'Unisex'].obs; - void loadDemoProductsFromSomeWhere() { - products.add( + final SettingsController settingsController = Get.find(); + + @override + void onInit() { + super.onInit(); + loadDemoProducts(); + applyFilter(); + } + + // Method to load demo products + void loadDemoProducts() { + products.assignAll([ Product( - name: 'Product added on: ${DateTime.now().toString()}', - id: DateTime.now().millisecondsSinceEpoch.toString(), + name: 'Chanel Perfume for Women', + id: '1', + productImage: 'https://i.ibb.co/PQW82z4/1.png', + price: 2000.0, + description: 'Luxury Chanel Perfume.', + category: 'Women', ), - ); + Product( + name: 'Men\'s Exclusive Perfume', + id: '2', + productImage: 'https://i.ibb.co/n1nG68M/4.png', + price: 4000.0, + description: 'Exclusive Men\'s Perfume.', + category: 'Men', + ), + Product( + name: 'Unisex Perfume', + id: '3', + productImage: 'https://i.ibb.co/WgntTLS/3.png', + price: 5600.0, + description: 'Versatile unisex fragrance.', + category: 'Unisex', + ), + Product( + name: 'Elegant Women\'s Perfume', + id: '4', + productImage: 'https://i.ibb.co/72h1Zzp/2.png', + price: 7600.0, + description: 'Elegant fragrance for women.', + category: 'Women', + ), + Product( + name: 'Luxury Night Perfume', + id: '5', + productImage: 'https://i.ibb.co/NVjdssC/5.png', + price: 3000.0, + description: 'Luxurious night fragrance.', + category: 'Unisex', + ), + Product( + name: 'Fresh Citrus Perfume', + id: '6', + productImage: 'https://i.ibb.co/GnphSbv/6.png', + price: 1500.0, + description: 'Refreshing citrus scent.', + category: 'Unisex', + ), + ]); + applyFilter(); } - @override - void onReady() { - super.onReady(); - loadDemoProductsFromSomeWhere(); + // Method to apply filters based on selected category and persona + void applyFilter() { + // Get selected persona from SettingsController + final persona = settingsController.selectedPersona.value; + + if (persona == null) { + // No persona selected, show all products + if (selectedCategory.isEmpty || selectedCategory.value == 'All') { + filteredProducts.assignAll(products); + } else { + filteredProducts.assignAll(products.where((product) => product.category == selectedCategory.value).toList()); + } + } else { + // Persona selected, filter products based on persona + final isFemalePersona = persona.name == 'Solitaire Female'; + final filteredByPersona = products.where((product) { + if (isFemalePersona) { + return product.category == 'Women' || product.category == 'Unisex'; + } else { + return product.category == 'Men' || product.category == 'Unisex'; + } + }).toList(); + + // Apply category filter if not 'All' + if (selectedCategory.isEmpty || selectedCategory.value == 'All') { + filteredProducts.assignAll(filteredByPersona); + } else { + filteredProducts.assignAll(filteredByPersona.where((product) => product.category == selectedCategory.value).toList()); + } + } } @override @@ -25,4 +114,55 @@ class ProductsController extends GetxController { Get.printInfo(info: 'Products: onClose'); super.onClose(); } + + void setCategory(String category) { + selectedCategory.value = category; + applyFilter(); + } + // Method to load demo products from somewhere + void loadDemoProductsFromSomeWhere() { + loadDemoProducts(); // Call the actual method to load products + } + + void addToCart(Product product) { + var existingItem = cartItems.firstWhereOrNull((item) => item.product.id == product.id); + if (existingItem != null) { + existingItem.quantity.value++; + } else { + cartItems.add(CartItem(product: product, quantity: 1)); + } + saveCartItems(); + Get.snackbar('Added to Cart', '${product.name} has been added to your cart.'); + } + + void loadCartItems() { + var savedItems = box.read('cartItems') ?? []; + cartItems.value = savedItems.map((item) => CartItem.fromJson(item)).toList(); + } + + void saveCartItems() { + box.write('cartItems', cartItems.map((item) => item.toJson()).toList()); + } +} + +class CartItem { + final Product product; + RxInt quantity; + + CartItem({required this.product, required int quantity}) + : quantity = quantity.obs; + + Map toJson() { + return { + 'product': product.toJson(), + 'quantity': quantity.value, + }; + } + + factory CartItem.fromJson(Map json) { + return CartItem( + product: Product.fromJson(json['product']), + quantity: json['quantity'], + ); + } } diff --git a/lib/app/modules/products/views/products_view.dart b/lib/app/modules/products/views/products_view.dart index 5b190a6a..63ea4114 100644 --- a/lib/app/modules/products/views/products_view.dart +++ b/lib/app/modules/products/views/products_view.dart @@ -1,10 +1,7 @@ -// ignore_for_file: inference_failure_on_function_invocation - import 'package:flutter/material.dart'; import 'package:get/get.dart'; - +import 'package:get_flutter_fire/app/routes/app_pages.dart'; import '../../../../models/role.dart'; -import '../../../routes/app_pages.dart'; import '../controllers/products_controller.dart'; class ProductsView extends GetView { @@ -17,10 +14,33 @@ class ProductsView extends GetView { floatingActionButton: (arg != null && Get.rootDelegate.arguments()["role"] == Role.seller) ? FloatingActionButton.extended( - onPressed: controller.loadDemoProductsFromSomeWhere, + onPressed: () { + // Ensure controller is initialized and method exists + if (controller != null) { + controller.loadDemoProductsFromSomeWhere(); + } else { + print('Controller is not initialized.'); + } + }, label: const Text('Add'), + backgroundColor: Colors.pinkAccent, // Theme color for FAB ) : null, + appBar: AppBar( + title: const Text('Perfumes'), + backgroundColor: Colors.pink, // Theme color for AppBar + actions: [ + PopupMenuButton( + onSelected: controller.setCategory, + itemBuilder: (context) => [ + const PopupMenuItem(value: '', child: Text('All')), + const PopupMenuItem(value: 'Women', child: Text('Women')), + const PopupMenuItem(value: 'Men', child: Text('Men')), + const PopupMenuItem(value: 'Unisex', child: Text('Unisex')), + ], + ), + ], + ), body: Column( children: [ const Hero( @@ -31,20 +51,97 @@ class ProductsView extends GetView { child: Obx( () => RefreshIndicator( onRefresh: () async { - controller.products.clear(); - controller.loadDemoProductsFromSomeWhere(); + // Ensure controller is initialized and method exists + if (controller != null) { + controller.products.clear(); + controller.loadDemoProductsFromSomeWhere(); + } else { + print('Controller is not initialized.'); + } }, - child: ListView.builder( - itemCount: controller.products.length, + child: GridView.builder( + padding: const EdgeInsets.all(16), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // Two products per row + crossAxisSpacing: 16, + mainAxisSpacing: 16, + childAspectRatio: 0.75, // Adjust to control height/width ratio + ), + itemCount: controller.filteredProducts.length, itemBuilder: (context, index) { - final item = controller.products[index]; - return ListTile( + final item = controller.filteredProducts[index]; + return GestureDetector( onTap: () { - Get.rootDelegate.toNamed(Routes.PRODUCT_DETAILS( - item.id)); //we could use Get Parameters + Get.rootDelegate.toNamed(Routes.PRODUCT_DETAILS(item.id)); }, - title: Text(item.name), - subtitle: Text(item.id), + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.pink.withOpacity(0.1), // Theme color for shadow + spreadRadius: 2, + blurRadius: 6, + offset: const Offset(0, 4), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + child: item.productImage.isNotEmpty + ? Image.network( + item.productImage, + width: double.infinity, + fit: BoxFit.cover, + ) + : Container( + color: Colors.grey, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.name, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Text( + '${item.description}\n${item.category.isNotEmpty ? item.category : 'No Category'}', + style: const TextStyle( + color: Colors.grey, + fontSize: 14, + ), + ), + const SizedBox(height: 4), + Text( + '₹${item.price.toStringAsFixed(2)}', + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.pink, + fontSize: 16, + ), + ), + ], + ), + ), + ], + ), + ), ); }, ), diff --git a/lib/app/modules/profile/controllers/profile_controller.dart b/lib/app/modules/profile/controllers/profile_controller.dart index 0c1e059e..8fb2c55a 100644 --- a/lib/app/modules/profile/controllers/profile_controller.dart +++ b/lib/app/modules/profile/controllers/profile_controller.dart @@ -1,53 +1,82 @@ +import 'dart:async'; import 'dart:io'; - +import 'dart:typed_data'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_storage/firebase_storage.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; - import 'package:path/path.dart'; import '../../../../services/auth_service.dart'; +import '../../../../models/screens.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; class ProfileController extends GetxController { - FirebaseStorage storage = FirebaseStorage.instance; - User? currentUser = AuthService.to.user; + final FirebaseStorage storage = FirebaseStorage.instance; + final FirebaseAuth auth = FirebaseAuth.instance; + User? get currentUser => auth.currentUser; final Rxn _photoURL = Rxn(); + String? get photoURLValue => _photoURL.value; + - File? _photo; + // Reactive variable to hold display name + final Rxn _displayName = Rxn(); + String? get displayNameValue => _displayName.value; - String? get photoURL => _photoURL.value; + get userPhotoUrl => null; @override - onInit() { + void onInit() { super.onInit(); - _photoURL.value = currentUser!.photoURL; - _photoURL.bindStream(currentUser!.photoURL.obs.stream); + _photoURL.value = currentUser?.photoURL; + _displayName.value = currentUser?.displayName; + listenToUserChanges(); + } + + void listenToUserChanges() { + auth.userChanges().listen((User? user) { + if (user != null) { + _photoURL.value = user.photoURL; + _displayName.value = user.displayName; + } else { + _photoURL.value = null; + _displayName.value = null; + } + }); } - Future uploadFile(String path) async { + Future uploadFile(dynamic fileData, String fileName) async { try { - var byt = GetStorage().read(path); - if (byt != null) { - final fileName = path; - final destination = 'profilePics/${currentUser!.uid}'; - - final ref = storage.ref(destination).child(fileName); - await ref.putData(byt); - return "$destination/$fileName"; + final destination = 'profilePics/${currentUser!.uid}/$fileName'; + final ref = storage.ref(destination); + if (fileData is String && !kIsWeb) { + var byt = GetStorage().read(fileData); + if (byt != null) { + await ref.putData(byt); + } else { + File photo = File(fileData); + await ref.putFile(photo); + } + } else if (fileData is Uint8List) { + await ref.putData(fileData); } else { - _photo = File(path); - if (_photo == null) return null; - final fileName = basename(_photo!.path); - final destination = 'profilePics/${currentUser!.uid}'; - - final ref = storage.ref(destination).child(fileName); - await ref.putFile(_photo!); - return "$destination/$fileName"; + throw ArgumentError('Invalid file data type'); } + return destination; } catch (e) { - Get.snackbar('Error', 'Image Not Uploaded as ${e.toString()}'); + Get.snackbar('Error', 'Image Not Uploaded: ${e.toString()}'); + return null; + } + } + + Future getImageBytes(String path) async { + if (kIsWeb) { + // For web, we'll implement this differently or remove if not needed + return null; + } else { + // For non-web platforms, read the file as bytes + final file = File(path); + return await file.readAsBytes(); } - return null; } void logout() { @@ -55,8 +84,39 @@ class ProfileController extends GetxController { } Future updatePhotoURL(String dest) async { - _photoURL.value = await storage.ref().child(dest).getDownloadURL(); - await currentUser?.updatePhotoURL(_photoURL.value); - Get.snackbar('Success', 'Picture stored and linked'); + try { + String downloadURL = await storage.ref(dest).getDownloadURL(); + await auth.currentUser?.updatePhotoURL(downloadURL); + _photoURL.value = downloadURL; + Get.snackbar('Success', 'Picture stored and linked'); + } catch (e) { + Get.snackbar('Error', 'Failed to update profile picture: ${e.toString()}'); + } + } + + Future updateDisplayName(String newName) async { + try { + await auth.currentUser?.updateDisplayName(newName); + _displayName.value = newName; // Update local reactive state + Get.snackbar('Success', 'Display name updated'); + } catch (e) { + Get.snackbar('Error', 'Failed to update display name: ${e.toString()}'); + } + } + + Future deleteAccount() async { + try { + await auth.currentUser?.delete(); + Get.defaultDialog( + title: 'Account Deleted', + middleText: 'Your account has been successfully deleted.', + onConfirm: () { + logout(); + Get.offAllNamed(Screen.HOME.route); + }, + ); + } catch (e) { + throw Exception('Failed to delete account: ${e.toString()}'); + } } -} +} \ No newline at end of file diff --git a/lib/app/modules/profile/views/profile_view.dart b/lib/app/modules/profile/views/profile_view.dart index c26d11c1..d92d0785 100644 --- a/lib/app/modules/profile/views/profile_view.dart +++ b/lib/app/modules/profile/views/profile_view.dart @@ -1,112 +1,129 @@ -// ignore_for_file: inference_failure_on_function_invocation - import 'package:firebase_ui_auth/firebase_ui_auth.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; - +import 'package:image_picker/image_picker.dart'; import '../../../../services/auth_service.dart'; import '../../../../models/screens.dart'; import '../../../widgets/change_password_dialog.dart'; -import '../../../widgets/image_picker_button.dart'; import '../controllers/profile_controller.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; class ProfileView extends GetView { const ProfileView({super.key}); + ShapeBorder get shape => const CircleBorder(); double get size => 120; Color get placeholderColor => Colors.grey; Widget _imageFrameBuilder( - BuildContext context, - Widget? child, - int? frame, - bool? _, - ) { + BuildContext context, + Widget? child, + int? frame, + bool? _, + ) { if (frame == null) { return Container(color: placeholderColor); } - return child!; } @override Widget build(BuildContext context) { - return Obx(() => profileScreen()); + return Obx(() => profileScreen(context)); } - Widget profileScreen() { + Widget profileScreen(BuildContext context) { return AuthService.to.isLoggedInValue ? ProfileScreen( - // We are using the Flutter Fire Profile Screen now but will change in subsequent steps. - // The issues are highlighted in comments here - - // appBar: AppBar( - // title: const Text('User Profile'), - // ), - avatar: SizedBox( - //null will give the profile image component but it does not refresh the pic when changed - height: size, - width: size, - child: ClipPath( - clipper: ShapeBorderClipper(shape: shape), - clipBehavior: Clip.hardEdge, - child: controller.photoURL != null - ? Image.network( - controller.photoURL!, - width: size, - height: size, - cacheWidth: size.toInt(), - cacheHeight: size.toInt(), - fit: BoxFit.contain, - frameBuilder: _imageFrameBuilder, - ) - : Center( - child: Image.asset( - 'assets/images/dash.png', + appBar: AppBar( + title: const Text('User Profile'), + backgroundColor: Color(0xFFE91E63), // Pink shade color + ), + avatar: GestureDetector( + onTap: () => _pickImage(), + child: SizedBox( + height: size, + width: size, + child: ClipPath( + clipper: ShapeBorderClipper(shape: shape), + clipBehavior: Clip.hardEdge, + child: Obx(() => controller.photoURLValue != null + ? Image.network( + controller.photoURLValue!, width: size, - fit: BoxFit.contain, - ), - ), + height: size, + cacheWidth: size.toInt(), + cacheHeight: size.toInt(), + fit: BoxFit.cover, + frameBuilder: _imageFrameBuilder, + ) + : Center( + child: Image.asset( + 'assets/images/dash.png', + width: size, + fit: BoxFit.contain, + ), + )), + ), ), ), - // showDeleteConfirmationDialog: true, //this does not work properly. Possibly a bug in FlutterFire actions: [ SignedOutAction((context) { - Get.back(); controller.logout(); Get.rootDelegate.toNamed(Screen.PROFILE.route); - // Navigator.of(context).pop(); }), AccountDeletedAction((context, user) { - //If we don't include this the button is still shown but no action gets done. Ideally the button should also not be shown. Its a bug in FlutterFire Get.defaultDialog( - //this is only called after the delete is done and not useful for confirmation of the delete action - title: 'Deleted Account of ${user.displayName}', - barrierDismissible: true, - navigatorKey: Get.nestedKey(Screen.HOME.route), + title: 'Delete Account', + middleText: 'Are you sure you want to delete your account? This action cannot be undone.', + textConfirm: 'Delete', + textCancel: 'Cancel', + confirmTextColor: Colors.white, + buttonColor: Color(0xFFE91E63), // Pink shade color + onConfirm: () async { + Get.back(); + try { + await user.delete(); + Get.defaultDialog( + title: 'Account Deleted', + middleText: 'Your account has been successfully deleted.', + onConfirm: () { + controller.logout(); + Get.offAllNamed(Screen.HOME.route); + }, + buttonColor: Color(0xFFE91E63), // Pink shade color + ); + } catch (e) { + Get.defaultDialog( + title: 'Error', + middleText: 'Failed to delete account: ${e.toString()}', + onConfirm: () => Get.back(), + buttonColor: Color(0xFFE91E63), // Pink shade color + ); + } + }, ); - }) + }), ], children: [ - //This is to show that we can add custom content here const Divider(), - controller.currentUser?.email != null - ? TextButton.icon( - onPressed: callChangePwdDialog, - label: const Text('Change Password'), - icon: const Icon(Icons.password_rounded), - ) - : const SizedBox.shrink(), - ImagePickerButton(callback: (String? path) async { - if (path != null) { - //Upload to Store - String? dest = await controller.uploadFile(path); - //attach it to User imageUrl - if (dest != null) { - await controller.updatePhotoURL(dest); - } - } - }) + if (controller.currentUser?.email != null) + TextButton.icon( + onPressed: () => callChangePwdDialog(), + label: const Text('Change Password'), + icon: const Icon(Icons.password_rounded), + style: TextButton.styleFrom( + foregroundColor: const Color(0xFFE91E63), // Pink shade color + ), + ), + TextButton.icon( + onPressed: () => _pickImage(), + label: const Text('Change Profile Picture'), + icon: const Icon(Icons.image), + style: TextButton.styleFrom( + foregroundColor: Color(0xFFE91E63), // Pink shade color + ), + ), ], ) : const Scaffold(); @@ -115,10 +132,31 @@ class ProfileView extends GetView { void callChangePwdDialog() { var dlg = ChangePasswordDialog(controller.currentUser!); Get.defaultDialog( - title: "Change Password", - content: dlg, - textConfirm: "Submit", - textCancel: "Cancel", - onConfirm: dlg.onSubmit); + title: "Change Password", + content: dlg, + textConfirm: "Submit", + textCancel: "Cancel", + onConfirm: dlg.onSubmit, + buttonColor: Color(0xFFE91E63), // Pink shade color + ); + } + + Future _pickImage() async { + final ImagePicker picker = ImagePicker(); + final XFile? image = await picker.pickImage(source: ImageSource.gallery); + + if (image != null) { + String? dest; + if (kIsWeb) { + final bytes = await image.readAsBytes(); + dest = await controller.uploadFile(bytes, image.name); + } else { + // For mobile/desktop, pass the file path and filename + dest = await controller.uploadFile(image.path, image.name); + } + if (dest != null) { + await controller.updatePhotoURL(dest); + } + } } } diff --git a/lib/app/modules/register/views/register_view.dart b/lib/app/modules/register/views/register_view.dart index 01f73e88..f8bee8e4 100644 --- a/lib/app/modules/register/views/register_view.dart +++ b/lib/app/modules/register/views/register_view.dart @@ -1,53 +1,56 @@ -// import 'package:firebase_ui_auth/firebase_ui_auth.dart'; +import 'dart:async'; // Import this for Timer +import 'package:firebase_ui_auth/firebase_ui_auth.dart'; import 'package:flutter/material.dart'; - import 'package:get/get.dart'; - +import 'package:get_flutter_fire/app/widgets/login_widgets.dart'; import '../../../../services/auth_service.dart'; -// import '../../../widgets/login_widgets.dart'; import '../controllers/register_controller.dart'; -//ALso add a form to take additional info such as display name of other customer details mapped with uid in Firestore class RegisterView extends GetView { const RegisterView({super.key}); @override Widget build(BuildContext context) { - // Add pre verification Form if any. Mostly it can be post verification and can be the Profile or Setting screens - try { - // using this is causing an error when we send verification mail from server side - // if it was initiated once, even when no visible. So we need to dispose when not visible - var w = - // EmailVerificationScreen( - // headerBuilder: LoginWidgets.headerBuilder, - // sideBuilder: LoginWidgets.sideBuilder, - // actions: [ - // EmailVerifiedAction(() { - // AuthService.to.register(); - // }), - // ], - // ); - Scaffold( - appBar: AppBar( - title: const Text('Registeration'), - centerTitle: true, - ), - body: Center( - child: Column(children: [ - const Text( - 'Please verify your email (check SPAM folder), and then relogin', - style: TextStyle(fontSize: 20), - ), - TextButton( - onPressed: () => AuthService.to.register(), - child: const Text("Verification Done. Relogin"), - ) - ])), - ); - return w; - } catch (e) { - // TODO + // Timer to check email verification status + Timer? timer; + + // Function to check email verification status + void checkEmailVerification() async { + final user = AuthService.to.user; + if (user != null) { + await user.reload(); // Reload user to get the latest data + if (user.emailVerified) { + AuthService.to.register(); // Navigate away once email is verified + timer?.cancel(); // Stop the timer + } + } } - return const Scaffold(); + + // Start a timer to periodically check email verification status + timer = Timer.periodic(Duration(seconds: 5), (timer) { + checkEmailVerification(); + }); + + // Display the EmailVerificationScreen initially + var w = EmailVerificationScreen( + headerBuilder: LoginWidgets.headerBuilder, + sideBuilder: LoginWidgets.sideBuilder, + actions: [ + EmailVerifiedAction(() { + // You might want to handle verification done action here, but + // navigation should be handled automatically by the timer. + }), + ], + ); + + return Scaffold( + appBar: AppBar( + title: const Text('Registration'), + centerTitle: true, + ), + body: Center( + child: w, + ), + ); } } diff --git a/lib/app/modules/root/views/drawer.dart b/lib/app/modules/root/views/drawer.dart index 908d0223..ec216cb1 100644 --- a/lib/app/modules/root/views/drawer.dart +++ b/lib/app/modules/root/views/drawer.dart @@ -1,116 +1,216 @@ -// ignore_for_file: inference_failure_on_function_invocation - import 'package:flutter/material.dart'; import 'package:get/get.dart'; - -import '../../../../models/role.dart'; +import 'package:get_flutter_fire/app/modules/utils/size_config.dart'; import '../../../../services/auth_service.dart'; - +import '../../../../models/role.dart'; import '../../../../models/screens.dart'; +import '../../../../utils/size_config.dart'; import '../controllers/my_drawer_controller.dart'; class DrawerWidget extends StatelessWidget { - const DrawerWidget({ - super.key, - }); + const DrawerWidget({super.key}); @override Widget build(BuildContext context) { - MyDrawerController controller = Get.put(MyDrawerController([]), - permanent: true); //must make true else gives error + MyDrawerController controller = + Get.put(MyDrawerController([]), permanent: true); Screen.drawer().then((v) => {controller.values.value = v}); - return Obx(() => Drawer( - //changing the shape of the drawer - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topRight: Radius.circular(0), bottomRight: Radius.circular(20)), - ), - width: 200, - child: Column( - children: drawerItems(context, controller.values), - ), - )); + SizeConfig.init(context); + double drawerWidth = + GetPlatform.isDesktop ? SizeConfig.w * 0.27 : SizeConfig.w * 0.7; + + return Obx(() { + Role userRole = AuthService.to.maxRole; + return LayoutBuilder( + builder: (context, constraints) { + bool isHorizontal = GetPlatform.isDesktop; + + double leftPanelWidth = + isHorizontal ? SizeConfig.w * 0.16 : drawerWidth; + + return Row( + children: [ + if (isHorizontal) + Container( + width: SizeConfig.w * 0.08, + color: Color(0xFFE91E63), // Pink shade color + child: Column( + children: [ + SizedBox(height: SizeConfig.h * 0.1), + IconButton( + icon: const Icon(Icons.dashboard, color: Colors.white), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + if (GetPlatform.isDesktop) + ...userRole.tabs.map((tab) => Padding( + padding: const EdgeInsets.only(top: 8.0), + child: IconButton( + icon: Icon(tab.icon, color: Colors.white), + onPressed: () { + Get.rootDelegate.toNamed(tab.route); + Navigator.of(context).pop(); + }, + ), + )), + ], + ), + ), + SizedBox( + width: leftPanelWidth, + child: Drawer( + backgroundColor: Color(0xFFFCE4EC), // Light pink color + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topRight: Radius.circular(20), + bottomRight: Radius.circular(20), + ), + ), + child: Column( + children: drawerItems(context, controller.values), + ), + ), + ), + ], + ); + }, + ); + }); } List drawerItems(BuildContext context, Rx> values) { + double sWidth = + GetPlatform.isDesktop ? SizeConfig.w * 0.25 : SizeConfig.w * 0.7; List list = [ Container( - height: 100, - color: Colors.red, - //adding content in the highlighted part of the drawer + height: SizeConfig.h * 0.37, + color: Color(0xFFE91E63), // Pink shade color child: Align( - alignment: Alignment.centerLeft, - child: Container( - margin: const EdgeInsets.only(left: 15), - child: const Text('User Name', //Profile Icon also - style: TextStyle(fontWeight: FontWeight.bold)))), - ) - ]; - - if (AuthService.to.maxRole.index > 1) { - for (var i = 0; i <= AuthService.to.maxRole.index; i++) { - Role role = Role.values[i]; - list.add(ListTile( - title: Text( - role.name, - style: const TextStyle( - color: Colors.blue, + alignment: Alignment.centerLeft, + child: Container( + margin: EdgeInsets.only( + left: SizeConfig.h * 0.02, + top: SizeConfig.h * 0.02, + right: SizeConfig.h * 0.02), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(50)), + color: Colors.white, ), + child: SizedBox( + height: sWidth, + width: sWidth, + child: ListView( + children: [ + const SizedBox(height: 10), + CircleAvatar( + radius: 50.5, + backgroundColor: Color(0xFFE91E63), // Pink shade color + child: CircleAvatar( + radius: 50, + backgroundImage: AuthService.to.userPhotoUrl != null + ? NetworkImage(AuthService.to.userPhotoUrl!) + : AssetImage('assets/default_profile.png') + as ImageProvider, + ), + ), + Center( + child: Container( + padding: EdgeInsets.only(top: SizeConfig.h * 0.01), + child: Text( + '${AuthService.to.userName}', + style: const TextStyle( + fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + ), + Center( + child: Text( + AuthService.to.userEmail ?? '', + style: const TextStyle( + fontSize: 15, + color: Color.fromARGB(169, 37, 38, 39)), + ), + ), + const SizedBox(height: 10), + ], + )), ), - onTap: () { - Get.rootDelegate - .toNamed(Screen.HOME.route, arguments: {'role': role}); - //to close the drawer - Navigator.of(context).pop(); - }, - )); - } - } - - for (Screen screen in values.value) { - list.add(ListTile( - title: Text(screen.label ?? ''), - onTap: () { - Get.rootDelegate.toNamed(screen.route); - //to close the drawer - - Navigator.of(context).pop(); + ), + ), + Obx( + () { + return Column( + children: values.value.map((screen) { + return Padding( + padding: EdgeInsets.only(left: SizeConfig.h * 0.01), + child: ListTile( + leading: Icon(screen.icon, + color: Color(0xFFE91E63)), // Pink shade color + title: Text( + screen.label ?? '', + style: const TextStyle(fontWeight: FontWeight.w400), + ), + onTap: () { + Get.rootDelegate.toNamed(screen.route); + Navigator.of(context).pop(); + }, + ), + ); + }).toList(), + ); }, - )); - } + ), + ]; if (AuthService.to.isLoggedInValue) { - list.add(ListTile( - title: const Text( - 'Logout', - style: TextStyle( - color: Colors.red, + list.add(const Spacer(flex: 1)); + list.add( + Padding( + padding: EdgeInsets.only(left: SizeConfig.h * 0.01), + child: ListTile( + leading: const Icon( + Icons.logout_sharp, + color: Color(0xFFE91E63), // Pink shade color + ), + title: const Text( + 'Logout', + style: TextStyle( + color: Color(0xFFE91E63), + fontWeight: FontWeight.w400), // Pink shade color + ), + onTap: () { + AuthService.to.logout(); + Get.rootDelegate.toNamed(Screen.LOGIN.route); + Navigator.of(context).pop(); + }, ), ), - onTap: () { - AuthService.to.logout(); - Get.rootDelegate.toNamed(Screen.LOGIN.route); - //to close the drawer - - Navigator.of(context).pop(); - }, - )); - } - if (!AuthService.to.isLoggedInValue) { - list.add(ListTile( - title: const Text( - 'Login', - style: TextStyle( - color: Colors.blue, + ); + list.add(SizedBox(height: SizeConfig.h * 0.02)); + } else { + list.add(const Spacer(flex: 1)); + list.add( + Padding( + padding: EdgeInsets.only(left: SizeConfig.h * 0.01), + child: ListTile( + leading: const Icon( + Icons.login_sharp, + color: Color(0xFFE91E63), // Pink shade color + ), + title: const Text( + 'Login', + style: TextStyle( + color: Color(0xFFE91E63), // Pink shade color + ), + ), + onTap: () { + Get.rootDelegate.toNamed(Screen.LOGIN.route); + Navigator.of(context).pop(); + }, ), ), - onTap: () { - Get.rootDelegate.toNamed(Screen.LOGIN.route); - //to close the drawer - - Navigator.of(context).pop(); - }, - )); + ); } return list; diff --git a/lib/app/modules/root/views/root_view.dart b/lib/app/modules/root/views/root_view.dart index 2bbf228c..51e717f5 100644 --- a/lib/app/modules/root/views/root_view.dart +++ b/lib/app/modules/root/views/root_view.dart @@ -31,7 +31,7 @@ class RootView extends GetView { ) : IconButton( icon: ImageIcon( - const AssetImage("icons/logo.png"), + const AssetImage("assets/icons/logo.png"), color: Colors.grey.shade800, ), onPressed: () => AuthService.to.isLoggedInValue diff --git a/lib/app/modules/settings/controllers/settings_controller.dart b/lib/app/modules/settings/controllers/settings_controller.dart index 265e54b1..31595981 100644 --- a/lib/app/modules/settings/controllers/settings_controller.dart +++ b/lib/app/modules/settings/controllers/settings_controller.dart @@ -1,20 +1,124 @@ +import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:get_storage/get_storage.dart'; class SettingsController extends GetxController { - //TODO: Implement SettingsController + final GetStorage box = GetStorage(); + final RxBool isDarkMode = false.obs; + final Rx selectedPersona = Rx(null); + + final Persona solitaireMale = Persona( + name: 'Solitaire Male', + primaryColor: Colors.blue, + secondaryColor: Colors.blueAccent, + backgroundColor: Colors.blue[50]!, + textColor: Colors.blue, + icon: Icons.male, + ); + + final Persona solitaireFemale = Persona( + name: 'Solitaire Female', + primaryColor: Colors.pink, + secondaryColor: Colors.pinkAccent, + backgroundColor: Colors.pink[50]!, + textColor: Colors.pink, + icon: Icons.female, + ); - final count = 0.obs; @override void onInit() { super.onInit(); + loadSettings(); } - @override - void onReady() { - super.onReady(); + void loadSettings() { + try { + isDarkMode.value = box.read('isDarkMode') ?? false; + final savedPersonaName = box.read('selectedPersona'); + selectedPersona.value = savedPersonaName == 'Solitaire Male' + ? solitaireMale + : savedPersonaName == 'Solitaire Female' + ? solitaireFemale + : null; + updateTheme(); + } catch (e) { + print('Error loading settings: $e'); + isDarkMode.value = false; + selectedPersona.value = null; + } } - @override - void onClose() {} - void increment() => count.value++; + void toggleDarkMode(bool value) { + isDarkMode.value = value; + if (value) { + selectedPersona.value = null; // Disable persona when dark mode is selected + } + box.write('isDarkMode', value); + updateTheme(); + } + + void selectPersona(Persona persona) { + selectedPersona.value = persona; + isDarkMode.value = false; // Disable dark mode when persona is selected + box.write('selectedPersona', persona.name); + box.write('isDarkMode', false); + updateTheme(); + } + + void updateTheme() { + try { + final themeData = isDarkMode.value + ? ThemeData.dark() + : selectedPersona.value != null + ? ThemeData( + primaryColor: selectedPersona.value!.primaryColor, + scaffoldBackgroundColor: selectedPersona.value!.backgroundColor, + textTheme: TextTheme( + bodyLarge: TextStyle(color: selectedPersona.value!.textColor), + bodyMedium: TextStyle(color: selectedPersona.value!.textColor.withOpacity(0.7)), + ), + appBarTheme: AppBarTheme( + backgroundColor: selectedPersona.value!.primaryColor, + foregroundColor: Colors.white, + ), + ) + : ThemeData( + primaryColor: const Color(0xFF5D3A69), // Rich Plum + scaffoldBackgroundColor: const Color(0xFFF5F5DC), // Soft Cream + hintColor: const Color(0xFFD4AF37), // Antique Gold + textTheme: const TextTheme( + bodyLarge: TextStyle(color: Color(0xFF333333)), // Charcoal Gray + bodyMedium: TextStyle(color: Color.fromARGB(255, 63, 59, 59)), + + ), + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFF5D3A69), // Rich Plum + foregroundColor: Colors.white, + ), + ); + + Get.changeTheme(themeData); + } catch (e) { + print('Error updating theme: $e'); + Get.changeTheme(ThemeData.light()); // Fallback to light theme + } + } +} + +class Persona { + final String name; + final Color primaryColor; + final Color secondaryColor; + final Color backgroundColor; + final Color textColor; + final IconData icon; // Icon for the persona + + Persona({ + required this.name, + required this.primaryColor, + required this.secondaryColor, + required this.backgroundColor, + required this.textColor, + required this.icon, + }); } diff --git a/lib/app/modules/settings/views/settings_view.dart b/lib/app/modules/settings/views/settings_view.dart index 2bb244b6..b44b7770 100644 --- a/lib/app/modules/settings/views/settings_view.dart +++ b/lib/app/modules/settings/views/settings_view.dart @@ -1,20 +1,125 @@ import 'package:flutter/material.dart'; - import 'package:get/get.dart'; - -import '../controllers/settings_controller.dart'; +import 'package:get_flutter_fire/app/modules/settings/controllers/settings_controller.dart'; class SettingsView extends GetView { const SettingsView({super.key}); @override Widget build(BuildContext context) { - return const Scaffold( - body: Center( - child: Text( - 'SettingsView is working', - style: TextStyle(fontSize: 20), - ), + return Scaffold( + appBar: AppBar( + title: const Text('Settings'), + ), + body: ListView( + padding: const EdgeInsets.all(16), + children: [ + Obx(() => SwitchListTile( + title: const Text('Dark Mode'), + value: controller.isDarkMode.value, + onChanged: (value) { + controller.toggleDarkMode(value); + }, + )), + const SizedBox(height: 20), + Text( + 'Select Persona', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => controller.selectPersona(controller.solitaireMale), + child: Container( + width: 140, // Adjusted width for a larger square + height: 140, // Adjusted height for a larger square + decoration: BoxDecoration( + color: controller.solitaireMale.backgroundColor, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + controller.solitaireMale.icon, + color: controller.solitaireMale.textColor, + size: 50, // Adjusted icon size + ), + SizedBox(height: 10), + Text( + controller.solitaireMale.name, + style: TextStyle( + color: controller.solitaireMale.textColor, + fontWeight: FontWeight.bold, + ), + ), + if (controller.selectedPersona.value == controller.solitaireMale) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Icon(Icons.check, color: controller.solitaireMale.primaryColor), + ), + ], + ), + ), + ), + ), + const SizedBox(width: 10), // Minimal spacing between boxes + GestureDetector( + onTap: () => controller.selectPersona(controller.solitaireFemale), + child: Container( + width: 140, // Adjusted width for a larger square + height: 140, // Adjusted height for a larger square + decoration: BoxDecoration( + color: controller.solitaireFemale.backgroundColor, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + controller.solitaireFemale.icon, + color: controller.solitaireFemale.textColor, + size: 50, // Adjusted icon size + ), + SizedBox(height: 10), + Text( + controller.solitaireFemale.name, + style: TextStyle( + color: controller.solitaireFemale.textColor, + fontWeight: FontWeight.bold, + ), + ), + if (controller.selectedPersona.value == controller.solitaireFemale) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Icon(Icons.check, color: controller.solitaireFemale.primaryColor), + ), + ], + ), + ), + ), + ), + ], + ), + ], ), ); } diff --git a/lib/app/modules/utils/size_config.dart b/lib/app/modules/utils/size_config.dart new file mode 100644 index 00000000..9470d795 --- /dev/null +++ b/lib/app/modules/utils/size_config.dart @@ -0,0 +1,11 @@ +import 'package:flutter/widgets.dart'; + +class SizeConfig { + static late double h; + static late double w; + + static void init(BuildContext context) { + h = MediaQuery.of(context).size.height; + w = MediaQuery.of(context).size.width; + } +} \ No newline at end of file diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 7269755d..d2746556 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -69,7 +69,7 @@ class AppPages { binding: ProfileBinding(), ), Screen.SETTINGS.getPage( - page: () => const SettingsView(), + page: () => SettingsView(), binding: SettingsBinding(), ), Screen.HOME.getPage( @@ -109,7 +109,7 @@ class AppPages { binding: CategoriesBinding(), ), Screen.CART.getPage( - page: () => const CartView(), + page: () => CartView(), binding: CartBinding(), role: Role.buyer, children: [ diff --git a/lib/app/widgets/cartProduct.dart b/lib/app/widgets/cartProduct.dart new file mode 100644 index 00000000..1675d21f --- /dev/null +++ b/lib/app/widgets/cartProduct.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:get_flutter_fire/models/cart_item.dart' as model; // Alias for CartItem +import '../modules/cart/controllers/cart_controller.dart'; + +class CartCard extends GetWidget { + final model.CartItem cartItem; // Use the model.CartItem + + CartCard({required this.cartItem}); + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + final cardWidth = screenWidth * 0.9; + + return Obx(() => Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: cardWidth, + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + cartItem.product.productImage, + width: 80, + height: 80, + fit: BoxFit.cover, + ), + ), + SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + cartItem.product.name, + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ), + + SizedBox(height: 30), + Text( + '\₹${(cartItem.product.price * cartItem.quantity.value).toStringAsFixed(2)}', + style: TextStyle(fontSize: 14, color: Colors.green), + ), + ], + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon(Icons.close), + onPressed: () => controller.removeItem(cartItem), + ), + Row( + children: [ + IconButton( + style: ButtonStyle( + side: WidgetStateProperty.all(BorderSide(color: Colors.grey, width: 1.0)), + shape: WidgetStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + )), + ), + icon: Icon(Icons.remove), + onPressed: () => controller.updateQuantity(cartItem, cartItem.quantity.value - 1), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + cartItem.quantity.value.toString(), + style: TextStyle(fontSize: 16), + ), + ), + IconButton( + style: ButtonStyle( + side: WidgetStateProperty.all(BorderSide(color: Colors.grey, width: 1.0)), + shape: WidgetStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + )), + ), + icon: Icon(Icons.add), + onPressed: () => controller.updateQuantity(cartItem, cartItem.quantity.value + 1), + ), + ], + ), + ], + ), + ], + ), + ), + )); + } +} diff --git a/lib/main.dart b/lib/main.dart index 30c258f2..f62c0480 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,10 @@ -// ignore_for_file: inference_failure_on_instance_creation - -import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; - +import 'package:firebase_core/firebase_core.dart'; +import 'app/modules/settings/controllers/settings_controller.dart'; +import 'app/modules/cart/controllers/cart_controller.dart'; +import 'app/modules/products/controllers/products_controller.dart'; import 'app/routes/app_pages.dart'; import 'firebase_options.dart'; import 'services/auth_service.dart'; @@ -16,29 +16,86 @@ void main() async { options: DefaultFirebaseOptions.currentPlatform, ); + // Initialize settings controller to load saved settings before running the app + final settingsController = Get.put(SettingsController()); + + // Run the app runApp( GetMaterialApp.router( - debugShowCheckedModeBanner: - false, //the debug banner will automatically disappear in prod build + debugShowCheckedModeBanner: false, title: 'Application', initialBinding: BindingsBuilder( () { Get.put(AuthService()); + Get.put(ProductsController()); + Get.put(CartController()); }, ), getPages: AppPages.routes, - // routeInformationParser: GetInformationParser( - // // initialRoute: Routes.HOME, - // ), - // routerDelegate: GetDelegate( - // backButtonPopMode: PopMode.History, - // preventDuplicateHandlingMode: - // PreventDuplicateHandlingMode.ReorderRoutes, - // ), theme: ThemeData( - highlightColor: Colors.black.withOpacity(0.5), - bottomSheetTheme: - const BottomSheetThemeData(surfaceTintColor: Colors.blue)), + primaryColor: pastelGold, + scaffoldBackgroundColor: pastelBackground, + appBarTheme: AppBarTheme( + backgroundColor: pastelGold, + foregroundColor: Colors.black, + iconTheme: IconThemeData(color: Colors.black), + titleTextStyle: TextStyle( + color: Colors.black, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + buttonTheme: ButtonThemeData( + buttonColor: pastelGold, + textTheme: ButtonTextTheme.primary, + ), + floatingActionButtonTheme: FloatingActionButtonThemeData( + backgroundColor: pastelGold, + ), + colorScheme: ColorScheme.light( + primary: pastelGold, + onPrimary: Colors.black, + secondary: pastelBeige, + onSecondary: Colors.black, + background: pastelBackground, + surface: pastelCream, + onSurface: pastelBrown, + ), + textTheme: TextTheme( + bodyLarge: TextStyle(color: pastelBrown), + bodyMedium: TextStyle(color: pastelBrown), + displayLarge: TextStyle(color: pastelGold), + displayMedium: TextStyle(color: pastelGold), + labelLarge: TextStyle(color: Colors.black), + ), + cardTheme: CardTheme( + color: pastelCream, + shadowColor: pastelBrown.withOpacity(0.3), + margin: EdgeInsets.all(8.0), + ), + inputDecorationTheme: InputDecorationTheme( + filled: true, + fillColor: pastelCream, + border: OutlineInputBorder( + borderSide: BorderSide(color: pastelBrown), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: pastelGold), + ), + labelStyle: TextStyle(color: pastelBrown), + ), + iconTheme: IconThemeData(color: pastelBrown), + dividerColor: pastelBrown.withOpacity(0.5), + ), + darkTheme: ThemeData.dark(), // Define dark theme if needed + themeMode: settingsController.isDarkMode.value ? ThemeMode.dark : ThemeMode.light, ), ); } + +// Golden Pastel Colors +const Color pastelGold = Color(0xFFF4D06F); +const Color pastelCream = Color(0xFFFDF6E3); +const Color pastelBeige = Color(0xFFE7CBA9); +const Color pastelBrown = Color(0xFF8D775F); +const Color pastelBackground = Color(0xFFF7EFE5); diff --git a/lib/models/cart_item.dart b/lib/models/cart_item.dart new file mode 100644 index 00000000..1fba29d6 --- /dev/null +++ b/lib/models/cart_item.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; +import 'product.dart'; + +class CartItem { + final Product product; + final RxInt quantity; + + CartItem({ + required this.product, + required this.quantity, + }); +} diff --git a/lib/models/product.dart b/lib/models/product.dart index 003d5785..6a885967 100644 --- a/lib/models/product.dart +++ b/lib/models/product.dart @@ -1,9 +1,39 @@ class Product { - final String name; final String id; + final String name; + final double price; + final String productImage; + final String category; + final String description; Product({ - required this.name, required this.id, + required this.name, + required this.price, + required this.productImage, + required this.category, + required this.description, }); -} + + Map toJson() { + return { + 'id': id, + 'name': name, + 'price': price, + 'productImage': productImage, + 'category': category, + 'description': description, + }; + } + + factory Product.fromJson(Map json) { + return Product( + id: json['id'], + name: json['name'], + price: json['price'], + productImage: json['productImage'], + category: json['category'], + description: json['description'], + ); + } +} \ No newline at end of file diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 8bf72aaa..305bdef7 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -5,6 +5,8 @@ import 'package:firebase_ui_auth/firebase_ui_auth.dart' as fbui; import 'package:firebase_ui_localizations/firebase_ui_localizations.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:get_flutter_fire/app/routes/app_pages.dart'; +import 'package:get_flutter_fire/models/access_level.dart'; import '../models/screens.dart'; import '../constants.dart'; @@ -17,18 +19,19 @@ class AuthService extends GetxService { late Rxn credential = Rxn(); final Rxn _firebaseUser = Rxn(); final Rx _userRole = Rx(Role.buyer); - final Rx robot = RxBool(useRecaptcha); + final RxBool robot = RxBool(useRecaptcha); final RxBool registered = false.obs; User? get user => _firebaseUser.value; Role get maxRole => _userRole.value; @override - onInit() { + void onInit() { super.onInit(); if (useEmulator) _auth.useAuthEmulator(emulatorHost, 9099); _firebaseUser.bindStream(_auth.authStateChanges()); - _auth.authStateChanges().listen((User? user) { + _auth.userChanges().listen((User? user) { + _firebaseUser.value = user; if (user != null) { user.getIdTokenResult().then((token) { _userRole.value = Role.fromString(token.claims?["role"]); @@ -48,12 +51,35 @@ class AuthService extends GetxService { bool get isAnon => user != null && user!.isAnonymous; - String? get userName => (user != null && !user!.isAnonymous) - ? (user!.displayName ?? user!.email) + String? get userName => (user != null) + ? (user!.displayName ?? (user!.isAnonymous ? 'Guest' : user!.email)) : 'Guest'; + String? get userPhotoUrl => user?.photoURL; + String? get userEmail => user?.email; + + Future updatePhotoURL(String photoURL) async { + try { + await user?.updatePhotoURL(photoURL); + // Refresh the user to ensure the latest data is available. + await user?.reload(); + _firebaseUser.value = + _auth.currentUser; // Update the Rxn to trigger listeners + Get.snackbar("Success", "Profile picture updated successfully"); + } catch (e) { + Get.snackbar("Error", "Failed to update profile picture: $e"); + } + } + + void updateProfileImage(String imagePath) async { + try { + await AuthService.to.updatePhotoURL(imagePath); + } catch (e) { + Get.snackbar("Error", "Failed to update profile picture: $e"); + } + } void login() { - // this is not needed as we are using Firebase UI for the login part + // Not needed as we use Firebase UI for login } void sendVerificationMail({EmailAuthCredential? emailAuth}) async { @@ -84,21 +110,11 @@ class AuthService extends GetxService { } } - void sendSingInLink(EmailAuthCredential emailAuth) { + void sendSignInLink(EmailAuthCredential emailAuth) { var acs = ActionCodeSettings( - // URL you want to redirect back to. The domain (www.example.com) for this - // URL must be whitelisted in the Firebase Console. url: '$baseUrl:5001/flutterfast-92c25/us-central1/handleEmailLinkVerification', - // // This must be true if deep linking. - // // If deeplinking. See [https://firebase.google.com/docs/dynamic-links/flutter/receive] handleCodeInApp: true, - // iOSBundleId: '$bundleID.ios', - // androidPackageName: '$bundleID.android', - // // installIfNotAvailable - // androidInstallApp: true, - // // minimumVersion - // androidMinimumVersion: '12' ); _auth .sendSignInLinkToEmail(email: emailAuth.email, actionCodeSettings: acs) @@ -109,11 +125,10 @@ class AuthService extends GetxService { void register() { registered.value = true; - // logout(); // Uncomment if we need to enforce relogin final thenTo = Get.rootDelegate.currentConfiguration!.currentPage!.parameters?['then']; Get.rootDelegate - .offAndToNamed(thenTo ?? Screen.PROFILE.route); //Profile has the forms + .offAndToNamed(thenTo ?? Screen.PROFILE.route); // Profile has the forms } void logout() { @@ -122,33 +137,51 @@ class AuthService extends GetxService { _firebaseUser.value = null; } + + + Future guest() async { return await Get.defaultDialog( - middleText: 'Sign in as Guest', + middleText: 'Sign in Required', barrierDismissible: true, - onConfirm: loginAsGuest, - onCancel: () => Get.back(result: false), - textConfirm: 'Yes, will SignUp later', - textCancel: 'No, will SignIn now'); + onConfirm: () { + Get.rootDelegate.toNamed(Screen.LOGIN.route); + Get.back(result: false); + }, + onCancel: () { + Get.back(result: true); // Keeps the user as a guest + }, + textConfirm: 'Yes, will SignIn Now', + textCancel: 'No, will SignUp Later'); } - void loginAsGuest() async { + void guestlogin({String? name}) async { try { - await FirebaseAuth.instance.signInAnonymously(); - Get.back(result: true); + final UserCredential userCredential = await _auth.signInAnonymously(); + + print("Signed in with temporary account."); + + if (name != null && name.isNotEmpty) { + await userCredential.user?.updateDisplayName(name); + await userCredential.user?.reload(); + _firebaseUser.value = + _auth.currentUser; // Ensure the current user is updated + } + Get.snackbar( 'Alert!', - 'Signed in with temporary account.', + 'Signed in anonymously${name != null ? ' as $name' : ''}.', ); } on FirebaseAuthException catch (e) { switch (e.code) { case "operation-not-allowed": print("Anonymous auth hasn't been enabled for this project."); + Get.snackbar('Error', 'Anonymous authentication is not enabled.'); break; default: - print("Unknown error."); + print("Unknown error: ${e.message}"); + Get.snackbar('Error', 'An unknown error occurred: ${e.message}'); } - Get.back(result: false); } } @@ -158,12 +191,9 @@ class AuthService extends GetxService { (BuildContext context, FirebaseAuthException e) { final defaultLabels = FirebaseUILocalizations.labelsOf(context); - // for verification error, also set a boolean flag to trigger button visibility to resend verification mail String? verification; if (e.code == "internal-error" && e.message!.contains('"status":"UNAUTHENTICATED"')) { - // Note that (possibly in Emulator only) the e.email is always coming as null - // String? email = e.email ?? parseEmail(e.message!); callback(true, credential.value); verification = "Please verify email id by clicking the link on the email sent"; @@ -181,6 +211,52 @@ class AuthService extends GetxService { }; }; } + + // Phone Authentication Methods + Future pnVerify( + String phoneNumber, + Function(String) onCodeSent, + Function(String) onVerificationCompleted) async { + await _auth.verifyPhoneNumber( + phoneNumber: phoneNumber, + verificationCompleted: (PhoneAuthCredential credential) async { + // Sign in the user automatically once verification is completed + await _auth.signInWithCredential(credential); + _firebaseUser.value = _auth.currentUser; + // Notify the caller that verification is completed + onVerificationCompleted(_auth.currentUser?.uid ?? ''); + }, + verificationFailed: (FirebaseAuthException e) { + // Display an error message if verification fails + Get.snackbar('Error', 'Failed to verify phone number: ${e.message}'); + }, + codeSent: (String verificationId, int? resendToken) { + // Return the verification ID to the caller + onCodeSent(verificationId); + }, + codeAutoRetrievalTimeout: (String verificationId) { + // Handle the timeout of auto-retrieval + }, + ); + } + + Future pnsignin( + String verificationId, String smsCode) async { + try { + // Create a credential for sign-in using the provided verification ID and SMS code + final credential = PhoneAuthProvider.credential( + verificationId: verificationId, + smsCode: smsCode, + ); + // Attempt to sign in with the generated credential + await _auth.signInWithCredential(credential); + _firebaseUser.value = _auth.currentUser; + } catch (e) { + // Show an error message if sign-in fails + Get.snackbar('Error', 'Sign-in process failed: $e'); + } + } + } class MyCredential extends AuthCredential { @@ -198,4 +274,4 @@ parseEmail(String message) { int i = message.indexOf('"message":') + 13; int j = message.indexOf('"', i); return message.substring(i, j - 1); -} +} \ No newline at end of file diff --git a/lib/utils/size_config.dart b/lib/utils/size_config.dart new file mode 100644 index 00000000..b9f78bb0 --- /dev/null +++ b/lib/utils/size_config.dart @@ -0,0 +1 @@ +// TODO Implement this library. \ No newline at end of file diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig new file mode 100644 index 00000000..118b51cd --- /dev/null +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -0,0 +1,11 @@ +// This is a generated file; do not edit or check into version control. +FLUTTER_ROOT=E:\flutter +FLUTTER_APPLICATION_PATH=E:\get-flutter\get-flutter-fire +COCOAPODS_PARALLEL_CODE_SIGN=true +FLUTTER_BUILD_DIR=build +FLUTTER_BUILD_NAME=1.0.0 +FLUTTER_BUILD_NUMBER=1 +DART_OBFUSCATION=false +TRACK_WIDGET_CREATION=true +TREE_SHAKE_ICONS=false +PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh new file mode 100644 index 00000000..610b0523 --- /dev/null +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# This is a generated file; do not edit or check into version control. +export "FLUTTER_ROOT=E:\flutter" +export "FLUTTER_APPLICATION_PATH=E:\get-flutter\get-flutter-fire" +export "COCOAPODS_PARALLEL_CODE_SIGN=true" +export "FLUTTER_BUILD_DIR=build" +export "FLUTTER_BUILD_NAME=1.0.0" +export "FLUTTER_BUILD_NUMBER=1" +export "DART_OBFUSCATION=false" +export "TRACK_WIDGET_CREATION=true" +export "TREE_SHAKE_ICONS=false" +export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/pubspec.lock b/pubspec.lock index 877fc75e..86ceb96c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,18 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "37a42d06068e2fe3deddb2da079a8c4d105f241225ba27b7122b37e9865fd8f7" + sha256: b1595874fbc8f7a50da90f5d8f327bb0bfd6a95dc906c390efe991540c3b54aa url: "https://pub.dev" source: hosted - version: "1.3.35" + version: "1.3.40" + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.dev" + source: hosted + version: "3.6.1" args: dependency: transitive description: @@ -33,6 +41,38 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "4a5d8d2c728b0f3d0245f69f921d7be90cae4c2fd5288f773088672c0893f819" + url: "https://pub.dev" + source: hosted + version: "3.4.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + sha256: "7b006ec356205054af5beaef62e2221160ea36b90fb70a35e4deacd49d0349ae" + url: "https://pub.dev" + source: hosted + version: "5.0.0" characters: dependency: transitive description: @@ -61,18 +101,18 @@ packages: dependency: transitive description: name: cross_file - sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" url: "https://pub.dev" source: hosted - version: "0.3.4+1" + version: "0.3.4+2" crypto: dependency: transitive description: name: crypto - sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" cupertino_icons: dependency: "direct main" description: @@ -109,18 +149,26 @@ packages: dependency: transitive description: name: ffi - sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" file_picker: dependency: "direct main" description: name: file_picker - sha256: "29c90806ac5f5fb896547720b73b17ee9aed9bba540dc5d91fe29f8c5745b10a" + sha256: "825aec673606875c33cd8d3c4083f1a3c3999015a84178b317b7ef396b7384f3" url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "8.0.7" file_selector_linux: dependency: transitive description: @@ -149,154 +197,154 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" url: "https://pub.dev" source: hosted - version: "0.9.3+1" + version: "0.9.3+2" firebase_analytics: dependency: "direct main" description: name: firebase_analytics - sha256: dbf1e7ab22cfb1f4a4adb103b46a26276b4edc593d4a78ef6fb942bafc92e035 + sha256: "064e5b57b0693305946b7caa6a80ed80a918f46804c247b6cd7ed9cd327df48f" url: "https://pub.dev" source: hosted - version: "10.10.7" + version: "11.2.1" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: "3729b74f8cf1d974a27ba70332ecb55ff5ff560edc8164a6469f4a055b429c37" + sha256: d094547c9022c404b5ca39b7209607fc80e75e39d38875f050508fa4346b3e74 url: "https://pub.dev" source: hosted - version: "3.10.8" + version: "4.2.1" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "019cd7eee74254d33fbd2e29229367ce33063516bf6b3258a341d89e3b0f1655" + sha256: "06dc023b0144c0df630a56b6262cc9e7d6069fe78148853d97614dbefb6ea923" url: "https://pub.dev" source: hosted - version: "0.5.7+7" + version: "0.5.9+1" firebase_auth: dependency: "direct main" description: name: firebase_auth - sha256: f0a75f61992d036e4c46ad0e9febd364d98aa2c092690a5475cb1421a8243cfe + sha256: "2457ac6cbc152fa464aad3fb35f98039b0c4ab8e9bedf476672508b291bdbc3a" url: "https://pub.dev" source: hosted - version: "4.19.5" + version: "5.1.4" firebase_auth_platform_interface: dependency: transitive description: name: firebase_auth_platform_interface - sha256: feb77258404309ffc7761c78e1c0ad2ed5e4fdc378e035619e2cc13be4397b62 + sha256: "0408e2ed74b1afa0490a93aa041fe90d7573af7ffc59a641edc6c5b5c1b8d2a4" url: "https://pub.dev" source: hosted - version: "7.2.6" + version: "7.4.3" firebase_auth_web: dependency: transitive description: name: firebase_auth_web - sha256: "6d527f357da2bf93a67a42b423aa92943104a0c290d1d72ad9a42c779d501cd2" + sha256: "7e0c6d0fa8c5c1b2ae126a78f2d1a206a77a913f78d20f155487bf746162dccc" url: "https://pub.dev" source: hosted - version: "5.11.5" + version: "5.12.5" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "26de145bb9688a90962faec6f838247377b0b0d32cc0abecd9a4e43525fc856c" + sha256: "3187f4f8e49968573fd7403011dca67ba95aae419bc0d8131500fae160d94f92" url: "https://pub.dev" source: hosted - version: "2.32.0" + version: "3.3.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + sha256: "3c3a1e92d6f4916c32deea79c4a7587aa0e9dbbe5889c7a16afcf005a485ee02" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.2.0" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "43d9e951ac52b87ae9cc38ecdcca1e8fa7b52a1dd26a96085ba41ce5108db8e9" + sha256: e8d1e22de72cb21cdcfc5eed7acddab3e99cd83f3b317f54f7a96c32f25fd11e url: "https://pub.dev" source: hosted - version: "2.17.0" + version: "2.17.4" firebase_dynamic_links: dependency: transitive description: name: firebase_dynamic_links - sha256: f704859abc17d99e74b47eaf47455b45a88ab7e2973f03e6130ff666b45fe11f + sha256: f094b1f90951981328abdb39c4140c0b0590c8d56fe23a9ad987b654a33000d0 url: "https://pub.dev" source: hosted - version: "5.5.5" + version: "6.0.4" firebase_dynamic_links_platform_interface: dependency: transitive description: name: firebase_dynamic_links_platform_interface - sha256: f86992605b50e2f0ce6c24993430affc98021da8d8a74d5596b7a2c84196c110 + sha256: a9af616ec8e33c739b12153c420d16d6987532e13c8d764a0f20a64031bb93f1 url: "https://pub.dev" source: hosted - version: "0.2.6+33" + version: "0.2.6+40" firebase_remote_config: dependency: "direct main" description: name: firebase_remote_config - sha256: "653bd94b68e2c4e89eca10db90576101f1024151f39f2d4e7c64ae6a90a5f9c5" + sha256: "62e86ed64370c382a2f872fbcabcae591c404776eb84685eb535bab53c0c00d5" url: "https://pub.dev" source: hosted - version: "4.4.7" + version: "5.0.4" firebase_remote_config_platform_interface: dependency: transitive description: name: firebase_remote_config_platform_interface - sha256: "24a2c445b15de3af7e4582ebceb2aa9a1e3731d0202cb3e7a1e03012440fa07d" + sha256: "80973fa763b7c9a0fc0596afed7063f2378de2cf2d37b017254e613160b43135" url: "https://pub.dev" source: hosted - version: "1.4.35" + version: "1.4.40" firebase_remote_config_web: dependency: transitive description: name: firebase_remote_config_web - sha256: "525aa3000fd27cd023841c802010a06515e564aab2f147aa964b35f54abbf449" + sha256: "14ba362bdcf7abda12fa9060f2ebae7d342153e4d619007071e98cd557ce29a3" url: "https://pub.dev" source: hosted - version: "1.6.7" + version: "1.6.12" firebase_storage: dependency: "direct main" description: name: firebase_storage - sha256: da76ca9c11d795c4bae1bd13b31d54bb9eb9ccbee7eb5f6b86b8294370e9d488 + sha256: "9c837ed303aea5fb7d1f4ad097f6ab092310c33af9cd290e738c65670f3ce7f1" url: "https://pub.dev" source: hosted - version: "11.7.5" + version: "12.1.3" firebase_storage_platform_interface: dependency: transitive description: name: firebase_storage_platform_interface - sha256: be17bfa9110a6429b40dd3760c755034079fd734aa1dd2476d5638ab780cc508 + sha256: "889db34d0369f7c0f3b40af058d6e1ea5a6622e0a4592483bf5923327071f5d0" url: "https://pub.dev" source: hosted - version: "5.1.20" + version: "5.1.27" firebase_storage_web: dependency: transitive description: name: firebase_storage_web - sha256: "5219c20c0768a8e2ffedf0a116b7bc80ab32fcc6e2cbd50cbde14f8c4575c3f4" + sha256: "95aeed3265f5aacee98f8ede842bae5b34360fe8a5f09b2a811688f3ac7f7c99" url: "https://pub.dev" source: hosted - version: "3.9.5" + version: "3.9.12" firebase_ui_auth: dependency: "direct main" description: name: firebase_ui_auth - sha256: "62c3ce9c8da134e0780bf8ed7d7ed91dd2308596ee3cb56fab03eb79f8323479" + sha256: "36be86d964b16d5025108519ccdc8baf5143d59bc9d0d113d6386b41b5a775e4" url: "https://pub.dev" source: hosted - version: "1.14.0" + version: "1.15.0" firebase_ui_localizations: dependency: "direct main" description: @@ -309,18 +357,18 @@ packages: dependency: transitive description: name: firebase_ui_oauth - sha256: b86458b7d403d48d335c2502004bf88f2145d2f0be58b29fba8de28944f7fa91 + sha256: "31eb35172f1cfc02f60a1b7048ee9a248964427794b5ddcae27147725068c3db" url: "https://pub.dev" source: hosted - version: "1.5.2" + version: "1.5.3" firebase_ui_oauth_google: dependency: "direct main" description: name: firebase_ui_oauth_google - sha256: "101c13ba7ac04f6b70e1d73ab6d063b82801b794394914655356eb2a6f18bbcb" + sha256: "79ddc76efffc3853bd9a94c7dadb9342f0f8c489d051951635a0ee9ed7b677c6" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" firebase_ui_shared: dependency: transitive description: @@ -329,19 +377,35 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_lints: dependency: "direct dev" description: name: flutter_lints - sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "4.0.0" flutter_localizations: dependency: transitive description: flutter @@ -351,10 +415,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" url: "https://pub.dev" source: hosted - version: "2.0.19" + version: "2.0.22" flutter_svg: dependency: transitive description: @@ -373,6 +437,14 @@ packages: description: flutter source: sdk version: "0.0.0" + g_recaptcha_v3: + dependency: "direct main" + description: + name: g_recaptcha_v3 + sha256: b493d9bbad64bb4631a2b7bb86f7b4c6c6a3c2b327729c4130b3c95817bedf29 + url: "https://pub.dev" + source: hosted + version: "0.0.6" get: dependency: "direct main" description: @@ -401,10 +473,10 @@ packages: dependency: transitive description: name: google_identity_services_web - sha256: "9482364c9f8b7bd36902572ebc3a7c2b5c8ee57a9c93e6eb5099c1a9ec5265d8" + sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6" url: "https://pub.dev" source: hosted - version: "0.3.1+1" + version: "0.3.1+4" google_sign_in: dependency: "direct main" description: @@ -417,10 +489,10 @@ packages: dependency: transitive description: name: google_sign_in_android - sha256: "7647893c65e6720973f0e579051c8f84b877b486614d9f70a404259c41a4632e" + sha256: "5a47ebec9af97daf0822e800e4f101c3340b5ebc3f6898cf860c1a71b53cf077" url: "https://pub.dev" source: hosted - version: "6.1.23" + version: "6.1.28" google_sign_in_ios: dependency: transitive description: @@ -441,18 +513,18 @@ packages: dependency: transitive description: name: google_sign_in_web - sha256: fc0f14ed45ea616a6cfb4d1c7534c2221b7092cc4f29a709f0c3053cc3e821bd + sha256: "042805a21127a85b0dc46bba98a37926f17d2439720e8a459d27045d8ef68055" url: "https://pub.dev" source: hosted - version: "0.12.4" + version: "0.12.4+2" http: - dependency: transitive + dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: @@ -465,34 +537,34 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "33974eca2e87e8b4e3727f1b94fa3abcb25afe80b6bc2c4d449a0e150aedf720" + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "79455f6cff4cbef583b2b524bbf0d4ec424e5959f4d464e36ef5323715b98370" + sha256: c0a6763d50b354793d0192afd0a12560b823147d3ded7c6b77daf658fa05cc85 url: "https://pub.dev" source: hosted - version: "0.8.12" + version: "0.8.12+13" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3" + sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.5" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: cb0db0ec0d3e2cd49674f2e6053be25ccdb959832607c1cbd215dd6cf10fb0dd + sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" url: "https://pub.dev" source: hosted - version: "0.8.11" + version: "0.8.12" image_picker_linux: dependency: transitive description: @@ -533,22 +605,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -561,10 +641,18 @@ packages: dependency: transitive description: name: lints - sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + lottie: + dependency: "direct main" + description: + name: lottie + sha256: "6a24ade5d3d918c306bb1c21a6b9a04aab0489d51a2582522eea820b4093b62b" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.2" matcher: dependency: transitive description: @@ -577,18 +665,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -597,6 +685,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: "direct main" description: @@ -617,18 +713,18 @@ packages: dependency: transitive description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.10" path_provider_foundation: dependency: transitive description: @@ -657,10 +753,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" petitparser: dependency: transitive description: @@ -673,10 +769,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -685,6 +781,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" sky_engine: dependency: transitive description: flutter @@ -698,6 +802,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + url: "https://pub.dev" + source: hosted + version: "2.3.3+1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e" + url: "https://pub.dev" + source: hosted + version: "2.5.4+2" stack_trace: dependency: transitive description: @@ -722,6 +850,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: a824e842b8a054f91a728b783c177c1e4731f6b124f9192468457a8913371255 + url: "https://pub.dev" + source: hosted + version: "3.2.0" term_glyph: dependency: transitive description: @@ -734,10 +870,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" typed_data: dependency: transitive description: @@ -746,6 +882,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" + url: "https://pub.dev" + source: hosted + version: "4.4.2" vector_graphics: dependency: transitive description: @@ -782,10 +926,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" web: dependency: transitive description: @@ -794,14 +938,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" - win32: + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: ec81f57aa1611f8ebecf1d2259da4ef052281cb5ad624131c93546c79ccc7736 + url: "https://pub.dev" + source: hosted + version: "4.9.0" + webview_flutter_android: dependency: transitive + description: + name: webview_flutter_android + sha256: "6e64fcb1c19d92024da8f33503aaeeda35825d77142c01d0ea2aa32edc79fdc8" + url: "https://pub.dev" + source: hosted + version: "3.16.7" + 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_plus: + dependency: "direct main" + description: + name: webview_flutter_plus + sha256: "57ec757eada4e23bfb015f5d5f84a45108cb2c29b1e77e23956768cd5e0c8468" + url: "https://pub.dev" + source: hosted + version: "0.4.7" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: "1942a12224ab31e9508cf00c0c6347b931b023b8a4f0811e5dec3b06f94f117d" + url: "https://pub.dev" + source: hosted + version: "3.15.0" + win32: + dependency: "direct main" description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.4" xdg_directories: dependency: transitive description: @@ -819,5 +1003,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.3.4 <4.0.0" - flutter: ">=3.19.2" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2909a374..7c5c8d87 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,23 +10,30 @@ dependencies: get: 4.6.6 flutter: sdk: flutter - firebase_core: ^2.31.0 + firebase_core: ^3.3.0 firebase_ui_auth: ^1.14.0 - firebase_auth: ^4.19.5 + firebase_auth: ^5.1.4 google_sign_in: ^6.2.1 firebase_ui_oauth_google: ^1.3.2 google_fonts: ^6.2.1 - firebase_storage: ^11.7.5 + firebase_storage: ^12.1.3 image_picker: ^1.1.1 file_picker: ^8.0.3 path: ^1.9.0 get_storage: ^2.1.1 firebase_ui_localizations: ^1.12.0 - firebase_remote_config: ^4.4.7 - firebase_analytics: ^10.10.7 + firebase_remote_config: ^5.0.4 + firebase_analytics: ^11.2.1 + g_recaptcha_v3: ^0.0.6 + webview_flutter_plus: ^0.4.5 + lottie: ^3.1.2 + carousel_slider: ^5.0.0 + http: ^1.2.2 + win32: ^5.5.4 + cached_network_image: ^3.3.1 dev_dependencies: - flutter_lints: 3.0.2 + flutter_lints: ^4.0.0 flutter_test: sdk: flutter @@ -36,8 +43,7 @@ flutter: fonts: - asset: packages/firebase_ui_auth/fonts/SocialIcons.ttf assets: - - assets/images/flutterfire_300x.png - - assets/images/dash.png - - assets/icons/logo.png - uses-material-design: true - + - assets/ + - assets/images/ + - assets/icons/ + uses-material-design: true \ No newline at end of file