diff --git a/README.md b/README.md
index 451d649a..68250c04 100644
--- a/README.md
+++ b/README.md
@@ -1,132 +1,40 @@
-# get-flutter-fire
-
-This codebase provides a boilerplate code utilizing the following three technologies:
-
-1. Flutter 3.0 - For UX and uses Dart languange. See [https://flutter.dev/]
-2. GetX - State management for Flutter. See [https://github.com/jonataslaw/getx/tree/4.6.1]
-3. Firebase - For Backend as a Service. See [https://firebase.google.com/]
- 1. Easy Authentication flow
- 2. Server side functions
- 3. Remote Configurations which can be used for A/B testing
-
-This was created as part of my own learning process and you will find that git commits were made according to the Steps listed below. You can use the git version history to check each commit and learn step by step, as I did.
-
-I am also using this codebase as an experiment towards hiring people (freshers especially but not limited to them) for my development team. If you are in Mumbai and are interested to join my team, you can use this codebase in the following manner:
-
-* Fork the codebase
-* Add your own firebase_options.dart (follow steps and see firebase_options.template)
-* **Build your own application using this as a base (integrating any existing project of yours also works)**, or complete a TODO or fix a bug (only if you have no other ideas)
-* Send me a Pull Request. Mention a way of connecting with you in the commit message along with details of commit. Also modify ReadMe to say what you have changed in detail.
-* I will go through the request and then connect with you if I find the entry to be interesting and take an interview round.
-
-## The Steps
-
-Step 1: Use Get CLI [https://pub.dev/packages/get_cli]
-
-`get create project`
-
-Step 2: Copy code from [https://github.com/jonataslaw/getx/tree/4.6.1/example_nav2/lib]
-
-Step 3: Integrate FlutterFire Authentication
-
-- Tutorials [https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps#0] for inspiration
-- Firebase Documentation [https://firebase.google.com/docs/auth/flutter/start]
-- Blog [www.medium.com/TBD]
-- To compile the code ensure that you generate your own firebase_options.dart by running
-
- `flutterfire configure`
-
-Step 4: Add Google OAuth [https://firebase.google.com/codelabs/firebase-auth-in-flutter-apps#6]. Note ensure you do the steps for Android and iOS as the code for it is not in Github
-
-Step 5: Add Guest User/Anonymous login with a Cart and Checkout use case [https://firebase.google.com/docs/auth/flutter/anonymous-auth]
-
-* delete unlinked anonymous user post logout
-
-Step 6: Add ImagePicker and Firebase Storage for profile image
-
-* Create PopupMenu button for web [https://api.flutter.dev/flutter/material/PopupMenuButton-class.html]
-* BottomSheet for phones and single file button for desktops
-* GetX and Image Picker [https://stackoverflow.com/questions/66559553/flutter-imagepicker-with-getx]
-* Add FilePicker [https://medium.com/@onuaugustine07/pick-any-file-file-picker-flutter-f82c0144e27c]
-* Firebase Storage [https://mercyjemosop.medium.com/select-and-upload-images-to-firebase-storage-flutter-6fac855970a9] and [https://firebase.google.com/docs/storage/flutter/start]
-
- Modify the Firebase Rules
- `service firebase.storage { match /b/{bucket}/o { match /{allPaths=**} { allow write: if request.auth.uid != null; allow read: if true; } } }`
-
- Fix CORS [https://stackoverflow.com/questions/37760695/firebase-storage-and-access-control-allow-origin]
-* PList additions [https://medium.com/unitechie/flutter-tutorial-image-picker-from-camera-gallery-c27af5490b74]
-
-Step 7: Additional Auth flow items
-
-1. Add a Change Password Screen. The Flutter Fire package does not have this screen.
-2. TODO: Add ReCaptcha to login flow for password authentication for Web only
- * Phone Auth on Web has a ReCaptcha already [https://firebase.flutter.dev/docs/auth/phone/]. Tried to use that library but it is very cryptic.
- * Use the following instead [https://stackoverflow.com/questions/60675575/how-to-implement-recaptcha-into-a-flutter-app] or [https://medium.com/cloudcraftz/securing-your-flutter-web-app-with-google-recaptcha-b556c567f409] or [https://pub.dev/packages/g_recaptcha_v3]
-3. TODO: Ensure Reset Password has Email verification
-4. TODO: Add Phone verification [https://firebase.google.com/docs/auth/flutter/phone-auth]
- * See [https://github.com/firebase/flutterfire/issues/4189].
-5. TODO: Add 2FA with SMS Pin. This screen is available in the Flutter Fire package
-
-Step 8: Add Firebase Emulator to test on laptop instead of server so that we can use Functions without upgrading the plan to Blaze. See [https://firebase.google.com/docs/emulator-suite/install_and_configure]
-
-Step 9: Add User Roles using Custom Claims. This requires upgrade of plan as we need to use Firebase Functions. Instead using Emulator.
-
-1. In Emulator we can add user via http://127.0.0.1:4000/auth and add custom claim via UI as {"role":"admin"}. The effect is shown via Product page in Nav instead of Cart page for admin user.
-2. Add Function to add the custom claim to make the first user the admin using the Admin SDK
-3. Registeration form to collect some data post signUp and enforce email verification from Client side.
-
- * Note! for Emulator check the console to verify using the link provided as email is not sent.
-4. Enforcing verify email using a button which appears when SignIn fails due to non verification.
-
- * Fixed the error handling message during login.
- * Coverted server side to Typescript
- * Enabled Resend verification mail button
- * Approach 1 - Use Email Link Authentication and signIn, assuming it marks email as verified also. We cannot send the verification mail as is, since that can be sent only if signed in (which was allowed only for first login post signup)
- * Refer https://firebase.google.com/docs/auth/flutter/email-link-auth
- * TODO Enable Deep Linking: According to https://firebase.google.com/docs/dynamic-links/flutter/receive, the Flutter feature is being deprecated and we should use the AppLinks (Android), UniversalLinks(iOS) instead. Leaving this for future as adding complexity.
- * We could use the server side handling instead of deep linking. See [https://firebase.google.com/docs/auth/custom-email-handler?hl=en&authuser=0#web]. However, this requires changing the email template for the URL which is not possible in Emulator. Using the continueURL instead does not work as oobCode is already expired. This handling also uses the web client sdk. Thus it is better to go with the below method instead.
- * Approach 2 - (Hack) send a create request with suffix ".verify" added in email when button clicked. Use the server side beforeCreate to catch this and send verification mail
- * Note that the Server side beforeCreate function can also bypass user creation till verification but the user record (esp password) needs to be stored anyways, so bypassing user creation is not a good idea. Instead, we should use the verified tag in subsequent processing
- * Sending emails from server side is possible but by using the web client SDK.
-5. TODO: Other Items
-
- * TODO: Using autocomplete for emails is throwing error log in terminal. No impact but need to fix when all is done.
- * TODO: Add a job that removes all unverified users after a while automatically. This could be useful if you were victim of bot attacks, but adding the Recaptcha is better precaution. See [https://stackoverflow.com/questions/67148672/how-to-delete-unverified-e-mail-addresses-in-firebase-authentication-flutter/67150606#67150606]
-6. Added Roles of Buyer and Seller.
-
- 1. Added Access level in increasing order of role order => Buyer then Seller then Admin
- 2. Created Navigation for each of Admin, Buyer, Seller screens
- 3. Allowed switch from lower role Navigation to Navigation view till the given role of the user
-
-Step 10: Firebase Remote Config for A/B testing. See [https://firebase.google.com/docs/remote-config]
-
-1. Complete the Screen enum based Navigation framework
-2. Config useBottomSheetForProfileOptions for Navigation element to be one of the following
- * False: Drawer for Account, Settings, Sign Out
- * True: Hamburger that opens BottomSheet (Context Menu in larger screen) for the same options
-3. TODO: Config for adding Search Bar at the Top vs a Bottom Navigation button
-
-Step 11: TODO: CRUD
-
-* Users request role upgrade
-* Add this request data to Firebase Datastore
-* Create ListView with slidable tiles for approvals
-* Admin SDK used by admin user via workflow on this request data and is approved from app
- * Allow a Plan attribute via Custome Claims (e.g. Premium user flag) for Buyer and Seller, to add features which are not Navigation linked. Add a button Upgrade to Plan in Drawer that leads to Payment screen. Also certain aspects of premium plan can be visible that leads to upgrade plan screen via middleware
-* Nested Category, Sub-Category tree creation
-
-Step 12: TODO: Theming and Custom Settings
-
-* Add Persona (like that in Netflix) and create a Persona selection only for Buyer Role
-* Add Minimal (Three Color Gradient Pallette) Material Theme. Align it with Persona Templates (like Kids Template in Netflix)
-* Dark theme toggle setting based on each Persona of the logged in User
-
-Step 13: TODO: Large vs Small screen responsiveness
-
-* Drawer: Triggered by Top Left Icon (App Logo). For iOS this icon changes to back button when required. Contains allowed Role List, Screens specified as Drawer. Becomes Left Side Navigation for Horizontal Screens. Can have additional extreme left vertical Navigation Strip. Bottom Navigation Bar also folds into this strip in Horizontal Screens.
-* Top Right Icon: used for Login and post Login triggers BottomSheet/Context Menu for Persona Change, Profile, Settings, Change Password, Logout
-* Search Bar (Toggle Button for phones) on Top Center with Title
-* Status Bottom Bar for desktops only instead of SnackBars
-* FAB vs Main Menu
-
-Step 14: TODO: Make own login flow screens. Remove firebase library reference from all but auth_service
+# Rest Quest
+An Airbnb-like mobile application built using Flutter boilerplate for seamless booking and property management.
+1) Integrated a custom splash screen for an enhanced user experience during app launch.
+2) Added a dynamic search bar for quick and easy property searches.
+3) Implemented check-in/check-out date selection with a calendar view and a user-friendly bar for selecting the number of guests.
+
+
+
+4) Enabled navigation to detailed accommodation information upon tapping on an accommodation listing.
+
+
+
+5) Fixed bugs in the 'showDeletedConfirmationDialog' and 'AccountDeletedAction' for improved stability and user experience.
+6) Enhanced image handling by incorporating a placeholder image or loading indicator during image fetching for smoother user experience.
+7) Implemented Google OAuth for secure and convenient user authentication.
+8) Added SnackBar notifications to confirm when a user adds a stay to the wishlist or completes checkout.
+9) Corrected Cross-Origin Resource Sharing (CORS) issues in Firebase Storage to ensure proper functionality across different domains.
+
+
+
+
+
+# Name: Arshia Rajesh Yadav
+# College: K.J Somaiya College Of Engineering
+# Roll No: 16010321013
+# Email: arshia.yadav@somaiya.edu
+# Phone no: 9819975349
diff --git a/android/app/google-services.json b/android/app/google-services.json
new file mode 100644
index 00000000..be5c8912
--- /dev/null
+++ b/android/app/google-services.json
@@ -0,0 +1,54 @@
+{
+ "project_info": {
+ "project_number": "367198788310",
+ "project_id": "flutterfire-63994",
+ "storage_bucket": "flutterfire-63994.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:367198788310:android:f3c3a4b97d6e804c240777",
+ "android_client_info": {
+ "package_name": "com.share.getflutterfire"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "367198788310-svn5pmeeuuhp1ifdkqreae2hitl4krct.apps.googleusercontent.com",
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.share.getflutterfire",
+ "certificate_hash": "2512537fa01a2063fed80acdfe63d15c8348e109"
+ }
+ },
+ {
+ "client_id": "367198788310-90l1ret3kd6acs32rfl2plpb0g0bhvtt.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyDAY9IpqQhfCbo3Taupo1n6mxscQTw2lmE"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "367198788310-90l1ret3kd6acs32rfl2plpb0g0bhvtt.apps.googleusercontent.com",
+ "client_type": 3
+ },
+ {
+ "client_id": "367198788310-rbm6ka80bqismo58r7b8hc52ma75q5de.apps.googleusercontent.com",
+ "client_type": 2,
+ "ios_info": {
+ "bundle_id": "com.Arshia.getflutterfire"
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/android/app/src/main/kotlin/com/example/temp/MainActivity.kt b/android/app/src/main/kotlin/com/example/temp/MainActivity.kt
new file mode 100644
index 00000000..87d7f7d3
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/temp/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.example.temp
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/assets/Eva/E1.jpeg b/assets/Eva/E1.jpeg
new file mode 100644
index 00000000..e62c89f4
Binary files /dev/null and b/assets/Eva/E1.jpeg differ
diff --git a/assets/Eva/E2.jpg b/assets/Eva/E2.jpg
new file mode 100644
index 00000000..5c4e73c3
Binary files /dev/null and b/assets/Eva/E2.jpg differ
diff --git a/assets/Fiona/F1.jpeg b/assets/Fiona/F1.jpeg
new file mode 100644
index 00000000..f2fa785f
Binary files /dev/null and b/assets/Fiona/F1.jpeg differ
diff --git a/assets/Fiona/F2.jpeg b/assets/Fiona/F2.jpeg
new file mode 100644
index 00000000..3a43d330
Binary files /dev/null and b/assets/Fiona/F2.jpeg differ
diff --git a/assets/Fiona/F3.jpeg b/assets/Fiona/F3.jpeg
new file mode 100644
index 00000000..543022d6
Binary files /dev/null and b/assets/Fiona/F3.jpeg differ
diff --git a/assets/Fiona/F4.png b/assets/Fiona/F4.png
new file mode 100644
index 00000000..0d26894c
Binary files /dev/null and b/assets/Fiona/F4.png differ
diff --git a/assets/Flora/FL1.jpeg b/assets/Flora/FL1.jpeg
new file mode 100644
index 00000000..b36ae25b
Binary files /dev/null and b/assets/Flora/FL1.jpeg differ
diff --git a/assets/Flora/FL2.jpeg b/assets/Flora/FL2.jpeg
new file mode 100644
index 00000000..b0c3ddbf
Binary files /dev/null and b/assets/Flora/FL2.jpeg differ
diff --git a/assets/Flora/FL3.jpeg b/assets/Flora/FL3.jpeg
new file mode 100644
index 00000000..5504fa67
Binary files /dev/null and b/assets/Flora/FL3.jpeg differ
diff --git a/assets/Historia/H1.jpeg b/assets/Historia/H1.jpeg
new file mode 100644
index 00000000..49f534a6
Binary files /dev/null and b/assets/Historia/H1.jpeg differ
diff --git a/assets/Historia/H2.jpeg b/assets/Historia/H2.jpeg
new file mode 100644
index 00000000..82a059c9
Binary files /dev/null and b/assets/Historia/H2.jpeg differ
diff --git a/assets/Historia/H3.jpeg b/assets/Historia/H3.jpeg
new file mode 100644
index 00000000..220de998
Binary files /dev/null and b/assets/Historia/H3.jpeg differ
diff --git a/assets/Plush/P1.jpeg b/assets/Plush/P1.jpeg
new file mode 100644
index 00000000..ea54251c
Binary files /dev/null and b/assets/Plush/P1.jpeg differ
diff --git a/assets/Plush/P2.jpeg b/assets/Plush/P2.jpeg
new file mode 100644
index 00000000..88c3e8ca
Binary files /dev/null and b/assets/Plush/P2.jpeg differ
diff --git a/assets/Plush/P3.jpeg b/assets/Plush/P3.jpeg
new file mode 100644
index 00000000..1dadf48e
Binary files /dev/null and b/assets/Plush/P3.jpeg differ
diff --git a/assets/Plush/P4.jpeg b/assets/Plush/P4.jpeg
new file mode 100644
index 00000000..6c36adbe
Binary files /dev/null and b/assets/Plush/P4.jpeg differ
diff --git a/assets/Stay/S1.jpeg b/assets/Stay/S1.jpeg
new file mode 100644
index 00000000..216eb801
Binary files /dev/null and b/assets/Stay/S1.jpeg differ
diff --git a/assets/Stay/S2.jpeg b/assets/Stay/S2.jpeg
new file mode 100644
index 00000000..240f948a
Binary files /dev/null and b/assets/Stay/S2.jpeg differ
diff --git a/assets/icons/logo.png b/assets/icons/logo.png
index 77ffea42..bf9a6e28 100644
Binary files a/assets/icons/logo.png and b/assets/icons/logo.png differ
diff --git a/assets/images/dash.png b/assets/images/dash.png
deleted file mode 100644
index 6ccb3f39..00000000
Binary files a/assets/images/dash.png and /dev/null differ
diff --git a/assets/images/flutterfire_300x.png b/assets/images/flutterfire_300x.png
deleted file mode 100644
index 604593b8..00000000
Binary files a/assets/images/flutterfire_300x.png and /dev/null differ
diff --git a/image.png b/image.png
new file mode 100644
index 00000000..a76b00e9
Binary files /dev/null and b/image.png differ
diff --git a/images/Signin.jpg b/images/Signin.jpg
new file mode 100644
index 00000000..dabdb31f
Binary files /dev/null and b/images/Signin.jpg differ
diff --git a/images/checkin.jpg b/images/checkin.jpg
new file mode 100644
index 00000000..2fb699b1
Binary files /dev/null and b/images/checkin.jpg differ
diff --git a/images/checkout.jpg b/images/checkout.jpg
new file mode 100644
index 00000000..e48bfe81
Binary files /dev/null and b/images/checkout.jpg differ
diff --git a/images/productDetails1.jpg b/images/productDetails1.jpg
new file mode 100644
index 00000000..bb49ef88
Binary files /dev/null and b/images/productDetails1.jpg differ
diff --git a/images/productDetails3.jpg b/images/productDetails3.jpg
new file mode 100644
index 00000000..ec23918c
Binary files /dev/null and b/images/productDetails3.jpg differ
diff --git a/images/productdetails2.jpg b/images/productdetails2.jpg
new file mode 100644
index 00000000..7764d70f
Binary files /dev/null and b/images/productdetails2.jpg differ
diff --git a/images/searchbar.jpg b/images/searchbar.jpg
new file mode 100644
index 00000000..72dd47b8
Binary files /dev/null and b/images/searchbar.jpg differ
diff --git a/images/splashscreen.jpg b/images/splashscreen.jpg
new file mode 100644
index 00000000..332a946f
Binary files /dev/null and b/images/splashscreen.jpg differ
diff --git a/images/ui.jpg b/images/ui.jpg
new file mode 100644
index 00000000..796f9e11
Binary files /dev/null and b/images/ui.jpg differ
diff --git a/images/wishlist.jpg b/images/wishlist.jpg
new file mode 100644
index 00000000..8227f175
Binary files /dev/null and b/images/wishlist.jpg differ
diff --git a/lib/app/modules/cart/controllers/cart_controller.dart b/lib/app/modules/cart/controllers/cart_controller.dart
index c938ec4c..16a4e2bd 100644
--- a/lib/app/modules/cart/controllers/cart_controller.dart
+++ b/lib/app/modules/cart/controllers/cart_controller.dart
@@ -1,9 +1,31 @@
import 'package:get/get.dart';
+import '../../../../models/product_details.dart';
class CartController extends GetxController {
- //TODO: Implement CartController
+ // List to store cart items
+ var cartItems = [].obs;
+
+ // Method to add a product to the cart
+ void addProductToCart(Product product) {
+ cartItems.add(product);
+ Get.snackbar('Added to Cart', '${product.name} has been added to your cart.', snackPosition: SnackPosition.BOTTOM);
+ }
+
+ // Method to remove a product from the cart
+ void removeProductFromCart(Product product) {
+ cartItems.remove(product);
+ Get.snackbar('Removed from Cart', '${product.name} has been removed from your cart.', snackPosition: SnackPosition.BOTTOM);
+ }
+
+ // Method to clear the cart
+ void clearCart() {
+ cartItems.clear();
+ Get.snackbar('Cart Cleared', 'All items have been removed from your cart.', snackPosition: SnackPosition.BOTTOM);
+ }
+
+ // Get the total price of items in the cart
+ double get totalPrice => cartItems.fold(0, (sum, item) => sum + item.price);
- final count = 0.obs;
@override
void onInit() {
super.onInit();
@@ -18,6 +40,4 @@ class CartController extends GetxController {
void onClose() {
super.onClose();
}
-
- void increment() => count.value++;
}
diff --git a/lib/app/modules/cart/views/cart_view.dart b/lib/app/modules/cart/views/cart_view.dart
index 3e048c79..049ab09e 100644
--- a/lib/app/modules/cart/views/cart_view.dart
+++ b/lib/app/modules/cart/views/cart_view.dart
@@ -1,27 +1,161 @@
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 '../../../routes/app_pages.dart';
import '../controllers/cart_controller.dart';
class CartView extends GetView {
const CartView({super.key});
+
@override
Widget build(BuildContext context) {
- return ScreenWidget(
+ return Scaffold(
appBar: AppBar(
- title: Text('${AuthService.to.userName} Cart'),
- centerTitle: true,
- ),
- body: const Center(
- child: Text(
- 'CartView is working',
- style: TextStyle(fontSize: 20),
+ title: Text(
+ '${AuthService.to.userName}\'s Wishlist',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 25.0,
+ color: Colors.white,
+ ),
),
+ centerTitle: true,
+ backgroundColor: Colors.pinkAccent[100],
),
- screen: screen!,
+ body: Obx(() {
+ if (controller.cartItems.isEmpty) {
+ return Center(
+ child: Text(
+ 'Your wishlist is empty',
+ style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
+ ),
+ );
+ }
+
+ return ListView.builder(
+ padding: const EdgeInsets.all(8.0),
+ itemCount: controller.cartItems.length,
+ itemBuilder: (context, index) {
+ final item = controller.cartItems[index];
+ return Card(
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(15),
+ ),
+ elevation: 4,
+ margin: const EdgeInsets.symmetric(vertical: 8.0),
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(8.0),
+ child: Image.asset(
+ item.imageAsset,
+ height: 80,
+ width: 80,
+ fit: BoxFit.cover,
+ ),
+ ),
+ SizedBox(width: 16),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ item.name,
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ SizedBox(height: 4),
+ Text(
+ 'Location: ${item.location}',
+ style: TextStyle(
+ fontSize: 14,
+ color: Colors.grey[600],
+ ),
+ ),
+ SizedBox(height: 4),
+ Text(
+ 'Price: Rs ${item.price} night',
+ style: TextStyle(
+ fontSize: 16,
+ color: Colors.green[700],
+ ),
+ ),
+ ],
+ ),
+ ),
+ IconButton(
+ icon: Icon(Icons.delete, color: Colors.red),
+ onPressed: () {
+ controller.removeProductFromCart(item);
+ },
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }),
+ bottomNavigationBar: Obx(() {
+ return Container(
+ padding: const EdgeInsets.all(16.0),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ boxShadow: [
+ BoxShadow(
+ color: Colors.grey.withOpacity(0.5),
+ spreadRadius: 5,
+ blurRadius: 7,
+ offset: Offset(0, 3),
+ ),
+ ],
+ ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Text(
+ 'Total:',
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ Text(
+ 'Rs ${controller.totalPrice}',
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ color: Colors.green[700],
+ ),
+ ),
+ ElevatedButton(
+ onPressed: () {
+ Get.toNamed(Routes.CHECKOUT);
+ },
+ child: Text(
+ 'Checkout',
+ style: TextStyle(
+ fontSize: 18,
+ color: Colors.white,
+ ),
+ ),
+ style: ElevatedButton.styleFrom(
+ padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
+ backgroundColor: Colors.pinkAccent[100],
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }),
);
}
}
diff --git a/lib/app/modules/checkout/controllers/checkout_controller.dart b/lib/app/modules/checkout/controllers/checkout_controller.dart
index aa1265f6..f6bdef3e 100644
--- a/lib/app/modules/checkout/controllers/checkout_controller.dart
+++ b/lib/app/modules/checkout/controllers/checkout_controller.dart
@@ -1,23 +1,18 @@
import 'package:get/get.dart';
+import '../../cart/controllers/cart_controller.dart';
class CheckoutController extends GetxController {
- //TODO: Implement CheckoutController
+ final CartController cartController = Get.find();
- final count = 0.obs;
- @override
- void onInit() {
- super.onInit();
- }
+ void processCheckout() {
- @override
- void onReady() {
- super.onReady();
- }
+ // For demonstration, we're just clearing the cart
+ cartController.clearCart();
- @override
- void onClose() {
- super.onClose();
- }
+ // Show a confirmation message
+ Get.snackbar('Success', 'Your Booking is successful!', snackPosition: SnackPosition.BOTTOM);
- void increment() => count.value++;
-}
+ // Navigate to a different page or home screen after checkout
+ Get.offAllNamed('/home');
+ }
+}
\ No newline at end of file
diff --git a/lib/app/modules/checkout/views/checkout_view.dart b/lib/app/modules/checkout/views/checkout_view.dart
index b8b17072..f55a2258 100644
--- a/lib/app/modules/checkout/views/checkout_view.dart
+++ b/lib/app/modules/checkout/views/checkout_view.dart
@@ -1,22 +1,73 @@
import 'package:flutter/material.dart';
-
import 'package:get/get.dart';
-
import '../controllers/checkout_controller.dart';
+import '../../cart/controllers/cart_controller.dart';
class CheckoutView extends GetView {
- const CheckoutView({super.key});
+ final CartController cartController = Get.find();
+
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
- title: const Text('CheckoutView'),
+ title: Text(
+ 'Checkout',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 25.0,
+ color: Colors.white,
+ ),
+ ),
centerTitle: true,
+ backgroundColor: Colors.pinkAccent[100],
),
- body: const Center(
- child: Text(
- 'CheckoutView is working',
- style: TextStyle(fontSize: 20),
+ body: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: ListView.builder(
+ itemCount: cartController.cartItems.length,
+ itemBuilder: (context, index) {
+ final item = cartController.cartItems[index];
+ return Card(
+ margin: const EdgeInsets.symmetric(vertical: 8.0),
+ child: ListTile(
+ leading: Image.asset(
+ item.imageAsset,
+ width: 80,
+ height: 80,
+ fit: BoxFit.cover,
+ ),
+ title: Text(item.name),
+ subtitle: Text('Price: Rs ${item.price}'),
+ ),
+ );
+ },
+ ),
+ ),
+ SizedBox(height: 16.0),
+ Text(
+ 'Total: Rs ${cartController.totalPrice}',
+ style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
+ ),
+ SizedBox(height: 16.0),
+ ElevatedButton(
+ onPressed: () {
+ Get.find().processCheckout();
+ },
+ child: Text('Complete Booking'),
+ style: ElevatedButton.styleFrom(
+ padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
+ backgroundColor: Colors.pinkAccent[100],
+ textStyle: TextStyle(
+ fontSize: 18,
+ color: Colors.white
+ ),
+ ),
+ ),
+ ],
),
),
);
diff --git a/lib/app/modules/home/controllers/home_controller.dart b/lib/app/modules/home/controllers/home_controller.dart
index f058de2a..a2630eb6 100644
--- a/lib/app/modules/home/controllers/home_controller.dart
+++ b/lib/app/modules/home/controllers/home_controller.dart
@@ -1,14 +1,28 @@
import 'package:get/get.dart';
-
import '../../../../models/role.dart';
import '../../../../services/auth_service.dart';
class HomeController extends GetxController {
+ //observable role that tracks the current role of the user
final Rx chosenRole = Rx(AuthService.to.maxRole);
+ // Observable to track the selected index of the bottom navigation bar
+ final RxInt selectedIndex = 0.obs;
+
// Role get role => AuthService.to.maxRole;
- get isBuyer => chosenRole.value == Role.buyer;
+ bool get isGuest => chosenRole.value == Role.guest;
+ bool get isRegisteredUser => chosenRole.value == Role.registeredUser;
+ bool get isAdmin => chosenRole.value == Role.admin;
- get isAdmin => chosenRole.value == Role.admin;
+ //Method to update the role (when the user logs in)
+ void setRole(Role role){
+ chosenRole.value = role;
+ }
+ @override
+ void onInit() {
+ super.onInit();
+ // Example: Initialize with a role from the AuthService or other logic
+ chosenRole.value = AuthService.to.maxRole;
+ }
}
diff --git a/lib/app/modules/home/views/home_view.dart b/lib/app/modules/home/views/home_view.dart
index 0cfc040d..79a880cd 100644
--- a/lib/app/modules/home/views/home_view.dart
+++ b/lib/app/modules/home/views/home_view.dart
@@ -1,34 +1,97 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../routes/app_pages.dart';
-import '../../../widgets/screen_widget.dart';
import '../controllers/home_controller.dart';
+import '../../products/views/products_view.dart';
+import '../../products/controllers/products_controller.dart';
class HomeView extends GetView {
const HomeView({super.key});
@override
Widget build(BuildContext context) {
- return GetRouterOutlet.builder(
- builder: (context, delegate, currentRoute) {
- var arg = Get.rootDelegate.arguments();
- if (arg != null) {
- controller.chosenRole.value = arg["role"];
- }
- var route = controller.chosenRole.value.tabs[0].route;
- //This router outlet handles the appbar and the bottom navigation bar
- return ScreenWidget(
- screen: screen!,
- body: GetRouterOutlet(
- initialRoute: route,
- // anchorRoute: Routes.HOME,
- key: Get.nestedKey(route),
+ Get.put(ProductsController());
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(
+ 'RestQuest',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 25.0,
+ color: Colors.white,
),
- role: controller.chosenRole.value,
- delegate: delegate,
- currentRoute: currentRoute,
+ ),
+ centerTitle: true,
+ backgroundColor: Colors.pinkAccent[100],
+ ),
+ body: Obx(() {
+ // Displaying different content based on user role
+ if (controller.isAdmin) {
+ return AdminHomeContent();
+ } else {
+ return ProductsView();
+ }
+ }),
+ bottomNavigationBar: Obx(() {
+ return BottomNavigationBar(
+ currentIndex: controller.selectedIndex.value,
+ onTap: (index) {
+ controller.selectedIndex.value = index;
+ switch (index) {
+ case 0:
+ Get.offAllNamed(Routes.HOME);
+ break;
+ case 1:
+ Get.toNamed(Routes.PROFILE);
+ break;
+ case 2:
+ Get.toNamed(Routes.CART);
+ break;
+ }
+ },
+ items: const [
+ BottomNavigationBarItem(
+ icon: Icon(
+ Icons.home,
+ color: Colors.black,
+ ),
+ label: 'Home',
+ ),
+ BottomNavigationBarItem(
+ icon: Icon(
+ Icons.person,
+ color: Colors.black,
+ ),
+ label: 'Profile',
+ ),
+ BottomNavigationBarItem(
+ icon: Icon(
+ Icons.favorite,
+ color: Colors.black,
+ ),
+ label: 'Wishlist',
+ ),
+ ],
);
- },
+ }),
+ );
+ }
+}
+
+class AdminHomeContent extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ ElevatedButton(
+ onPressed: () => Get.toNamed(Routes.MY_PRODUCTS),
+ child: Text('Manage Hotels'),
+ ),
+ ElevatedButton(
+ onPressed: () => Get.toNamed(Routes.USERS),
+ child: Text('Manage Users'),
+ ),
+ ],
);
}
}
diff --git a/lib/app/modules/home/views/splash_screen.dart b/lib/app/modules/home/views/splash_screen.dart
new file mode 100644
index 00000000..0cde0422
--- /dev/null
+++ b/lib/app/modules/home/views/splash_screen.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import '../../../routes/app_pages.dart';
+
+class SplashScreen extends StatelessWidget {
+ const SplashScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ // Navigate to the home screen after 3 seconds
+ Future.delayed(Duration(seconds: 7), () {
+ Get.offNamed(Routes.HOME); // Navigate to Home Screen
+ });
+
+ return Scaffold(
+ backgroundColor: Colors.pinkAccent[50],
+ body: Center(
+ child: Image.asset('assets/icons/logo.png'), // Your splash logo
+ ),
+ );
+ }
+}
diff --git a/lib/app/modules/login/views/login_view.dart b/lib/app/modules/login/views/login_view.dart
index 00c3af3f..f705d83e 100644
--- a/lib/app/modules/login/views/login_view.dart
+++ b/lib/app/modules/login/views/login_view.dart
@@ -39,8 +39,8 @@ class LoginView extends GetView {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: action == AuthAction.signIn
- ? const Text('Welcome to Get Flutter Fire, please sign in!')
- : const Text('New to Get Flutter Fire, please sign up!'),
+ ? const Text('Welcome to Rest Quest, please sign in!')
+ : const Text('New to Rest Quest, please sign up!'),
);
}
@@ -55,7 +55,7 @@ class LoginView extends GetView {
? recaptcha()
: SignInScreen(
providers: [
- GoogleProvider(clientId: DefaultFirebaseOptions.webClientId),
+ GoogleProvider(clientId: 'YOUR_WEB_CLIENT_ID'),
MyEmailAuthProvider(),
],
showAuthActionSwitch: !controller.isRegistered,
diff --git a/lib/app/modules/product_details/bindings/product_details_binding.dart b/lib/app/modules/product_details/bindings/product_details_binding.dart
index 624d55ac..3fc4f7e4 100644
--- a/lib/app/modules/product_details/bindings/product_details_binding.dart
+++ b/lib/app/modules/product_details/bindings/product_details_binding.dart
@@ -1,5 +1,4 @@
import 'package:get/get.dart';
-
import '../controllers/product_details_controller.dart';
class ProductDetailsBinding extends Bindings {
@@ -7,7 +6,7 @@ class ProductDetailsBinding extends Bindings {
void dependencies() {
Get.create(
() => ProductDetailsController(
- Get.parameters['productId'] ?? '',
+ Get.parameters['productId'] ?? '',
),
);
}
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..57556ffd 100644
--- a/lib/app/modules/product_details/controllers/product_details_controller.dart
+++ b/lib/app/modules/product_details/controllers/product_details_controller.dart
@@ -1,18 +1,111 @@
+//Accommodation Details Controller
+
import 'package:get/get.dart';
+import '../../../../models/product_details.dart';
+import '../../../routes/app_pages.dart';
+import '../../cart/controllers/cart_controller.dart';
class ProductDetailsController extends GetxController {
- final String productId;
+ final RxString productId = ''.obs;
+ final RxString accommodationName = ''.obs;
+ final RxString accommodationLocation = ''.obs;
+ final RxDouble accommodationPrice = 0.0.obs;
+ final RxString accommodationDescription = ''.obs;
+ final RxList additionalImages = [].obs;
+
+ final List products = [
+ Product(
+ id: '1',
+ name: 'Fiona Apartment',
+ location: 'Powai Mumbai',
+ price: 2200,
+ imageAsset: 'assets/Fiona/F1.jpeg',
+ description: 'This is a Graceful, Vivid and Luxurious apartment located in Hiranandani Regent Hill, Powai. It is in an upscale neighborhood close to various offices, supermarkets , trendy cafes and restaurants. Conveniently located at just a 25 min drive from the International Airport.',
+ additionalImages:['assets/Fiona/F1.jpeg','assets/Fiona/F2.jpeg','assets/Fiona/F3.jpeg','assets/Fiona/F4.png']
+ ),
+ Product(
+ id: '2',
+ name: 'Historia Apartment',
+ location: 'Goa',
+ price: 4300,
+ imageAsset: 'assets/Historia/H1.jpeg',
+ description: 'Studio 109 comes with exquisitely set living room, kitchen and bathrooms in the heart of Goa. Situated in Arpora which is right in centre of Anjuna and BAGA .The place has all the amenities specifically the pool . Located in one of the most plush and urban areas of North Goa, the home offers beautiful experience of urban and country side living.',
+ additionalImages: ['assets/Historia/H1.jpeg','assets/Historia/H2.jpeg','assets/Historia/H3.jpeg']
+ ),
+ Product(
+ id: '3' ,
+ name: 'Plush Stays' ,
+ location: 'Khar,Mumbai' ,
+ price: 3800,
+ imageAsset: 'assets/Plush/P1.jpeg',
+ description: 'Welcome to Milo B from The Bombay Home Company, this is one listing from over 40 different options available with us.All our apartments come with the same standard amenities.Enjoy a stylish design experience with us, we have taken this up as a passion project and done up the place from scratch, taking special care to provide a comfy abode that matches international standards and expectations of the discerning traveler.',
+ additionalImages: ['assets/Plush/P1.jpeg','assets/Plush/P2.jpeg','assets/Plush/P3.jpeg','assets/Plush/P4.jpeg']
+ ),
+ Product(
+ id: '4',
+ name: 'Flora Inn',
+ location: 'Lonavala',
+ price: 5000,
+ imageAsset: 'assets/Flora/FL1.jpeg',
+ description: 'The Cottage is made out of total wood which give a very rustic vibe. It has smart TV with Netflix prime video hotstar any other streaming channels for your late night binges . We have a huge private balcony for your evening teas and early mornings. The cottage has a mini fridge to store your drinks . A kettle and tea/coffee supply if you would like to make your own tea/coffee. Our place is more for nature lovers who want to immersed into total silence and surrounded by forest area',
+ additionalImages: ['assets/Flora/FL1.jpeg','assets/Flora/FL2.jpeg','assets/Flora/FL3.jpeg']
+ ),
+ Product(
+ id: '5',
+ name: 'Stay Vista',
+ location: 'Thane',
+ price: 2200,
+ imageAsset: 'assets/Stay/S1.jpeg',
+ description: 'Welcome to our stunning AirBnB, where the carefully chosen decor exudes a sense of calmness, allowing you to unwind and escape from your daily routine.Step outside to find a picturesque creek vista, lush greenery and an exquisite sky, reminding that you have indeed found your blissful escape.If you\'re seeking a romantic getaway, an earned break from the daily grind, or simply a beautiful escape for some introspection, our AirBnB promises to be the perfect sanctuary',
+ additionalImages: ['assets/Stay/S1.jpeg','assets/Stay/S2.jpeg']
+ ),
+ Product(
+ id: '6',
+ name: 'Eva Studios',
+ location: 'Mumbai',
+ price: 1200,
+ imageAsset: 'assets/Eva/E1.jpeg',
+ description: 'Experience comfort and convenience at our fully equipped studio with excellent views. Perfectly situated near Nanavati Hospital, Mithibai/NM Colleges, and Vile Parle Station, the place is also just half an hour away from Mumbai International Airport. Our apartment is located just off the main SV Road. Whether you\'re here for a short stay or a long-term visit, you\'ll find everything you need for a relaxing and productive time.',
+ additionalImages: ['assets/Eva/E1.jpeg','assets/Eva/E2.jpg'])
+
+ ];
+
+ ProductDetailsController(String id) {
+ productId.value = id;
+ }
+
- ProductDetailsController(this.productId);
@override
void onInit() {
super.onInit();
- Get.log('ProductDetailsController created with id: $productId');
+ // Load the accommodation details using the productId
+ loadAccommodationDetails(productId.value);
}
- @override
- void onClose() {
- Get.log('ProductDetailsController close with id: $productId');
- super.onClose();
+ void loadAccommodationDetails(String id) {
+ // Find the product by id
+ final product = products.firstWhere((product) => product.id == id, orElse: () => throw Exception('Product not found'));
+
+ // Set the details into the reactive variables
+ accommodationName.value = product.name;
+ accommodationLocation.value = product.location;
+ accommodationPrice.value = product.price;
+ accommodationDescription.value = product.description;
+ additionalImages.value = product.additionalImages;
}
-}
+
+ // Method to add the product to the cart
+ void addToCart() {
+ final cartController = Get.find();
+
+ // Find the current product based on the ID
+ final product = products.firstWhere((product) => product.id == productId.value);
+
+ // Add the product to the cart
+ cartController.addProductToCart(product);
+ }
+ }
+
+
+
+
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..305304eb 100644
--- a/lib/app/modules/product_details/views/product_details_view.dart
+++ b/lib/app/modules/product_details/views/product_details_view.dart
@@ -1,8 +1,10 @@
-import 'package:flutter/material.dart';
+// Accommodation Details
+import 'package:flutter/material.dart';
import 'package:get/get.dart';
-
+import '../../../routes/app_pages.dart';
import '../controllers/product_details_controller.dart';
+import '../../cart/controllers/cart_controller.dart';
class ProductDetailsView extends GetWidget {
const ProductDetailsView({super.key});
@@ -10,16 +12,128 @@ class ProductDetailsView extends GetWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
- body: Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Text(
- 'ProductDetailsView is working',
- style: TextStyle(fontSize: 20),
- ),
- Text('ProductId: ${controller.productId}')
- ],
+ appBar: AppBar(
+ title: Obx(() => Text(
+ controller.accommodationName.value,
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 25.0,
+ color: Colors.white,
+ ),
+ )),
+ centerTitle: true,
+ backgroundColor: Colors.pinkAccent[100],
+ ),
+ body: SingleChildScrollView(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Display the main image
+ Obx(
+ () => Image.asset(
+ controller.additionalImages.isNotEmpty
+ ? controller.additionalImages.first
+ : controller.accommodationName.value,
+ height: 300,
+ width: double.infinity,
+ fit: BoxFit.cover,
+ ),
+ ),
+ const SizedBox(height: 16),
+
+ // Display the additional images in a horizontal list
+ Obx(
+ () => SizedBox(
+ height: 100,
+ child: ListView.builder(
+ scrollDirection: Axis.horizontal,
+ itemCount: controller.additionalImages.length,
+ itemBuilder: (context, index) {
+ return Padding(
+ padding: const EdgeInsets.only(right: 8.0),
+ child: Image.asset(
+ controller.additionalImages[index],
+ height: 100,
+ width: 100,
+ fit: BoxFit.cover,
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ const SizedBox(height: 16),
+
+ // Display the accommodation name
+ Obx(
+ () => Text(
+ controller.accommodationName.value,
+ style: TextStyle(
+ fontSize: 24,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ const SizedBox(height: 8),
+
+ // Display the location
+ Obx(
+ () => Text(
+ 'Location: ${controller.accommodationLocation.value}',
+ style: TextStyle(fontSize: 18, color: Colors.grey[700]),
+ ),
+ ),
+ const SizedBox(height: 16),
+
+ // Display the price
+ Obx(
+ () => Text(
+ 'Price: Rs ${controller.accommodationPrice.value} per night',
+ style: TextStyle(
+ fontSize: 20,
+ color: Colors.green,
+ fontWeight: FontWeight.bold),
+ ),
+ ),
+ const SizedBox(height: 16),
+
+ // Display the description
+ const Text(
+ 'Description',
+ style: TextStyle(
+ fontSize: 22,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 8),
+ Obx(
+ () => Text(
+ controller.accommodationDescription.value,
+ style: TextStyle(fontSize: 16),
+ ),
+ ),
+ const SizedBox(height: 24),
+
+ // Add to Cart button
+ Center(
+ child: ElevatedButton.icon(
+ onPressed: () {
+ controller.addToCart();
+ },
+ icon: Icon(
+ Icons.favorite,
+ color: Colors.red,
+ ),
+ label: Text('Add to Wishlist'),
+ style: ElevatedButton.styleFrom(
+ padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12),
+ ),
+ ),
+ ),
+ ],
+ ),
),
),
);
diff --git a/lib/app/modules/products/controllers/products_controller.dart b/lib/app/modules/products/controllers/products_controller.dart
index 118c7dc8..88c616ca 100644
--- a/lib/app/modules/products/controllers/products_controller.dart
+++ b/lib/app/modules/products/controllers/products_controller.dart
@@ -4,14 +4,67 @@ import '../../../../models/product.dart';
class ProductsController extends GetxController {
final products = [].obs;
+ final searchQuery = ''.obs;
+ final selectedPersons = 1.obs;
+ final selectedCheckInDate = Rxn();
+ final selectedCheckOutDate = Rxn();
+
void loadDemoProductsFromSomeWhere() {
- products.add(
+ products.assignAll([
+ Product(
+ id: '1',
+ name: 'Fiona Apartment',
+ location: 'Powai Mumbai',
+ price: 2200,
+ imageAsset: 'assets/Fiona/F1.jpeg'),
+
+ Product(
+ id: '2',
+ name: 'Historia Apartment',
+ location: 'Goa',
+ price: 4300,
+ imageAsset: 'assets/Historia/H1.jpeg'),
+
+ Product(
+ id: '3' ,
+ name: 'Plush Stays' ,
+ location: 'Khar,Mumbai' ,
+ price: 3800,
+ imageAsset: 'assets/Plush/P1.jpeg'),
+
Product(
- name: 'Product added on: ${DateTime.now().toString()}',
- id: DateTime.now().millisecondsSinceEpoch.toString(),
- ),
- );
+ id: '4',
+ name: 'Flora Inn',
+ location: 'Lonavala',
+ price: 5000,
+ imageAsset: 'assets/Flora/FL1.jpeg'),
+
+ Product(
+ id: '5',
+ name: 'Stay Vista',
+ location: 'Thane',
+ price: 2200,
+ imageAsset: 'assets/Stay/S1.jpeg' ),
+
+ Product(
+ id: '6',
+ name: 'Eva Studios',
+ location: 'Mumbai',
+ price: 1200,
+ imageAsset: 'assets/Eva/E1.jpeg'),
+ ]);
+ }
+
+ // Get filtered products based on the search query
+ List get filteredProducts {
+ if (searchQuery.value.isEmpty) {
+ return products;
+ } else {
+ return products.where((product) {
+ return product.name.toLowerCase().contains(searchQuery.value.toLowerCase());
+ }).toList();
+ }
}
@override
diff --git a/lib/app/modules/products/views/products_view.dart b/lib/app/modules/products/views/products_view.dart
index 5b190a6a..3df1fdf9 100644
--- a/lib/app/modules/products/views/products_view.dart
+++ b/lib/app/modules/products/views/products_view.dart
@@ -1,11 +1,10 @@
-// ignore_for_file: inference_failure_on_function_invocation
-
import 'package:flutter/material.dart';
import 'package:get/get.dart';
-
import '../../../../models/role.dart';
import '../../../routes/app_pages.dart';
import '../controllers/products_controller.dart';
+import '../../product_details/controllers/product_details_controller.dart'; // Import ProductDetailsController
+import '../../cart/controllers/cart_controller.dart'; // Import CartController
class ProductsView extends GetView {
const ProductsView({super.key});
@@ -13,38 +12,178 @@ class ProductsView extends GetView {
@override
Widget build(BuildContext context) {
var arg = Get.rootDelegate.arguments();
+ var userRole = arg != null ? Get.rootDelegate.arguments()["role"] : Role.guest;
+
return Scaffold(
- floatingActionButton:
- (arg != null && Get.rootDelegate.arguments()["role"] == Role.seller)
- ? FloatingActionButton.extended(
- onPressed: controller.loadDemoProductsFromSomeWhere,
- label: const Text('Add'),
- )
- : null,
+ floatingActionButton: (userRole == Role.admin)
+ ? FloatingActionButton.extended(
+ onPressed: controller.loadDemoProductsFromSomeWhere,
+ label: const Text('Add'),
+ )
+ : null,
body: Column(
children: [
- const Hero(
- tag: 'heroLogo',
- child: FlutterLogo(),
+ // Search bar widget
+ Padding(
+ padding: const EdgeInsets.all(12.0),
+ child: TextField(
+ cursorColor: Colors.black,
+ decoration: InputDecoration(
+ hintText: 'Where to?',
+ contentPadding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0),
+ fillColor: Colors.white,
+ filled: true,
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ borderSide: const BorderSide(color: Colors.black, width: 1.0),
+ ),
+ enabledBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ borderSide: const BorderSide(color: Colors.black, width: 1.0),
+ ),
+ focusedBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(8.0),
+ borderSide: const BorderSide(color: Colors.black, width: 1.0),
+ ),
+ ),
+ onChanged: (value) {
+ controller.searchQuery.value = value;
+ },
+ ),
),
+
+ // Row for persons and date selection
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8.0),
+ child: Row(
+ children: [
+ // Dropdown for number of persons
+ Expanded(
+ flex: 1,
+ child: DropdownButton(
+ value: controller.selectedPersons.value,
+ onChanged: (value) {
+ controller.selectedPersons.value = value!;
+ },
+ items: List.generate(10, (index) {
+ return DropdownMenuItem(
+ value: index + 1,
+ child: Text('${index + 1} Persons'),
+ );
+ }),
+ isExpanded: true,
+ hint: Icon(
+ Icons.people,
+ color: Colors.blue,
+ ),
+ ),
+ ),
+ SizedBox(width: 12.0), // Spacing between dropdowns
+
+ // Check-in Date picker
+ Expanded(
+ flex: 1,
+ child: GestureDetector(
+ onTap: () async {
+ DateTime? pickedDate = await showDatePicker(
+ context: context,
+ initialDate: controller.selectedCheckInDate.value ?? DateTime.now(),
+ firstDate: DateTime.now(),
+ lastDate: DateTime(2101),
+ );
+ if (pickedDate != null) {
+ controller.selectedCheckInDate.value = pickedDate;
+ }
+ },
+ child: Obx(() => TextField(
+ decoration: InputDecoration(
+ border: OutlineInputBorder(),
+ hintText: controller.selectedCheckInDate.value == null
+ ? 'Check-in'
+ : '${controller.selectedCheckInDate.value?.toLocal().toString().split(' ')[0]}',
+ contentPadding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0),
+ ),
+ enabled: false,
+ style: TextStyle(fontSize: 16),
+ )),
+ ),
+ ),
+ SizedBox(width: 12.0),
+
+ // Check-out Date picker
+ Expanded(
+ flex: 1,
+ child: GestureDetector(
+ onTap: () async {
+ DateTime? pickedDate = await showDatePicker(
+ context: context,
+ initialDate: controller.selectedCheckInDate.value ?? DateTime.now(),
+ firstDate: DateTime.now(),
+ lastDate: DateTime(2101),
+ );
+ if (pickedDate != null) {
+ controller.selectedCheckOutDate.value = pickedDate;
+ }
+ },
+ child: Obx(() => TextField(
+ decoration: InputDecoration(
+ border: OutlineInputBorder(),
+ hintText: controller.selectedCheckOutDate.value == null
+ ? 'Check-out'
+ : '${controller.selectedCheckOutDate.value?.toLocal().toString().split(' ')[0]}',
+ contentPadding: EdgeInsets.symmetric(vertical: 16.0, horizontal: 12.0),
+ ),
+ enabled: false,
+ style: TextStyle(fontSize: 16),
+ )),
+ ),
+ ),
+ ],
+ )),
+ SizedBox(height: 12.0),
Expanded(
child: Obx(
- () => RefreshIndicator(
+ () => RefreshIndicator(
onRefresh: () async {
controller.products.clear();
controller.loadDemoProductsFromSomeWhere();
},
child: ListView.builder(
- itemCount: controller.products.length,
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ itemCount: controller.filteredProducts.length,
itemBuilder: (context, index) {
- final item = controller.products[index];
- return ListTile(
- onTap: () {
- Get.rootDelegate.toNamed(Routes.PRODUCT_DETAILS(
- item.id)); //we could use Get Parameters
- },
- title: Text(item.name),
- subtitle: Text(item.id),
+ final item = controller.filteredProducts[index];
+ return Card(
+ margin: const EdgeInsets.symmetric(vertical: 8.0),
+ child: ListTile(
+ onTap: () {
+ // Register the controllers before navigating
+ Get.lazyPut(() => ProductDetailsController(item.id));
+ Get.lazyPut(() => CartController());
+
+ // Navigate to the ProductDetailsView
+ Get.toNamed(Routes.PRODUCT_DETAILS(item.id));
+ },
+ contentPadding: EdgeInsets.all(16.0),
+ leading: Image.asset(
+ item.imageAsset,
+ width: 120,
+ height: 120,
+ fit: BoxFit.cover,
+ ),
+ title: Text(
+ item.name,
+ style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
+ ),
+ subtitle: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ SizedBox(height: 8.0),
+ Text('Location: ${item.location}', style: TextStyle(fontSize: 16)),
+ Text('Price: Rs ${item.price} night', style: TextStyle(fontSize: 16))
+ ],
+ ),
+ ),
);
},
),
diff --git a/lib/app/modules/profile/controllers/profile_controller.dart b/lib/app/modules/profile/controllers/profile_controller.dart
index 0c1e059e..e015e0ab 100644
--- a/lib/app/modules/profile/controllers/profile_controller.dart
+++ b/lib/app/modules/profile/controllers/profile_controller.dart
@@ -4,7 +4,6 @@ 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';
@@ -18,13 +17,22 @@ class ProfileController extends GetxController {
String? get photoURL => _photoURL.value;
@override
- onInit() {
+ void onInit() {
super.onInit();
- _photoURL.value = currentUser!.photoURL;
- _photoURL.bindStream(currentUser!.photoURL.obs.stream);
+ if (currentUser != null) {
+ _photoURL.value = currentUser!.photoURL;
+ _photoURL.bindStream(currentUser!.photoURL.obs.stream);
+ } else {
+ Get.snackbar('Error', 'No user is currently logged in.');
+ }
}
Future uploadFile(String path) async {
+ if (currentUser == null) {
+ Get.snackbar('Error', 'User is not logged in.');
+ return null;
+ }
+
try {
var byt = GetStorage().read(path);
if (byt != null) {
@@ -45,7 +53,7 @@ class ProfileController extends GetxController {
return "$destination/$fileName";
}
} catch (e) {
- Get.snackbar('Error', 'Image Not Uploaded as ${e.toString()}');
+ Get.snackbar('Error', 'Image Not Uploaded: ${e.toString()}');
}
return null;
}
@@ -55,6 +63,11 @@ class ProfileController extends GetxController {
}
Future updatePhotoURL(String dest) async {
+ if (currentUser == null) {
+ Get.snackbar('Error', 'User is not logged in.');
+ return;
+ }
+
_photoURL.value = await storage.ref().child(dest).getDownloadURL();
await currentUser?.updatePhotoURL(_photoURL.value);
Get.snackbar('Success', 'Picture stored and linked');
diff --git a/lib/app/modules/profile/views/profile_view.dart b/lib/app/modules/profile/views/profile_view.dart
index c26d11c1..a8ac29e1 100644
--- a/lib/app/modules/profile/views/profile_view.dart
+++ b/lib/app/modules/profile/views/profile_view.dart
@@ -1,4 +1,4 @@
-// ignore_for_file: inference_failure_on_function_invocation
+// ProfileView.dart
import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
@@ -12,113 +12,160 @@ import '../controllers/profile_controller.dart';
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 Scaffold(
+ appBar: AppBar(
+ title: Text(
+ 'Profile',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontSize: 25.0,
+ color: Colors.white,
+ ),
+ ),
+ centerTitle: true,
+ backgroundColor: Colors.pinkAccent[100],
+ ),
+ body: Obx(() => profileScreen()),
+ );
}
Widget profileScreen() {
- 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
+ final user = AuthService.to.user;
+ final userName = user?.displayName ?? 'Guest';
+ final photoURL = user?.photoURL;
- // 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,
+ if (AuthService.to.isLoggedInValue) {
+ return ProfileScreen(
+ avatar: SizedBox(
+ height: size,
+ width: size,
+ child: ClipPath(
+ clipper: ShapeBorderClipper(shape: shape),
+ clipBehavior: Clip.hardEdge,
+ child: photoURL != null
+ ? Image.network(
+ photoURL,
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',
- width: size,
- fit: BoxFit.contain,
- ),
- ),
+ height: size,
+ cacheWidth: size.toInt(),
+ cacheHeight: size.toInt(),
+ fit: BoxFit.contain,
+ frameBuilder: _imageFrameBuilder,
+ loadingBuilder: (BuildContext context, Widget child,
+ ImageChunkEvent? loadingProgress) {
+ if (loadingProgress == null) {
+ return child;
+ }
+ return Center(
+ child: CircularProgressIndicator(
+ value: loadingProgress.expectedTotalBytes != null
+ ? loadingProgress.cumulativeBytesLoaded /
+ loadingProgress.expectedTotalBytes!
+ : null,
+ ),
+ );
+ },
+ )
+ : Center(
+ child: Icon(
+ Icons.person,
+ size: size,
+ color: placeholderColor,
),
),
- // 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),
- );
- })
- ],
- 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);
- }
+ ),
+ ),
+ actions: [
+ SignedOutAction((context) {
+ Get.back();
+ controller.logout();
+ Get.rootDelegate.toNamed(Screen.PROFILE.route);
+ }),
+ AccountDeletedAction((context, user) {
+ Get.defaultDialog(
+ title: 'Deleted Account of ${user.displayName ?? 'User'}',
+ barrierDismissible: true,
+ navigatorKey: Get.nestedKey(Screen.HOME.route),
+ );
+ })
+ ],
+ children: [
+ const Divider(),
+ if (controller.currentUser?.email != null)
+ TextButton.icon(
+ onPressed: callChangePwdDialog,
+ label: const Text('Change Password'),
+ icon: const Icon(Icons.password_rounded),
+ ),
+ ImagePickerButton(callback: (String? path) async {
+ if (path != null) {
+ try {
+ String? dest = await controller.uploadFile(path);
+ if (dest != null) {
+ await controller.updatePhotoURL(dest);
}
- })
+ } catch (e) {
+ Get.snackbar('Error', 'Failed to upload image: $e');
+ }
+ }
+ }),
+ ],
+ );
+ } else {
+ return Scaffold(
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ 'Please log in to view your profile.',
+ style: TextStyle(fontSize: 18),
+ ),
+ SizedBox(height: 20),
+ ElevatedButton(
+ onPressed: () {
+ Get.toNamed(Screen.LOGIN.route);
+ },
+ style: ElevatedButton.styleFrom(
+ foregroundColor: Colors.white,
+ backgroundColor: Colors.pinkAccent[100]),
+ child: Text('Log in'),
+ ),
],
- )
- : const Scaffold();
+ ),
+ ),
+ );
+ }
}
void callChangePwdDialog() {
+ if (controller.currentUser == null) return;
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,
+ );
}
}
diff --git a/lib/app/modules/register/views/register_view.dart b/lib/app/modules/register/views/register_view.dart
index 01f73e88..67b3d805 100644
--- a/lib/app/modules/register/views/register_view.dart
+++ b/lib/app/modules/register/views/register_view.dart
@@ -1,53 +1,116 @@
-// import 'package:firebase_ui_auth/firebase_ui_auth.dart';
import 'package:flutter/material.dart';
-
import 'package:get/get.dart';
+import 'package:cloud_firestore/cloud_firestore.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),
+ final _formKey = GlobalKey();
+ final _displayNameController = TextEditingController();
+ final _emailController = TextEditingController();
+ final _passwordController = TextEditingController();
+ final _confirmPasswordController = TextEditingController();
+
+ return Scaffold(
+ appBar: AppBar(
+ title: const Text('Register'),
+ centerTitle: true,
+ backgroundColor: Colors.pinkAccent[100],
+ ),
+ body: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: Form(
+ key: _formKey,
+ child: Column(
+ children: [
+ TextFormField(
+ controller: _displayNameController,
+ decoration: InputDecoration(labelText: 'Display Name'),
+ validator: (value) {
+ if (value == null || value.isEmpty) {
+ return 'Please enter a display name';
+ }
+ return null;
+ },
+ ),
+ TextFormField(
+ controller: _emailController,
+ decoration: InputDecoration(labelText: 'Email'),
+ validator: (value) {
+ if (value == null || !value.contains('@')) {
+ return 'Please enter a valid email';
+ }
+ return null;
+ },
+ ),
+ TextFormField(
+ controller: _passwordController,
+ decoration: InputDecoration(labelText: 'Password'),
+ obscureText: true,
+ validator: (value) {
+ if (value == null || value.length < 6) {
+ return 'Password must be at least 6 characters long';
+ }
+ return null;
+ },
+ ),
+ TextFormField(
+ controller: _confirmPasswordController,
+ decoration: InputDecoration(labelText: 'Confirm Password'),
+ obscureText: true,
+ validator: (value) {
+ if (value != _passwordController.text) {
+ return 'Passwords do not match';
+ }
+ return null;
+ },
+ ),
+ const SizedBox(height: 20),
+ ElevatedButton(
+ onPressed: () async {
+ if (_formKey.currentState!.validate()) {
+ try {
+ // Register the user and get their UID
+ final user = await AuthService.to.registerWithEmailAndPassword(
+ _emailController.text,
+ _passwordController.text,
+ );
+
+ // Save additional details to Firestore
+ if (user != null) {
+ await FirebaseFirestore.instance
+ .collection('users')
+ .doc(user.uid)
+ .set({
+ 'displayName': _displayNameController.text,
+ 'email': _emailController.text,
+ // Add more fields here as needed
+ });
+
+ // Navigate to the next screen or show success message
+ Get.snackbar('Success', 'Account created successfully. Please verify your email.');
+ // Optionally, navigate to a different screen here
+ }
+ } catch (e) {
+ Get.snackbar('Error', 'Failed to register: $e');
+ }
+ }
+ },
+ style: ElevatedButton.styleFrom(
+ foregroundColor: Colors.white,
+ backgroundColor: Colors.pinkAccent[100],
+ ),
+ child: const Text('Register'),
+ ),
+ ],
),
- TextButton(
- onPressed: () => AuthService.to.register(),
- child: const Text("Verification Done. Relogin"),
- )
- ])),
- );
- return w;
- } catch (e) {
- // TODO
- }
- return const Scaffold();
+ ),
+ ),
+ );
}
}
diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart
index 7269755d..09fb86be 100644
--- a/lib/app/routes/app_pages.dart
+++ b/lib/app/routes/app_pages.dart
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
-
+import '../modules/home/views/splash_screen.dart';
import '../../models/access_level.dart';
import '../../models/role.dart';
import '../middleware/auth_middleware.dart';
@@ -38,17 +38,23 @@ import '../modules/users/bindings/users_binding.dart';
import '../modules/users/views/users_view.dart';
import '../../models/screens.dart';
+
part 'app_routes.dart';
part 'screen_extension.dart';
class AppPages {
AppPages._();
- static const INITIAL = Routes.HOME;
+ static const INITIAL = Routes.SPLASH;
//TODO create this using the information from Screen and Role data
//can use https://pub.dev/packages/freezed
static final routes = [
+ GetPage(
+ name: Routes.SPLASH,
+ page: () => SplashScreen(),
+ // No binding needed for splash screen if it doesn't use any controller
+ ),
GetPage(
name: '/',
page: () => const RootView(),
@@ -98,9 +104,10 @@ class AppPages {
binding: ProductsBinding(),
children: [
Screen.PRODUCT_DETAILS.getPages(
- page: () => const ProductDetailsView(),
+ page:() => const ProductDetailsView(),
binding: ProductDetailsBinding(),
- ),
+ )
+
],
),
Screen.CATEGORIES.getPage(
@@ -111,12 +118,12 @@ class AppPages {
Screen.CART.getPage(
page: () => const CartView(),
binding: CartBinding(),
- role: Role.buyer,
+ role: Role.registeredUser,
children: [
Screen.CHECKOUT.getPage(
//if this is after cart details, it never gets reached
- page: () => const CheckoutView(),
- binding: CheckoutBinding(),
+ page: () => CheckoutView(),
+ binding: CheckoutBinding()
),
Screen.CART_DETAILS.getPages(
page: () => const ProductDetailsView(),
@@ -127,7 +134,7 @@ class AppPages {
Screen.MY_PRODUCTS.getPage(
page: () => const MyProductsView(),
binding: MyProductsBinding(),
- role: Role.seller,
+ role: Role.admin,
children: [
Screen.MY_PRODUCT_DETAILS.getPages(
page: () => const ProductDetailsView(),
diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart
index f3129d21..648de2ff 100644
--- a/lib/app/routes/app_routes.dart
+++ b/lib/app/routes/app_routes.dart
@@ -4,19 +4,21 @@ part of 'app_pages.dart';
// DO NOT EDIT. This is code generated via package:get_cli/get_cli.dart
abstract class Routes {
+
static const HOME = _Paths.HOME;
- // static String PROFILE = Screen.PROFILE.fullPath;
+ static const String SPLASH = _Paths.SPLASH;
+ static String PROFILE = Screen.PROFILE.route;
// static String SETTINGS = Screen.SETTINGS.fullPath;
static String LOGIN = Screen.LOGIN.route;
static String REGISTER = Screen.REGISTER.route;
// static String DASHBOARD = Screen.DASHBOARD.fullPath;
- // static String PRODUCTS = Screen.PRODUCTS.fullPath;
- // static String CART = Screen.CART.fullPath;
- // static String CHECKOUT = Screen.CHECKOUT.fullPath;
+ static String PRODUCTS = Screen.PRODUCTS.route;
+ static String CART = Screen.CART.route;
+ static String CHECKOUT = Screen.CHECKOUT.route;
// static const CATEGORIES = _Paths.HOME + _Paths.CATEGORIES;
// static const TASKS = _Paths.HOME + _Paths.TASKS;
- // static const USERS = _Paths.HOME + _Paths.USERS;
- // static const MY_PRODUCTS = _Paths.HOME + _Paths.MY_PRODUCTS;
+ static const USERS = _Paths.HOME + _Paths.USERS;
+ static const MY_PRODUCTS = _Paths.HOME + _Paths.MY_PRODUCTS;
static String PRODUCT_DETAILS(String productId) =>
'${Screen.PRODUCTS.route}/$productId';
@@ -35,11 +37,12 @@ abstract class Routes {
// Keeping this as Get_Cli will require it. Any addition can later be added to Screen
abstract class _Paths {
static const String HOME = '/home';
- // static const DASHBOARD = '/dashboard';
- // static const PRODUCTS = '/products';
+ //static const DASHBOARD = '/dashboard';
+ static const String PRODUCTS = '/products';
+ static const String SPLASH = '/splash';
// static const PROFILE = '/profile';
// static const SETTINGS = '/settings';
- // static const PRODUCT_DETAILS = '/:productId';
+ static const String PRODUCT_DETAILS = '/products/:productId';
// static const CART_DETAILS = '/:productId';
// static const LOGIN = '/login';
// static const CART = '/cart';
@@ -48,7 +51,7 @@ abstract class _Paths {
// static const CATEGORIES = '/categories';
// static const TASKS = '/tasks';
// static const TASK_DETAILS = '/:taskId';
- // static const USERS = '/users';
+ static const USERS = '/users';
// static const USER_PROFILE = '/:uId';
- // static const MY_PRODUCTS = '/my-products';
+ static const MY_PRODUCTS = '/my-products';
}
diff --git a/lib/app/routes/screen_extension.dart b/lib/app/routes/screen_extension.dart
index aaf138b0..a304fa28 100644
--- a/lib/app/routes/screen_extension.dart
+++ b/lib/app/routes/screen_extension.dart
@@ -100,7 +100,7 @@ extension ScreenExtension on Screen {
AccessLevel.public => null,
AccessLevel.guest => [EnsureAuthOrGuestMiddleware()],
AccessLevel.authenticated => [EnsureAuthedAndNotGuestMiddleware()],
- AccessLevel.roleBased => [EnsureRoleMiddleware(role ?? Role.buyer)],
+ AccessLevel.roleBased => [EnsureRoleMiddleware(role ?? Role.registeredUser)],
AccessLevel.masked => throw UnimplementedError(), //not for screens
AccessLevel.secret => throw UnimplementedError(), //not for screens
AccessLevel.notAuthed => [EnsureNotAuthedOrGuestMiddleware()],
diff --git a/lib/app/widgets/change_password_dialog.dart b/lib/app/widgets/change_password_dialog.dart
index 78c392e5..9aaa4880 100644
--- a/lib/app/widgets/change_password_dialog.dart
+++ b/lib/app/widgets/change_password_dialog.dart
@@ -20,14 +20,13 @@ class ChangePasswordDialog extends StatefulWidget {
_formKey.currentState?.save();
try {
AuthCredential credential = EmailAuthProvider.credential(
- email: user.email!, password: _formValues.old!);
+ email: user.email ?? '', password: _formValues.old ?? '');
await user.reauthenticateWithCredential(credential);
- await user.updatePassword(_formValues.newP!);
+ await user.updatePassword(_formValues.newP ?? '');
Get.back(result: true);
} catch (e) {
_formValues.authError = "Incorrect Password";
- _formKey.currentState!.validate();
- // Get.snackbar("Error", e.toString());
+ _formKey.currentState?.validate();
}
}
}
@@ -47,93 +46,101 @@ class _ChangePasswordDialogState extends State {
@override
Widget build(BuildContext context) {
return Material(
- child: Form(
- key: widget._formKey,
- child: Column(
- children: [
- TextFormField(
- textInputAction: TextInputAction.done,
- autovalidateMode: AutovalidateMode.onUserInteraction,
- obscureText: true,
- cursorColor: kPrimaryColor,
- decoration: const InputDecoration(
- hintText: "Old Password",
- prefixIcon: Padding(
- padding: EdgeInsets.all(defaultPadding),
- child: Icon(Icons.key),
- ),
- ),
- validator: (String? value) {
- return widget._formValues.authError ??
- ((value != null && value.length < 8)
- ? 'Pwd cannot be less than 8 characters'
- : null);
- },
- onChanged: (value) {
- setState(() {
- widget._formValues.old = value;
- widget._formValues.authError = null;
- });
- },
- ),
- Padding(
- padding: const EdgeInsets.symmetric(vertical: defaultPadding),
- child: TextFormField(
+ child: Form(
+ key: widget._formKey,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ TextFormField(
textInputAction: TextInputAction.done,
autovalidateMode: AutovalidateMode.onUserInteraction,
obscureText: true,
cursorColor: kPrimaryColor,
decoration: const InputDecoration(
- hintText: "New Password",
+ hintText: "Old Password",
prefixIcon: Padding(
padding: EdgeInsets.all(defaultPadding),
- child: Icon(Icons.lock),
+ child: Icon(Icons.key),
),
),
validator: (String? value) {
- return (value != null && value.length < 8)
- ? 'Pwd cannot be less than 8 characters'
- : (value != null && value == widget._formValues.old)
- ? 'Pwd cannot be same as old Pwd'
- : null;
+ if (widget._formValues.authError != null) {
+ return widget._formValues.authError;
+ }
+ if (value == null || value.isEmpty || value.length < 8) {
+ return 'Password cannot be less than 8 characters';
+ }
+ return null;
},
onChanged: (value) {
setState(() {
- widget._formValues.newP = value;
+ widget._formValues.old = value;
+ widget._formValues.authError = null;
});
},
),
- ),
- Padding(
- padding: const EdgeInsets.symmetric(vertical: defaultPadding),
- child: TextFormField(
- textInputAction: TextInputAction.done,
- autovalidateMode: AutovalidateMode.onUserInteraction,
- obscureText: true,
- cursorColor: kPrimaryColor,
- decoration: const InputDecoration(
- hintText: "Confirm Password",
- prefixIcon: Padding(
- padding: EdgeInsets.all(defaultPadding),
- child: Icon(Icons.confirmation_num),
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: defaultPadding),
+ child: TextFormField(
+ textInputAction: TextInputAction.done,
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ obscureText: true,
+ cursorColor: kPrimaryColor,
+ decoration: const InputDecoration(
+ hintText: "New Password",
+ prefixIcon: Padding(
+ padding: EdgeInsets.all(defaultPadding),
+ child: Icon(Icons.lock),
+ ),
),
+ validator: (String? value) {
+ if (value == null || value.isEmpty || value.length < 8) {
+ return 'Password cannot be less than 8 characters';
+ }
+ if (value == widget._formValues.old) {
+ return 'New password cannot be the same as the old password';
+ }
+ return null;
+ },
+ onChanged: (value) {
+ setState(() {
+ widget._formValues.newP = value;
+ });
+ },
),
- validator: (String? value) {
- return (value != null && value != widget._formValues.newP)
- ? 'Pwd does not match'
- : null;
- },
),
- ),
- const SizedBox(height: defaultPadding),
- ElevatedButton(
- onPressed: widget.onReset,
- child: const Text(
- "Reset",
+ Padding(
+ padding: const EdgeInsets.symmetric(vertical: defaultPadding),
+ child: TextFormField(
+ textInputAction: TextInputAction.done,
+ autovalidateMode: AutovalidateMode.onUserInteraction,
+ obscureText: true,
+ cursorColor: kPrimaryColor,
+ decoration: const InputDecoration(
+ hintText: "Confirm Password",
+ prefixIcon: Padding(
+ padding: EdgeInsets.all(defaultPadding),
+ child: Icon(Icons.confirmation_num),
+ ),
+ ),
+ validator: (String? value) {
+ if (value != widget._formValues.newP) {
+ return 'Passwords do not match';
+ }
+ return null;
+ },
+ ),
+ ),
+ const SizedBox(height: defaultPadding),
+ ElevatedButton(
+ onPressed: widget.onReset,
+ child: const Text(
+ "Reset",
+ ),
),
- ),
- ],
+ ],
+ ),
),
- ));
+ );
}
}
diff --git a/lib/app/widgets/image_picker_button.dart b/lib/app/widgets/image_picker_button.dart
index d6e87ff4..09051c34 100644
--- a/lib/app/widgets/image_picker_button.dart
+++ b/lib/app/widgets/image_picker_button.dart
@@ -4,18 +4,19 @@ import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:image_picker/image_picker.dart';
-import '../../models/action_enum.dart';
import 'menu_sheet_button.dart';
-enum ImageSources implements ActionEnum {
+enum ImageSources {
camera(Icons.camera, 'Camera'),
gallery(Icons.photo_library, 'Gallery'),
file(Icons.file_upload, 'File');
const ImageSources(this.icon, this.label);
- @override
- Future doAction() async {
+ final IconData? icon;
+ final String? label;
+
+ Future doAction() async {
switch (this) {
case ImageSources.camera:
return await getImage(ImageSource.camera);
@@ -24,15 +25,10 @@ enum ImageSources implements ActionEnum {
case ImageSources.file:
return await getFile();
default:
+ return null;
}
- return null;
}
- @override
- final IconData? icon;
- @override
- final String? label;
-
static Future getImage(ImageSource imageSource) async {
final pickedFile = await ImagePicker().pickImage(source: imageSource);
if (pickedFile != null) {
@@ -53,7 +49,6 @@ enum ImageSources implements ActionEnum {
GetStorage().write(fileName, fileBytes);
return fileName;
- //result.files.single.path;//is causing issues for Web, see https://github.com/miguelpruivo/flutter_file_picker/wiki/FAQ
} else {
Get.snackbar('Error', 'Image Not Selected');
return null;
@@ -61,31 +56,57 @@ enum ImageSources implements ActionEnum {
}
}
-class ImagePickerButton extends MenuSheetButton {
- final ValueSetter? callback;
-
- const ImagePickerButton(
- {super.key,
- super.icon = const Icon(Icons.image),
- super.label = 'Pick an Image',
- this.callback});
+class ImagePickerButton extends StatelessWidget {
+ final ValueSetter? callback;
- @override
- Iterable get values => ImageSources.values;
+ const ImagePickerButton({
+ Key? key,
+ this.callback,
+ }) : super(key: key);
- @override
- void callbackFunc(act) {
- if (callback != null) callback!(act);
+ void callbackFunc(String? act) {
+ if (callback != null && act != null) {
+ callback!(act);
+ } else if (act == null) {
+ Get.snackbar('Error', 'No image selected.');
+ }
}
@override
Widget build(BuildContext context) {
- return !(GetPlatform.isAndroid || GetPlatform.isIOS)
+ return !GetPlatform.isAndroid && !GetPlatform.isIOS
? TextButton.icon(
- onPressed: () async => callbackFunc(await ImageSources.getFile()),
- icon: icon,
- label: const Text('Pick an Image'),
- )
- : builder(context);
+ onPressed: () async => callbackFunc(await ImageSources.getFile()),
+ icon: const Icon(Icons.image),
+ label: const Text('Pick an Image'),
+ )
+ : IconButton(
+ icon: const Icon(Icons.image),
+ onPressed: () async {
+ final result = await showModalBottomSheet(
+ context: context,
+ builder: (context) => _buildBottomSheet(),
+ );
+ if (result != null) {
+ final actionResult = await result.doAction();
+ callbackFunc(actionResult);
+ }
+ },
+ );
+ }
+
+ Widget _buildBottomSheet() {
+ return SizedBox(
+ height: 180,
+ child: ListView(
+ children: ImageSources.values.map((source) {
+ return ListTile(
+ leading: Icon(source.icon),
+ title: Text(source.label ?? ''),
+ onTap: () => Get.back(result: source),
+ );
+ }).toList(),
+ ),
+ );
}
}
diff --git a/lib/app/widgets/login_widgets.dart b/lib/app/widgets/login_widgets.dart
index b8f2d8c1..f25a1c5d 100644
--- a/lib/app/widgets/login_widgets.dart
+++ b/lib/app/widgets/login_widgets.dart
@@ -11,10 +11,11 @@ import 'menu_sheet_button.dart';
class LoginWidgets {
static Widget headerBuilder(context, constraints, shrinkOffset) {
return Padding(
- padding: const EdgeInsets.all(20),
- child: AspectRatio(
- aspectRatio: 1,
- child: Image.asset('assets/images/flutterfire_300x.png'),
+ padding: const EdgeInsets.all(10),
+ child: SizedBox(
+ width:200,
+ height:200,
+ child: Image.asset('assets/icons/logo.png'),
),
);
}
diff --git a/lib/app/widgets/menu_sheet_button.dart b/lib/app/widgets/menu_sheet_button.dart
index abd3873e..d1944c4f 100644
--- a/lib/app/widgets/menu_sheet_button.dart
+++ b/lib/app/widgets/menu_sheet_button.dart
@@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
-
+import 'package:image_picker/image_picker.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:get_storage/get_storage.dart';
import '../../models/action_enum.dart';
class MenuItemsController extends GetxController {
@@ -14,35 +16,32 @@ class MenuSheetButton extends StatelessWidget {
final Icon? icon;
final String? label;
- const MenuSheetButton(
- {super.key,
- this.values_,
- this.icon,
- this.label}); //passing scaffoldKey means that bottomSheet is added to it
+ const MenuSheetButton({
+ super.key,
+ this.values_,
+ this.icon,
+ this.label,
+ });
- Iterable get values => values_!;
+ Iterable get values => values_ ?? const [];
static Widget bottomSheet(
- Iterable values, ValueSetter? callback) {
+ Iterable values, ValueSetter? callback) {
return SizedBox(
height: 180,
width: Get.mediaQuery.size.width,
child: ListView(
- // mainAxisAlignment: MainAxisAlignment.center,
children: values
.map(
(ActionEnum value) => ListTile(
- leading: Icon(value.icon),
- title: Text(
- value.label!,
- ),
- onTap: () async {
- Get.back();
- callback != null
- ? callback(await value.doAction())
- : await value.doAction();
- }),
- )
+ leading: Icon(value.icon),
+ title: Text(value.label ?? ''),
+ onTap: () async {
+ Get.back();
+ callback?.call(await value.doAction());
+ },
+ ),
+ )
.toList(),
),
);
@@ -52,43 +51,164 @@ class MenuSheetButton extends StatelessWidget {
return values.map>(createPopupMenuItem).toList();
}
- PopupMenuEntry createPopupMenuItem(dynamic value) => PopupMenuItem(
- value: value,
- child: Text(value.label ?? ''), //TODO add Icon
- );
+ PopupMenuEntry createPopupMenuItem(T value) => PopupMenuItem(
+ value: value,
+ child: Text(value.label ?? ''),
+ );
@override
Widget build(BuildContext context) {
return builder(context);
}
-//This should be a modal bottom sheet if on Mobile (See https://mercyjemosop.medium.com/select-and-upload-images-to-firebase-storage-flutter-6fac855970a9)
Widget builder(BuildContext context, {Iterable? vals}) {
- Iterable values = vals ?? values_!;
+ Iterable values = vals ?? this.values;
+ if (values.isEmpty) {
+ return const SizedBox.shrink();
+ }
return values.length == 1 ||
- Get.mediaQuery.orientation == Orientation.portrait
- // : Get.context!.isPortrait
+ Get.mediaQuery.orientation == Orientation.portrait
? (icon != null
- ? IconButton(
- onPressed: () => buttonPressed(values),
- icon: icon!,
- tooltip: label,
- )
- : TextButton(
- onPressed: () => buttonPressed(values),
- child: Text(label ?? 'Need Label')))
+ ? IconButton(
+ onPressed: () => buttonPressed(values),
+ icon: icon!,
+ tooltip: label,
+ )
+ : TextButton(
+ onPressed: () => buttonPressed(values),
+ child: Text(label ?? 'Need Label')))
: PopupMenuButton(
- itemBuilder: (context_) => getItems(context_, values),
- icon: icon,
- tooltip: label,
- onSelected: (T value) async =>
- callbackFunc(await value.doAction()));
+ itemBuilder: (context_) => getItems(context_, values),
+ icon: icon,
+ tooltip: label,
+ onSelected: (T value) async =>
+ callbackFunc(await value.doAction()),
+ );
}
- void buttonPressed(Iterable values) async => values.length == 1
- ? callbackFunc(await values.first.doAction())
- : Get.bottomSheet(MenuSheetButton.bottomSheet(values, callbackFunc),
+ void buttonPressed(Iterable values) async {
+ if (values.length == 1) {
+ callbackFunc(await values.first.doAction());
+ } else {
+ Get.bottomSheet(MenuSheetButton.bottomSheet(values, callbackFunc),
backgroundColor: Colors.white);
+ }
+ }
+
+ void callbackFunc(dynamic act) {}
+}
+
+class ImagePickerButton extends StatelessWidget {
+ final ValueSetter? callback;
+
+ const ImagePickerButton({
+ super.key,
+ this.icon = const Icon(Icons.image),
+ this.label = 'Pick an Image',
+ this.callback,
+ });
+
+ final Icon icon;
+ final String label;
+
+ void callbackFunc(dynamic act) {
+ if (callback != null && act is String?) {
+ callback!(act);
+ } else if (act == null) {
+ Get.snackbar('Error', 'No image selected.');
+ }
+ }
- void callbackFunc(act) {}
+ @override
+ Widget build(BuildContext context) {
+ return !(GetPlatform.isAndroid || GetPlatform.isIOS)
+ ? TextButton.icon(
+ onPressed: () async => callbackFunc(await ImageSources.getFile()),
+ icon: icon,
+ label: const Text('Pick an Image'),
+ )
+ : IconButton(
+ icon: icon,
+ tooltip: label,
+ onPressed: () async {
+ final result = await showModalBottomSheet(
+ context: context,
+ builder: (context) => _buildBottomSheet(),
+ );
+ if (result != null) {
+ final actionResult = await result.doAction();
+ callbackFunc(actionResult);
+ }
+ },
+ );
+ }
+
+ Widget _buildBottomSheet() {
+ return SizedBox(
+ height: 180,
+ child: ListView(
+ children: ImageSources.values.map((source) {
+ return ListTile(
+ leading: Icon(source.icon),
+ title: Text(source.label ?? ''),
+ onTap: () => Get.back(result: source),
+ );
+ }).toList(),
+ ),
+ );
+ }
+}
+
+// Assuming you have an enum called ImageSources
+enum ImageSources implements ActionEnum {
+ camera(Icons.camera, 'Camera'),
+ gallery(Icons.photo_library, 'Gallery'),
+ file(Icons.file_upload, 'File');
+
+ const ImageSources(this.icon, this.label);
+
+ @override
+ final IconData? icon;
+ @override
+ final String? label;
+
+ @override
+ Future doAction() async {
+ switch (this) {
+ case ImageSources.camera:
+ return await getImage(ImageSource.camera);
+ case ImageSources.gallery:
+ return await getImage(ImageSource.gallery);
+ case ImageSources.file:
+ return await getFile();
+ default:
+ return null;
+ }
+ }
+
+ static Future getImage(ImageSource imageSource) async {
+ final pickedFile = await ImagePicker().pickImage(source: imageSource);
+ if (pickedFile != null) {
+ return pickedFile.path;
+ } else {
+ Get.snackbar('Error', 'Image Not Selected');
+ return null;
+ }
+ }
+
+ static Future getFile() async {
+ FilePickerResult? result = await FilePicker.platform
+ .pickFiles(type: FileType.image, allowMultiple: false);
+
+ if (result != null && result.files.isNotEmpty) {
+ final fileBytes = result.files.first.bytes;
+ final fileName = result.files.first.name;
+ GetStorage().write(fileName, fileBytes);
+
+ return fileName;
+ } else {
+ Get.snackbar('Error', 'Image Not Selected');
+ return null;
+ }
+ }
}
diff --git a/lib/app/widgets/screen_widget.dart b/lib/app/widgets/screen_widget.dart
index d80c9275..bd2b1288 100644
--- a/lib/app/widgets/screen_widget.dart
+++ b/lib/app/widgets/screen_widget.dart
@@ -19,7 +19,7 @@ class ScreenWidget extends StatelessWidget {
super.key,
required this.body,
required this.screen,
- this.role = Role.buyer,
+ this.role = Role.guest,
this.delegate,
this.currentRoute,
this.appBar,
diff --git a/lib/main.dart b/lib/main.dart
index 30c258f2..db7a9705 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -4,7 +4,7 @@ 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 'app/modules/cart/controllers/cart_controller.dart';
import 'app/routes/app_pages.dart';
import 'firebase_options.dart';
import 'services/auth_service.dart';
@@ -16,29 +16,21 @@ void main() async {
options: DefaultFirebaseOptions.currentPlatform,
);
+ Get.put(CartController());
+
runApp(
- GetMaterialApp.router(
- debugShowCheckedModeBanner:
- false, //the debug banner will automatically disappear in prod build
+ GetMaterialApp(
+ debugShowCheckedModeBanner: false, //the debug banner will automatically disappear in prod build
title: 'Application',
- initialBinding: BindingsBuilder(
- () {
- Get.put(AuthService());
- },
- ),
+ initialBinding: BindingsBuilder(() {
+ Get.put(AuthService());
+ }),
+ initialRoute: Routes.SPLASH, // Set the initial route to the splash screen
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)),
+ highlightColor: Colors.black.withOpacity(0.5),
+ bottomSheetTheme: const BottomSheetThemeData(surfaceTintColor: Colors.blue),
+ ),
),
);
}
diff --git a/lib/models/product.dart b/lib/models/product.dart
index 003d5785..50a25454 100644
--- a/lib/models/product.dart
+++ b/lib/models/product.dart
@@ -1,9 +1,17 @@
class Product {
- final String name;
final String id;
+ final String name;
+ final String location;
+ final double price;
+ final String imageAsset;
+
Product({
- required this.name,
required this.id,
+ required this.name,
+ required this.location,
+ required this.price,
+ required this.imageAsset,
+
});
}
diff --git a/lib/models/product_details.dart b/lib/models/product_details.dart
new file mode 100644
index 00000000..b48b2e1c
--- /dev/null
+++ b/lib/models/product_details.dart
@@ -0,0 +1,21 @@
+class Product {
+ final String id;
+ final String name;
+ final String location;
+ final double price;
+ final String imageAsset;
+ final String description;
+ final List additionalImages; // Additional images
+
+
+ Product ({
+ required this.id,
+ required this.name,
+ required this.location,
+ required this.price,
+ required this.imageAsset,
+ required this.description,
+ required this.additionalImages,
+
+ });
+}
\ No newline at end of file
diff --git a/lib/models/role.dart b/lib/models/role.dart
index 50ee31b4..20209726 100644
--- a/lib/models/role.dart
+++ b/lib/models/role.dart
@@ -1,23 +1,13 @@
import 'screens.dart';
-// First tab for all except Admin is Home/Dashboard which is diferrent for each role
-// Admin is User List By Roles with slide to Change Role or Disable
-// Second tab for
-// Guest & Buyer is Public Product List by Category with Slide to Add to Cart
-// Seller is Product List by Category with Add Product FAB leading to Product Form
-// Admin is Category List with Add Category FAB
-// Third tab for
-// Guest is Cart with Guest Auth
-// Buyer is Cart with own Auth
-// Seller is MyProducts
-// Admin is Tasks/Approvals
-// Profile and Settings is in Drawer
+// updated the roles to guest, registeredUser and admin
+// guest can access Home, Search and Profile Screens
+//admin has access to all screens, including managing hotels and users
enum Role {
- buyer([Screen.DASHBOARD, Screen.PRODUCTS, Screen.CART]),
- seller([Screen.DASHBOARD, Screen.PRODUCTS, Screen.MY_PRODUCTS]),
- admin([Screen.USERS, Screen.CATEGORIES, Screen.TASKS]);
-//higher role can assume a lower role
+ guest([Screen.HOME, Screen.PRODUCTS, Screen.LOGIN, Screen.REGISTER, Screen.PROFILE]),
+ registeredUser([Screen.HOME, Screen.PRODUCTS, Screen.CHECKOUT, Screen.PROFILE, Screen.CART]),
+ admin([Screen.USERS, Screen.HOME,Screen.MY_PRODUCTS,Screen.CHECKOUT, Screen.PROFILE, Screen.CATEGORIES,Screen.TASKS]);
const Role(this.permissions);
final List
@@ -25,7 +15,7 @@ enum Role {
static Role fromString(String? name) => (name != null
? Role.values.firstWhere((role) => role.name == name)
- : Role.buyer);
+ : Role.guest);
bool hasAccess(Role role) => index >= role.index;
bool hasAccessOf(String role) => index >= fromString(role).index;
diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart
index 8bf72aaa..d88a78ac 100644
--- a/lib/services/auth_service.dart
+++ b/lib/services/auth_service.dart
@@ -16,7 +16,7 @@ class AuthService extends GetxService {
final FirebaseAuth _auth = FirebaseAuth.instance;
late Rxn credential = Rxn();
final Rxn _firebaseUser = Rxn();
- final Rx _userRole = Rx(Role.buyer);
+ final Rx _userRole = Rx(Role.registeredUser);
final Rx robot = RxBool(useRecaptcha);
final RxBool registered = false.obs;
@@ -29,14 +29,34 @@ class AuthService extends GetxService {
if (useEmulator) _auth.useAuthEmulator(emulatorHost, 9099);
_firebaseUser.bindStream(_auth.authStateChanges());
_auth.authStateChanges().listen((User? user) {
+ _firebaseUser.value = user;
if (user != null) {
user.getIdTokenResult().then((token) {
_userRole.value = Role.fromString(token.claims?["role"]);
});
+ } else {
+ _userRole.value = Role.guest;
}
});
}
+ Future registerWithEmailAndPassword(String email, String password) async {
+ try {
+ UserCredential userCredential = await _auth.createUserWithEmailAndPassword(
+ email: email,
+ password: password,
+ );
+ return userCredential.user;
+ } on FirebaseAuthException catch (e) {
+ Get.snackbar('Error', e.message ?? 'Unknown error occurred');
+ return null;
+ } catch (e) {
+ Get.snackbar('Error', 'An unexpected error occurred: $e');
+ print('Exception: $e');
+ return null;
+ }
+ }
+
bool get isEmailVerified =>
user != null && (user!.email == null || user!.emailVerified);
@@ -48,30 +68,24 @@ class AuthService extends GetxService {
bool get isAnon => user != null && user!.isAnonymous;
- String? get userName => (user != null && !user!.isAnonymous)
+ String? get userName => user != null && !user!.isAnonymous
? (user!.displayName ?? user!.email)
: 'Guest';
void login() {
- // this is not needed as we are using Firebase UI for the login part
+ // This is not needed as we are using Firebase UI for the login part
}
void sendVerificationMail({EmailAuthCredential? emailAuth}) async {
if (sendMailFromClient) {
if (_auth.currentUser != null) {
await _auth.currentUser?.sendEmailVerification();
- } else if (emailAuth != null) {
- // Approach 1: sending email auth link requires deep linking which is
- // a TODO as the current Flutter methods are deprecated
- // sendSingInLink(emailAuth);
-
- // Approach 2: This is a hack.
- // We are using createUser to send the verification link from the server side by adding suffix .verify in the email
- // if the user already exists and the password is also same and sign in occurs via custom token on server side
+ } else if (emailAuth != null && credential.value != null) {
try {
await _auth.createUserWithEmailAndPassword(
- email: "${credential.value!.email}.verify",
- password: credential.value!.password!);
+ email: "${credential.value!.email}.verify",
+ password: credential.value!.password!,
+ );
} on FirebaseAuthException catch (e) {
int i = e.message!.indexOf("message") + 10;
int j = e.message!.indexOf('"', i);
@@ -80,40 +94,27 @@ class AuthService extends GetxService {
'Please verify your email by clicking the link on the Email sent',
);
}
+ } else {
+ Get.snackbar('Error', 'No credentials found to send verification email.');
}
}
}
- 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]
+ url: '$baseUrl:5001/flutterfast-92c25/us-central1/handleEmailLinkVerification',
handleCodeInApp: true,
- // iOSBundleId: '$bundleID.ios',
- // androidPackageName: '$bundleID.android',
- // // installIfNotAvailable
- // androidInstallApp: true,
- // // minimumVersion
- // androidMinimumVersion: '12'
);
_auth
.sendSignInLinkToEmail(email: emailAuth.email, actionCodeSettings: acs)
- .catchError(
- (onError) => print('Error sending email verification $onError'))
+ .catchError((onError) => print('Error sending email verification $onError'))
.then((value) => print('Successfully sent email verification'));
}
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
+ final thenTo = Get.rootDelegate.currentConfiguration?.currentPage?.parameters?['then'];
+ Get.rootDelegate.offAndToNamed(thenTo ?? Screen.PROFILE.route);
}
void logout() {
@@ -124,12 +125,13 @@ class AuthService extends GetxService {
Future guest() async {
return await Get.defaultDialog(
- middleText: 'Sign in as Guest',
- barrierDismissible: true,
- onConfirm: loginAsGuest,
- onCancel: () => Get.back(result: false),
- textConfirm: 'Yes, will SignUp later',
- textCancel: 'No, will SignIn now');
+ middleText: 'Sign in as Guest',
+ barrierDismissible: true,
+ onConfirm: loginAsGuest,
+ onCancel: () => Get.back(result: false),
+ textConfirm: 'Yes, will SignUp later',
+ textCancel: 'No, will SignIn now',
+ );
}
void loginAsGuest() async {
@@ -141,13 +143,7 @@ class AuthService extends GetxService {
'Signed in with temporary account.',
);
} on FirebaseAuthException catch (e) {
- switch (e.code) {
- case "operation-not-allowed":
- print("Anonymous auth hasn't been enabled for this project.");
- break;
- default:
- print("Unknown error.");
- }
+ print(e.message);
Get.back(result: false);
}
}
@@ -158,15 +154,11 @@ 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";
+ verification = "Please verify email id by clicking the link on the email sent";
} else {
callback(false, credential.value);
}
diff --git a/pubspec.lock b/pubspec.lock
index 877fc75e..96fc7b0f 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -49,6 +49,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
+ cloud_firestore:
+ dependency: "direct main"
+ description:
+ name: cloud_firestore
+ sha256: a0f161b92610e078b4962d7e6ebeb66dc9cce0ada3514aeee442f68165d78185
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.17.5"
+ cloud_firestore_platform_interface:
+ dependency: transitive
+ description:
+ name: cloud_firestore_platform_interface
+ sha256: "6a55b319f8d33c307396b9104512e8130a61904528ab7bd8b5402678fca54b81"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.2.5"
+ cloud_firestore_web:
+ dependency: transitive
+ description:
+ name: cloud_firestore_web
+ sha256: "89dfa1304d3da48b3039abbb2865e3d30896ef858e569a16804a99f4362283a9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.12.5"
collection:
dependency: transitive
description:
@@ -61,18 +85,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 +133,18 @@ 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_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,10 +173,10 @@ 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:
@@ -181,26 +205,26 @@ packages:
dependency: "direct main"
description:
name: firebase_auth
- sha256: f0a75f61992d036e4c46ad0e9febd364d98aa2c092690a5475cb1421a8243cfe
+ sha256: cfc2d970829202eca09e2896f0a5aa7c87302817ecc0bdfa954f026046bf10ba
url: "https://pub.dev"
source: hosted
- version: "4.19.5"
+ version: "4.20.0"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
- sha256: feb77258404309ffc7761c78e1c0ad2ed5e4fdc378e035619e2cc13be4397b62
+ sha256: a0270e1db3b2098a14cb2a2342b3cd2e7e458e0c391b1f64f6f78b14296ec093
url: "https://pub.dev"
source: hosted
- version: "7.2.6"
+ version: "7.3.0"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
- sha256: "6d527f357da2bf93a67a42b423aa92943104a0c290d1d72ad9a42c779d501cd2"
+ sha256: "64e067e763c6378b7e774e872f0f59f6812885e43020e25cde08f42e9459837b"
url: "https://pub.dev"
source: hosted
- version: "5.11.5"
+ version: "5.12.0"
firebase_core:
dependency: "direct main"
description:
@@ -213,34 +237,34 @@ packages:
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: "47b8c8a8546d8a7f9000edb90848549f20b137d814ee7e0407b3d43b8445e282"
url: "https://pub.dev"
source: hosted
- version: "5.5.5"
+ version: "5.5.7"
firebase_dynamic_links_platform_interface:
dependency: transitive
description:
name: firebase_dynamic_links_platform_interface
- sha256: f86992605b50e2f0ce6c24993430affc98021da8d8a74d5596b7a2c84196c110
+ sha256: "72e7810635f908ce060c5803c7acb29116c5b6befc73e90446c52722bc9506a2"
url: "https://pub.dev"
source: hosted
- version: "0.2.6+33"
+ version: "0.2.6+35"
firebase_remote_config:
dependency: "direct main"
description:
@@ -269,26 +293,26 @@ packages:
dependency: "direct main"
description:
name: firebase_storage
- sha256: da76ca9c11d795c4bae1bd13b31d54bb9eb9ccbee7eb5f6b86b8294370e9d488
+ sha256: "2ae478ceec9f458c1bcbf0ee3e0100e4e909708979e83f16d5d9fba35a5b42c1"
url: "https://pub.dev"
source: hosted
- version: "11.7.5"
+ version: "11.7.7"
firebase_storage_platform_interface:
dependency: transitive
description:
name: firebase_storage_platform_interface
- sha256: be17bfa9110a6429b40dd3760c755034079fd734aa1dd2476d5638ab780cc508
+ sha256: "4e18662e6a66e2e0e181c06f94707de06d5097d70cfe2b5141bf64660c5b5da9"
url: "https://pub.dev"
source: hosted
- version: "5.1.20"
+ version: "5.1.22"
firebase_storage_web:
dependency: transitive
description:
name: firebase_storage_web
- sha256: "5219c20c0768a8e2ffedf0a116b7bc80ab32fcc6e2cbd50cbde14f8c4575c3f4"
+ sha256: "3a44aacd38a372efb159f6fe36bb4a7d79823949383816457fd43d3d47602a53"
url: "https://pub.dev"
source: hosted
- version: "3.9.5"
+ version: "3.9.7"
firebase_ui_auth:
dependency: "direct main"
description:
@@ -351,10 +375,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:
@@ -401,10 +425,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 +441,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 +465,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
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 +489,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:
@@ -537,18 +561,18 @@ packages:
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:
@@ -577,26 +601,26 @@ 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:
name: mime
- sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
+ sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
- version: "1.0.5"
+ version: "1.0.6"
path:
dependency: "direct main"
description:
@@ -617,18 +641,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 +681,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 +697,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:
@@ -734,10 +758,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:
@@ -782,10 +806,10 @@ packages:
dependency: transitive
description:
name: vm_service
- sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
+ sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
- version: "14.2.1"
+ version: "14.2.4"
web:
dependency: transitive
description:
@@ -798,10 +822,10 @@ packages:
dependency: transitive
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 +843,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..a7b09601 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -10,7 +10,8 @@ dependencies:
get: 4.6.6
flutter:
sdk: flutter
- firebase_core: ^2.31.0
+ cloud_firestore: ^4.8.1
+ firebase_core: ^2.32.0
firebase_ui_auth: ^1.14.0
firebase_auth: ^4.19.5
google_sign_in: ^6.2.1
@@ -36,8 +37,13 @@ flutter:
fonts:
- asset: packages/firebase_ui_auth/fonts/SocialIcons.ttf
assets:
- - assets/images/flutterfire_300x.png
- - assets/images/dash.png
- - assets/icons/logo.png
+ - assets/icons/
+ - assets/Eva/
+ - assets/Fiona/
+ - assets/Flora/
+ - assets/Historia/
+ - assets/Plush/
+ - assets/Stay/
+
uses-material-design: true