From 985f57647b2a942829776cc914739bd82c2097e0 Mon Sep 17 00:00:00 2001 From: winterpigeon Date: Fri, 30 Aug 2024 14:46:39 +0530 Subject: [PATCH 1/4] feat: added product admin view --- .metadata | 25 ++- .vscode/settings.json | 3 +- android/app/google-services.json | 46 ++++++ .../example/get_flutter_fire/MainActivity.kt | 5 + assets/icons/images.jpg | Bin 0 -> 6652 bytes assets/icons/sharekhan_logo.png | Bin 0 -> 12677 bytes firebase.json | 1 + .../controllers/products_controller.dart | 2 +- .../bindings/products_admin_binding.dart | 9 ++ .../products_admin_controller.dart | 62 ++++++++ .../views/add_product_view.dart | 36 +++++ .../views/products_admin_view.dart | 30 ++++ lib/app/modules/root/views/root_view.dart | 9 +- lib/app/routes/app_pages.dart | 8 +- lib/app/routes/app_routes.dart | 1 - lib/models/products_admin.dart | 60 ++++++++ lib/models/screens.dart | 5 + pubspec.lock | 144 +++++++++--------- pubspec.yaml | 72 ++++----- 19 files changed, 397 insertions(+), 121 deletions(-) create mode 100644 android/app/google-services.json create mode 100644 android/app/src/main/kotlin/com/example/get_flutter_fire/MainActivity.kt create mode 100644 assets/icons/images.jpg create mode 100644 assets/icons/sharekhan_logo.png create mode 100644 firebase.json create mode 100644 lib/app/modules/products_admin/bindings/products_admin_binding.dart create mode 100644 lib/app/modules/products_admin/controllers/products_admin_controller.dart create mode 100644 lib/app/modules/products_admin/views/add_product_view.dart create mode 100644 lib/app/modules/products_admin/views/products_admin_view.dart create mode 100644 lib/models/products_admin.dart diff --git a/.metadata b/.metadata index 784ce129..90eabcff 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3" + revision: "80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819" channel: "stable" project_type: app @@ -13,11 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + - platform: android + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + - platform: ios + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + - platform: linux + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + - platform: macos + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 - platform: web - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + - platform: windows + create_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 + base_revision: 80c2e84975bbd28ecf5f8d4bd4ca5a2490bfc819 # User provided section diff --git a/.vscode/settings.json b/.vscode/settings.json index e0120650..3da905aa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { "java.compile.nullAnalysis.mode": "automatic", - "java.configuration.updateBuildConfiguration": "interactive" + "java.configuration.updateBuildConfiguration": "interactive", + "cmake.sourceDirectory": "D:/Sahil/flutter-apps/Sharekhan/get_flutter_fire/windows/flutter" } \ No newline at end of file diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 00000000..eda0a863 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,46 @@ +{ + "project_info": { + "project_number": "784525273637", + "project_id": "sharekhan-project", + "storage_bucket": "sharekhan-project.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:784525273637:android:c1966d53b8f4eb96626dcc", + "android_client_info": { + "package_name": "com.example.get_flutter_fire" + } + }, + "oauth_client": [ + { + "client_id": "784525273637-605r9ms12s7lbjikujvcvb8nsrqbebmr.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCeBbH7YPz08UZsiirSsJQ0mDtFqYezHsQ" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "784525273637-605r9ms12s7lbjikujvcvb8nsrqbebmr.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "784525273637-a5miokt96an7utjtb3dntpaag9ifb9i9.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.example.getFlutterFire" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/example/get_flutter_fire/MainActivity.kt b/android/app/src/main/kotlin/com/example/get_flutter_fire/MainActivity.kt new file mode 100644 index 00000000..018e286b --- /dev/null +++ b/android/app/src/main/kotlin/com/example/get_flutter_fire/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.get_flutter_fire + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/assets/icons/images.jpg b/assets/icons/images.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ab6605d3d1fefa97b71a3efe980e90c3dad2cf5 GIT binary patch literal 6652 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 literal 0 HcmV?d00001 diff --git a/assets/icons/sharekhan_logo.png b/assets/icons/sharekhan_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..8631d74affc76146d3b7d167db1fd90aafc13569 GIT binary patch literal 12677 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 literal 0 HcmV?d00001 diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..cf30bb38 --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"sharekhan-project","appId":"1:784525273637:android:c1966d53b8f4eb96626dcc","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"sharekhan-project","configurations":{"android":"1:784525273637:android:c1966d53b8f4eb96626dcc","ios":"1:784525273637:ios:0d855743258edd6e626dcc","macos":"1:784525273637:ios:0d855743258edd6e626dcc","web":"1:784525273637:web:b34a7d2fd4f32969626dcc","windows":"1:784525273637:web:90330fbc8bedc31e626dcc"}}}}}} \ No newline at end of file diff --git a/lib/app/modules/products/controllers/products_controller.dart b/lib/app/modules/products/controllers/products_controller.dart index 118c7dc8..6c01a201 100644 --- a/lib/app/modules/products/controllers/products_controller.dart +++ b/lib/app/modules/products/controllers/products_controller.dart @@ -17,7 +17,7 @@ class ProductsController extends GetxController { @override void onReady() { super.onReady(); - loadDemoProductsFromSomeWhere(); + // loadDemoProductsFromSomeWhere(); } @override diff --git a/lib/app/modules/products_admin/bindings/products_admin_binding.dart b/lib/app/modules/products_admin/bindings/products_admin_binding.dart new file mode 100644 index 00000000..46eefcd8 --- /dev/null +++ b/lib/app/modules/products_admin/bindings/products_admin_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import '../controllers/products_admin_controller.dart'; + +class ProductsAdminBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => ProductsAdminController()); + } +} diff --git a/lib/app/modules/products_admin/controllers/products_admin_controller.dart b/lib/app/modules/products_admin/controllers/products_admin_controller.dart new file mode 100644 index 00000000..93f87192 --- /dev/null +++ b/lib/app/modules/products_admin/controllers/products_admin_controller.dart @@ -0,0 +1,62 @@ +import 'package:get/get.dart'; +import 'package:get_flutter_fire/models/products_admin.dart'; + +class ProductsAdminController extends GetxController { + // List to store products + final RxList products = [].obs; + + // Method to fetch all products (for now, we will simulate with a list of sample data) + void fetchProducts() { + // Simulated fetching of products, replace this with actual API calls + List fetchedProducts = [ + Product( + id: '1', + name: 'Laptop', + description: 'A high-performance laptop', + price: 1200.0, + imageUrl: 'https://example.com/laptop.png', + ), + Product( + id: '2', + name: 'Smartphone', + description: 'A latest-gen smartphone', + price: 800.0, + imageUrl: 'https://example.com/smartphone.png', + ), + ]; + + // Update the products list + products.assignAll(fetchedProducts); + } + + // Method to add a new product + void addProduct(Product product) { + // Add the new product to the list + products.add(product); + } + + // Method to update an existing product + void updateProduct(String productId, Product updatedProduct) { + int index = products.indexWhere((product) => product.id == productId); + if (index != -1) { + products[index] = updatedProduct; + } + } + + // Method to delete a product by ID + void deleteProduct(String productId) { + products.removeWhere((product) => product.id == productId); + } + + // Optional: Method to find a product by ID + Product? findProductById(String productId) { + return products.firstWhereOrNull((product) => product.id == productId); + } + + @override + void onInit() { + super.onInit(); + // Fetch products when the controller is initialized + fetchProducts(); + } +} diff --git a/lib/app/modules/products_admin/views/add_product_view.dart b/lib/app/modules/products_admin/views/add_product_view.dart new file mode 100644 index 00000000..a94c3e23 --- /dev/null +++ b/lib/app/modules/products_admin/views/add_product_view.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class AddProductView extends StatelessWidget { + const AddProductView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text("Add Product"), + ), + body: SingleChildScrollView( + child: Container( + width: double.maxFinite, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Add New Product', + style: TextStyle( + fontSize: 30, + color: Colors.red, + fontWeight: FontWeight.bold), + ), + Text('data'), + Text('data'), + Text('data'), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/modules/products_admin/views/products_admin_view.dart b/lib/app/modules/products_admin/views/products_admin_view.dart new file mode 100644 index 00000000..59ff71ac --- /dev/null +++ b/lib/app/modules/products_admin/views/products_admin_view.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:get_flutter_fire/app/modules/products_admin/views/add_product_view.dart'; +import '../controllers/products_admin_controller.dart'; + +class ProductsAdminView extends GetView { + const ProductsAdminView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: ListView.builder( + itemCount: 10, + itemBuilder: (context, index) { + return ListTile( + title: Text('Title'), + subtitle: Text('Price: 199'), + trailing: IconButton(onPressed: () {}, icon: Icon(Icons.delete)), + ); + }), + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.orange, + onPressed: () { + Get.to(AddProductView()); + }, + child: Icon(Icons.add), + ), + ); + } +} diff --git a/lib/app/modules/root/views/root_view.dart b/lib/app/modules/root/views/root_view.dart index 2bbf228c..37d2cef5 100644 --- a/lib/app/modules/root/views/root_view.dart +++ b/lib/app/modules/root/views/root_view.dart @@ -22,7 +22,9 @@ class RootView extends GetView { appBar: AppBar( title: Text(title ?? ''), centerTitle: true, - leading: GetPlatform.isIOS // Since Web and Android have back button + leading: (GetPlatform.isIOS || + GetPlatform + .isAndroid) // Since Web and Android have back button && current.locationString.contains(RegExp(r'(\/[^\/]*){3,}')) ? BackButton( @@ -30,9 +32,8 @@ class RootView extends GetView { Get.rootDelegate.popRoute(), //Navigator.pop(context), ) : IconButton( - icon: ImageIcon( - const AssetImage("icons/logo.png"), - color: Colors.grey.shade800, + icon: Image( + image: new AssetImage("assets/icons/sharekhan_logo.png"), ), onPressed: () => AuthService.to.isLoggedInValue ? controller.openDrawer() diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 7269755d..62457c0a 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:get_flutter_fire/app/modules/products_admin/bindings/products_admin_binding.dart'; +import 'package:get_flutter_fire/app/modules/products_admin/views/products_admin_view.dart'; import '../../models/access_level.dart'; import '../../models/role.dart'; @@ -72,6 +74,10 @@ class AppPages { page: () => const SettingsView(), binding: SettingsBinding(), ), + Screen.PRODUCTS_ADMIN.getPage( + page: () => const ProductsAdminView(), + binding: ProductsAdminBinding(), + ), Screen.HOME.getPage( page: () => const HomeView(), bindings: [ @@ -145,7 +151,7 @@ class AppPages { binding: TaskDetailsBinding(), ), ], - ), + ) ], ) ], diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index f3129d21..29fb888d 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -17,7 +17,6 @@ abstract class Routes { // static const TASKS = _Paths.HOME + _Paths.TASKS; // 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'; static String CART_DETAILS(String productId) => diff --git a/lib/models/products_admin.dart b/lib/models/products_admin.dart new file mode 100644 index 00000000..01f216f2 --- /dev/null +++ b/lib/models/products_admin.dart @@ -0,0 +1,60 @@ +class Product { + final String id; + final String name; + final String description; + final double price; + final String imageUrl; + final bool isAvailable; + + Product({ + required this.id, + required this.name, + required this.description, + required this.price, + required this.imageUrl, + this.isAvailable = true, + }); + + // Factory constructor to create a Product from JSON + factory Product.fromJson(Map json) { + return Product( + id: json['id'] as String, + name: json['name'] as String, + description: json['description'] as String, + price: (json['price'] as num).toDouble(), + imageUrl: json['imageUrl'] as String, + isAvailable: json['isAvailable'] as bool? ?? true, + ); + } + + // Method to convert Product to JSON + Map toJson() { + return { + 'id': id, + 'name': name, + 'description': description, + 'price': price, + 'imageUrl': imageUrl, + 'isAvailable': isAvailable, + }; + } + + // Optional: Add methods to copy the object or handle other operations + Product copyWith({ + String? id, + String? name, + String? description, + double? price, + String? imageUrl, + bool? isAvailable, + }) { + return Product( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + price: price ?? this.price, + imageUrl: imageUrl ?? this.imageUrl, + isAvailable: isAvailable ?? this.isAvailable, + ); + } +} diff --git a/lib/models/screens.dart b/lib/models/screens.dart index 24dee39f..74f24de5 100644 --- a/lib/models/screens.dart +++ b/lib/models/screens.dart @@ -101,6 +101,11 @@ enum Screen implements ActionEnum { label: "Logout", accessor_: AccessedVia.bottomSheet, accessLevel: AccessLevel.authenticated), + PRODUCTS_ADMIN('/products-admin', + icon: Icons.admin_panel_settings, + label: "Products List", + accessor_: AccessedVia.drawer, + accessLevel: AccessLevel.authenticated), ; const Screen(this.path, diff --git a/pubspec.lock b/pubspec.lock index 877fc75e..fa65a210 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -61,18 +61,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 +109,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 +149,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 +181,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 +213,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 +269,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 +351,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" + sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" url: "https://pub.dev" source: hosted - version: "2.0.19" + version: "2.0.21" flutter_svg: dependency: transitive description: @@ -401,10 +401,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 +417,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 +441,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 +465,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 +537,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,18 +577,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -617,18 +617,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 +657,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 +673,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 +734,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 +782,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" web: dependency: transitive description: @@ -798,10 +798,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 +819,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..aebfdc67 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,42 +2,42 @@ name: get_flutter_fire version: 1.0.0+1 publish_to: none description: Boilerplate for Flutter with GetX, showing sample utilization of Firebase capabilities -environment: - sdk: '>=3.3.4 <4.0.0' +environment: + sdk: ">=3.3.4 <4.0.0" -dependencies: - cupertino_icons: ^1.0.6 - get: 4.6.6 - flutter: - sdk: flutter - firebase_core: ^2.31.0 - firebase_ui_auth: ^1.14.0 - firebase_auth: ^4.19.5 - google_sign_in: ^6.2.1 - firebase_ui_oauth_google: ^1.3.2 - google_fonts: ^6.2.1 - firebase_storage: ^11.7.5 - image_picker: ^1.1.1 - file_picker: ^8.0.3 - path: ^1.9.0 - get_storage: ^2.1.1 - firebase_ui_localizations: ^1.12.0 - firebase_remote_config: ^4.4.7 - firebase_analytics: ^10.10.7 +dependencies: + cupertino_icons: ^1.0.6 + get: 4.6.6 + flutter: + sdk: flutter + firebase_core: ^2.31.0 + firebase_ui_auth: ^1.14.0 + firebase_auth: ^4.19.5 + google_sign_in: ^6.2.1 + firebase_ui_oauth_google: ^1.3.2 + google_fonts: ^6.2.1 + firebase_storage: ^11.7.5 + image_picker: ^1.1.1 + file_picker: ^8.0.3 + path: ^1.9.0 + get_storage: ^2.1.1 + firebase_ui_localizations: ^1.12.0 + firebase_remote_config: ^4.4.7 + firebase_analytics: ^10.10.7 -dev_dependencies: - flutter_lints: 3.0.2 - flutter_test: - sdk: flutter - -flutter: - fonts: - - family: SocialIcons - fonts: - - asset: packages/firebase_ui_auth/fonts/SocialIcons.ttf - assets: - - assets/images/flutterfire_300x.png - - assets/images/dash.png - - assets/icons/logo.png - uses-material-design: true +dev_dependencies: + flutter_lints: 3.0.2 + flutter_test: + sdk: flutter +flutter: + fonts: + - family: SocialIcons + 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/sharekhan_logo.png + uses-material-design: true From 264e3eb19ecb23d7f5b3f180eb28d5e78cf4dff0 Mon Sep 17 00:00:00 2001 From: winterpigeon Date: Sat, 31 Aug 2024 23:15:06 +0530 Subject: [PATCH 2/4] feat: product CRUD and client side --- .../cart/controllers/cart_controller.dart | 35 +- lib/app/modules/cart/views/cart_view.dart | 51 ++- .../controllers/products_controller.dart | 89 ++++- .../views/products_descriptions_view.dart | 84 +++++ .../modules/products/views/products_view.dart | 117 ++++-- .../products_admin_controller.dart | 116 +++--- .../views/add_product_view.dart | 143 +++++-- .../views/products_admin_view.dart | 42 ++- lib/app/routes/app_pages.dart | 2 +- lib/app/widgets/drop_down_button.dart | 58 +++ lib/app/widgets/drop_down_checklist.dart | 102 +++++ lib/app/widgets/product_card.dart | 73 ++++ lib/models/product_category.dart | 20 + lib/models/product_category.g.dart | 19 + lib/models/products_admin.dart | 92 ++--- lib/models/products_admin.g.dart | 29 ++ pubspec.lock | 357 ++++++++++++++++++ pubspec.yaml | 6 +- 18 files changed, 1205 insertions(+), 230 deletions(-) create mode 100644 lib/app/modules/products/views/products_descriptions_view.dart create mode 100644 lib/app/widgets/drop_down_button.dart create mode 100644 lib/app/widgets/drop_down_checklist.dart create mode 100644 lib/app/widgets/product_card.dart create mode 100644 lib/models/product_category.dart create mode 100644 lib/models/product_category.g.dart create mode 100644 lib/models/products_admin.g.dart diff --git a/lib/app/modules/cart/controllers/cart_controller.dart b/lib/app/modules/cart/controllers/cart_controller.dart index c938ec4c..c201afe4 100644 --- a/lib/app/modules/cart/controllers/cart_controller.dart +++ b/lib/app/modules/cart/controllers/cart_controller.dart @@ -1,23 +1,30 @@ import 'package:get/get.dart'; +import 'package:get_flutter_fire/models/products_admin.dart'; class CartController extends GetxController { - //TODO: Implement CartController + var cartItems = {}.obs; // Map of product to quantity - final count = 0.obs; - @override - void onInit() { - super.onInit(); + void addToCart(Product product, int quantity) { + if (cartItems.containsKey(product)) { + cartItems[product] = cartItems[product]! + quantity; + } else { + cartItems[product] = quantity; + } + update(); } - @override - void onReady() { - super.onReady(); + void removeFromCart(Product product) { + if (cartItems.containsKey(product)) { + if (cartItems[product]! > 1) { + cartItems[product] = cartItems[product]! - 1; // Decrease quantity + } else { + cartItems.remove(product); // Remove if quantity is 1 or less + } + update(); + } } - @override - void onClose() { - super.onClose(); - } - - void increment() => count.value++; + double get totalPrice => cartItems.entries + .map((entry) => entry.key.price! * entry.value) + .fold(0, (sum, price) => sum + price); } diff --git a/lib/app/modules/cart/views/cart_view.dart b/lib/app/modules/cart/views/cart_view.dart index 3e048c79..8f216126 100644 --- a/lib/app/modules/cart/views/cart_view.dart +++ b/lib/app/modules/cart/views/cart_view.dart @@ -7,21 +7,46 @@ import '../../../../services/auth_service.dart'; import '../controllers/cart_controller.dart'; class CartView extends GetView { - const CartView({super.key}); @override Widget build(BuildContext context) { - return ScreenWidget( - appBar: AppBar( - title: Text('${AuthService.to.userName} Cart'), - centerTitle: true, - ), - body: const Center( - child: Text( - 'CartView is working', - style: TextStyle(fontSize: 20), + return GetBuilder(builder: (cartController) { + return Scaffold( + body: Obx(() { + if (cartController.cartItems.isEmpty) { + return const Center(child: Text('No items in cart')); + } + return ListView.builder( + itemCount: cartController.cartItems.length, + itemBuilder: (context, index) { + var product = cartController.cartItems.keys.toList()[index]; + var quantity = cartController.cartItems[product]!; + return ListTile( + title: Text(product.name ?? 'Product'), + subtitle: Text('Quantity: $quantity'), + trailing: Text('Rs. ${product.price! * quantity}'), + onTap: () { + // Navigate to product description or allow editing quantity + }, + ); + }, + ); + }), + bottomNavigationBar: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Total: Rs. ${cartController.totalPrice}'), + ElevatedButton( + onPressed: () { + // Proceed to payment or checkout + }, + child: const Text('Proceed to Payment'), + ), + ], + ), ), - ), - screen: screen!, - ); + ); + }); } } diff --git a/lib/app/modules/products/controllers/products_controller.dart b/lib/app/modules/products/controllers/products_controller.dart index 6c01a201..f74d1b54 100644 --- a/lib/app/modules/products/controllers/products_controller.dart +++ b/lib/app/modules/products/controllers/products_controller.dart @@ -1,28 +1,81 @@ import 'package:get/get.dart'; - -import '../../../../models/product.dart'; +import 'package:get_flutter_fire/models/product_category.dart'; +import 'package:get_flutter_fire/models/products_admin.dart'; +import 'package:flutter/material.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; class ProductsController extends GetxController { - final products = [].obs; + FirebaseFirestore firestore = FirebaseFirestore.instance; + late CollectionReference prodCollection; + late CollectionReference categoryCollection; + List products = []; + List productsInUI = []; + List categories = []; + @override + Future onInit() async { + prodCollection = firestore.collection('products'); + categoryCollection = firestore.collection('category'); + await fetchCategory(); + await fetchProducts(); + super.onInit(); + } - void loadDemoProductsFromSomeWhere() { - products.add( - Product( - name: 'Product added on: ${DateTime.now().toString()}', - id: DateTime.now().millisecondsSinceEpoch.toString(), - ), - ); + fetchProducts() async { + try { + QuerySnapshot productSnapshot = await prodCollection.get(); + final List retrievedProducts = productSnapshot.docs + .map((doc) => Product.fromJson(doc.data() as Map)) + .toList(); + products.clear(); + products.assignAll(retrievedProducts); + productsInUI.clear(); + productsInUI.assignAll(products); + } on Exception catch (e) { + Get.snackbar('Error', e.toString(), colorText: Colors.red); + } finally { + update(); + } } - @override - void onReady() { - super.onReady(); - // loadDemoProductsFromSomeWhere(); + fetchCategory() async { + try { + QuerySnapshot categorySnapshot = await categoryCollection.get(); + final List retrievedCategories = categorySnapshot.docs + .map((doc) => + ProductCategory.fromJson(doc.data() as Map)) + .toList(); + categories.clear(); + categories.assignAll(retrievedCategories); + } on Exception catch (e) { + Get.snackbar('Error', e.toString(), colorText: Colors.red); + } finally { + update(); + } } - @override - void onClose() { - Get.printInfo(info: 'Products: onClose'); - super.onClose(); + filterByCategory(String category) { + productsInUI.clear(); + productsInUI = + products.where((product) => product.category == category).toList(); + update(); + } + + filterByBrand(List brands) { + if (brands.isEmpty) { + productsInUI = products; + } else { + productsInUI = + products.where((product) => brands.contains(product.brand)).toList(); + } + update(); + } + + sortByPrice({required bool ascending}) { + List sortedProducts = List.from(productsInUI); + sortedProducts.sort((a, b) => ascending + ? a.price!.compareTo(b.price!) + : b.price!.compareTo(a.price!)); + productsInUI = sortedProducts; + update(); } } diff --git a/lib/app/modules/products/views/products_descriptions_view.dart b/lib/app/modules/products/views/products_descriptions_view.dart new file mode 100644 index 00000000..c8aed8e6 --- /dev/null +++ b/lib/app/modules/products/views/products_descriptions_view.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:get_flutter_fire/models/products_admin.dart'; + +class ProductsDescriptionsView extends StatelessWidget { + final Product product; + const ProductsDescriptionsView({super.key, required this.product}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Product Details'), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.network( + product.image ?? '', + fit: BoxFit.cover, + width: double.infinity, + height: 300, + )), + const SizedBox( + height: 20, + ), + Text(product.name ?? '', + style: + const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), + const SizedBox( + height: 20, + ), + Text( + product.description ?? '', + style: TextStyle(fontSize: 16, height: 1.5), + ), + const SizedBox( + height: 20, + ), + Text( + 'Rs. ${product.price}', + style: TextStyle( + fontSize: 20, + color: Colors.orange, + fontWeight: FontWeight.bold), + ), + const SizedBox( + height: 20, + ), + TextField( + maxLines: 3, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + labelText: 'Enter Your Billing Address'), + ), + const SizedBox( + height: 20, + ), + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(vertical: 15), + backgroundColor: Colors.orange, + ), + child: const Text( + 'Buy Now', + style: TextStyle(fontSize: 18, color: Colors.white), + ), + onPressed: () {}, + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/app/modules/products/views/products_view.dart b/lib/app/modules/products/views/products_view.dart index 5b190a6a..b271ae01 100644 --- a/lib/app/modules/products/views/products_view.dart +++ b/lib/app/modules/products/views/products_view.dart @@ -2,9 +2,10 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; - -import '../../../../models/role.dart'; -import '../../../routes/app_pages.dart'; +import 'package:get_flutter_fire/app/modules/products/views/products_descriptions_view.dart'; +import 'package:get_flutter_fire/app/widgets/drop_down_button.dart'; +import 'package:get_flutter_fire/app/widgets/drop_down_checklist.dart'; +import 'package:get_flutter_fire/app/widgets/product_card.dart'; import '../controllers/products_controller.dart'; class ProductsView extends GetView { @@ -12,47 +13,81 @@ class ProductsView extends GetView { @override Widget build(BuildContext context) { - var arg = Get.rootDelegate.arguments(); - return Scaffold( - floatingActionButton: - (arg != null && Get.rootDelegate.arguments()["role"] == Role.seller) - ? FloatingActionButton.extended( - onPressed: controller.loadDemoProductsFromSomeWhere, - label: const Text('Add'), - ) - : null, - body: Column( - children: [ - const Hero( - tag: 'heroLogo', - child: FlutterLogo(), - ), - Expanded( - child: Obx( - () => RefreshIndicator( - onRefresh: () async { - controller.products.clear(); - controller.loadDemoProductsFromSomeWhere(); - }, + return GetBuilder(builder: (ctrl) { + return RefreshIndicator( + onRefresh: () async { + ctrl.fetchProducts(); + }, + child: Scaffold( + body: Column( + children: [ + SizedBox( + height: 50, child: ListView.builder( - itemCount: controller.products.length, + scrollDirection: Axis.horizontal, + itemCount: ctrl.categories.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + ctrl.filterByCategory( + ctrl.categories[index].name ?? ''); + }, + child: Padding( + padding: EdgeInsets.all(6), + child: Chip( + label: Text(ctrl.categories[index].name ?? ''), + ), + ), + ); + })), + Row( + children: [ + Flexible( + child: DropDownBtn( + items: ['Rs: low to high', 'Rs: high to low'], + selectedItemsText: 'Sort', + onSelected: (selected) { + print(selected); + ctrl.sortByPrice( + ascending: + selected == 'Rs: low to high' ? true : false); + }), + ), + Flexible( + child: DropDownChecklist( + items: ['Nike', 'Zara', 'H&M', 'Samsung', 'Bata'], + onSelectionChanged: (selectedItems) { + ctrl.filterByBrand(selectedItems); + }, + )) + ], + ), + Expanded( + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 0.8, + crossAxisSpacing: 8, + mainAxisSpacing: 8), + itemCount: ctrl.productsInUI.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 + return ProductCard( + name: ctrl.productsInUI[index].name ?? 'Name', + imageUrl: ctrl.productsInUI[index].image ?? 'url', + price: ctrl.productsInUI[index].price ?? 200, + offerTag: '20% off', + productTap: () { + Get.to( + ProductsDescriptionsView( + product: ctrl.productsInUI[index]), + ); }, - title: Text(item.name), - subtitle: Text(item.id), ); - }, - ), - ), - ), - ), - ], - ), - ); + }), + ) + ], + )), + ); + }); } } diff --git a/lib/app/modules/products_admin/controllers/products_admin_controller.dart b/lib/app/modules/products_admin/controllers/products_admin_controller.dart index 93f87192..040b0d43 100644 --- a/lib/app/modules/products_admin/controllers/products_admin_controller.dart +++ b/lib/app/modules/products_admin/controllers/products_admin_controller.dart @@ -1,62 +1,86 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_flutter_fire/models/products_admin.dart'; class ProductsAdminController extends GetxController { - // List to store products - final RxList products = [].obs; + FirebaseFirestore firestore = FirebaseFirestore.instance; + late CollectionReference prodCollection; + TextEditingController productNameCtrl = TextEditingController(); + TextEditingController productDescriptionCtrl = TextEditingController(); + TextEditingController productImgCtrl = TextEditingController(); + TextEditingController productPriceCtrl = TextEditingController(); + String category = 'Category'; + String brand = 'Brand'; + bool offer = false; - // Method to fetch all products (for now, we will simulate with a list of sample data) - void fetchProducts() { - // Simulated fetching of products, replace this with actual API calls - List fetchedProducts = [ - Product( - id: '1', - name: 'Laptop', - description: 'A high-performance laptop', - price: 1200.0, - imageUrl: 'https://example.com/laptop.png', - ), - Product( - id: '2', - name: 'Smartphone', - description: 'A latest-gen smartphone', - price: 800.0, - imageUrl: 'https://example.com/smartphone.png', - ), - ]; - - // Update the products list - products.assignAll(fetchedProducts); - } - - // Method to add a new product - void addProduct(Product product) { - // Add the new product to the list - products.add(product); + List products = []; + @override + Future onInit() async { + prodCollection = firestore.collection('products'); + await fetchProducts(); + super.onInit(); } - // Method to update an existing product - void updateProduct(String productId, Product updatedProduct) { - int index = products.indexWhere((product) => product.id == productId); - if (index != -1) { - products[index] = updatedProduct; + addProduct() { + try { + DocumentReference doc = prodCollection.doc(); + Product product = Product( + id: doc.id, + name: productNameCtrl.text, + category: category, + description: productDescriptionCtrl.text, + price: double.tryParse(productPriceCtrl.text), + brand: brand, + image: productImgCtrl.text, + offer: offer, + ); + final productJson = product.toJson(); + doc.set(productJson); + Get.snackbar('Success', 'Product Added Successfully', + colorText: Colors.green); + setDefaultValues(); + fetchProducts(); + } on Exception catch (e) { + Get.snackbar('Error', e.toString(), colorText: Colors.green); + print(e); } } - // Method to delete a product by ID - void deleteProduct(String productId) { - products.removeWhere((product) => product.id == productId); + setDefaultValues() { + productNameCtrl.clear(); + productDescriptionCtrl.clear(); + productPriceCtrl.clear(); + productImgCtrl.clear(); + category = 'Category'; + brand = 'Brand'; + offer = false; + update(); } - // Optional: Method to find a product by ID - Product? findProductById(String productId) { - return products.firstWhereOrNull((product) => product.id == productId); + fetchProducts() async { + try { + QuerySnapshot productSnapshot = await prodCollection.get(); + final List retrievedProducts = productSnapshot.docs + .map((doc) => Product.fromJson(doc.data() as Map)) + .toList(); + products.clear(); + products.assignAll(retrievedProducts); + } on Exception catch (e) { + Get.snackbar('Error', e.toString(), colorText: Colors.red); + } finally { + update(); + } } - @override - void onInit() { - super.onInit(); - // Fetch products when the controller is initialized - fetchProducts(); + deleteProduct(String id) async { + try { + await prodCollection.doc(id).delete(); + fetchProducts(); + Get.snackbar('Success', 'Products Deleted Successfully', + colorText: Colors.green); + } on Exception catch (e) { + Get.snackbar('Error', e.toString(), colorText: Colors.red); + } } } diff --git a/lib/app/modules/products_admin/views/add_product_view.dart b/lib/app/modules/products_admin/views/add_product_view.dart index a94c3e23..6780506d 100644 --- a/lib/app/modules/products_admin/views/add_product_view.dart +++ b/lib/app/modules/products_admin/views/add_product_view.dart @@ -1,36 +1,131 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:get_flutter_fire/app/modules/products_admin/controllers/products_admin_controller.dart'; +import 'package:get_flutter_fire/app/widgets/drop_down_button.dart'; class AddProductView extends StatelessWidget { const AddProductView({super.key}); @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: Text("Add Product"), - ), - body: SingleChildScrollView( - child: Container( - width: double.maxFinite, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - 'Add New Product', - style: TextStyle( - fontSize: 30, - color: Colors.red, - fontWeight: FontWeight.bold), - ), - Text('data'), - Text('data'), - Text('data'), - ], + return GetBuilder(builder: (ctrl) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text( + 'Add New Product', + style: TextStyle( + fontSize: 30, + color: Colors.orange, + fontWeight: FontWeight.bold), ), ), - ), - ); + body: SingleChildScrollView( + child: Container( + margin: EdgeInsets.all(10), + width: double.maxFinite, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + TextField( + controller: ctrl.productNameCtrl, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10)), + label: const Text("Product Name"), + hintStyle: const TextStyle( + color: Color.fromARGB(255, 160, 160, 160)), + hintText: "Enter Your Product Name"), + ), + SizedBox(height: 10), + TextField( + controller: ctrl.productDescriptionCtrl, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10)), + label: const Text("Product Description"), + hintStyle: const TextStyle( + color: Color.fromARGB(255, 160, 160, 160)), + hintText: "Enter Your Product Description"), + maxLines: 4, + ), + SizedBox(height: 10), + TextField( + controller: ctrl.productImgCtrl, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10)), + label: const Text("Image URL"), + hintStyle: const TextStyle( + color: Color.fromARGB(255, 160, 160, 160)), + hintText: "Enter Your Product's Image"), + ), + SizedBox(height: 10), + TextField( + controller: ctrl.productPriceCtrl, + decoration: InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10)), + label: const Text("Product Price"), + hintStyle: const TextStyle( + color: Color.fromARGB(255, 160, 160, 160)), + hintText: "Enter Your Product's Price"), + ), + const SizedBox(height: 10), + Row( + children: [ + Flexible( + child: DropDownBtn( + items: const [ + 'Clothing', + 'Shoes', + 'Electronics', + 'Sports' + ], + selectedItemsText: ctrl.category, + onSelected: (selectedValue) { + ctrl.category = selectedValue ?? 'Category'; + ctrl.update(); + }, + )), + Flexible( + child: DropDownBtn( + items: const [ + 'Nike', + 'Zara', + 'H&M', + 'Samsung', + 'Bata' + ], + selectedItemsText: ctrl.brand, + onSelected: (selectedValue) { + ctrl.brand = selectedValue ?? 'Brand'; + ctrl.update(); + })), + ], + ), + Text("Offer Products"), + DropDownBtn( + items: ['true', 'false'], + selectedItemsText: ctrl.offer == true ? 'yes' : 'no', + onSelected: (selectedValue) { + ctrl.offer = + bool.tryParse(selectedValue ?? 'false') ?? false; + ctrl.update(); + }), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange, + foregroundColor: Colors.white), + onPressed: () { + ctrl.addProduct(); + }, + child: const Text("Add Product")) + ], + ), + ), + ), + ); + }); } } diff --git a/lib/app/modules/products_admin/views/products_admin_view.dart b/lib/app/modules/products_admin/views/products_admin_view.dart index 59ff71ac..72d4e1a6 100644 --- a/lib/app/modules/products_admin/views/products_admin_view.dart +++ b/lib/app/modules/products_admin/views/products_admin_view.dart @@ -8,23 +8,29 @@ class ProductsAdminView extends GetView { @override Widget build(BuildContext context) { - return Scaffold( - body: ListView.builder( - itemCount: 10, - itemBuilder: (context, index) { - return ListTile( - title: Text('Title'), - subtitle: Text('Price: 199'), - trailing: IconButton(onPressed: () {}, icon: Icon(Icons.delete)), - ); - }), - floatingActionButton: FloatingActionButton( - backgroundColor: Colors.orange, - onPressed: () { - Get.to(AddProductView()); - }, - child: Icon(Icons.add), - ), - ); + return GetBuilder(builder: (ctrl) { + return Scaffold( + body: ListView.builder( + itemCount: ctrl.products.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(ctrl.products[index].name ?? ''), + subtitle: Text((ctrl.products[index].price ?? '').toString()), + trailing: IconButton( + onPressed: () { + ctrl.deleteProduct(ctrl.products[index].id ?? ''); + }, + icon: Icon(Icons.delete)), + ); + }), + floatingActionButton: FloatingActionButton( + backgroundColor: Colors.orange, + onPressed: () { + Get.to(AddProductView()); + }, + child: Icon(Icons.add), + ), + ); + }); } } diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 62457c0a..3a9dee6c 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -115,7 +115,7 @@ class AppPages { binding: CategoriesBinding(), ), Screen.CART.getPage( - page: () => const CartView(), + page: () => CartView(), binding: CartBinding(), role: Role.buyer, children: [ diff --git a/lib/app/widgets/drop_down_button.dart b/lib/app/widgets/drop_down_button.dart new file mode 100644 index 00000000..b3d608ab --- /dev/null +++ b/lib/app/widgets/drop_down_button.dart @@ -0,0 +1,58 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; + +String? selectedValue; + +class DropDownBtn extends StatelessWidget { + final List items; + final String selectedItemsText; + final Function(String?) onSelected; + const DropDownBtn( + {super.key, + required this.items, + required this.selectedItemsText, + required this.onSelected}); + + @override + Widget build(BuildContext context) { + return Card( + child: Center( + child: DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + hint: Text( + selectedItemsText, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).hintColor, + ), + ), + items: items + .map((String item) => DropdownMenuItem( + value: item, + child: Text( + item, + style: const TextStyle( + fontSize: 14, + ), + ), + )) + .toList(), + value: selectedValue, + onChanged: (String? value) { + onSelected(value); + }, + buttonStyleData: const ButtonStyleData( + padding: EdgeInsets.symmetric(horizontal: 16), + height: 40, + width: 140, + ), + menuItemStyleData: const MenuItemStyleData( + height: 40, + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/widgets/drop_down_checklist.dart b/lib/app/widgets/drop_down_checklist.dart new file mode 100644 index 00000000..d6b4ef5c --- /dev/null +++ b/lib/app/widgets/drop_down_checklist.dart @@ -0,0 +1,102 @@ +import 'package:dropdown_button2/dropdown_button2.dart'; +import 'package:flutter/material.dart'; + +List selectedItems = []; + +class DropDownChecklist extends StatelessWidget { + final List items; + final Function(List) onSelectionChanged; + const DropDownChecklist( + {super.key, required this.items, required this.onSelectionChanged}); + + @override + Widget build(BuildContext context) { + return Card( + child: Center( + child: DropdownButtonHideUnderline( + child: DropdownButton2( + isExpanded: true, + hint: Text( + 'Brand', + style: TextStyle( + fontSize: 14, + color: Theme.of(context).hintColor, + ), + ), + items: items.map((item) { + return DropdownMenuItem( + value: item, + //disable default onTap to avoid closing menu when selecting an item + enabled: false, + child: StatefulBuilder( + builder: (context, menuSetState) { + final isSelected = selectedItems.contains(item); + return InkWell( + onTap: () { + isSelected + ? selectedItems.remove(item) + : selectedItems.add(item); + onSelectionChanged(selectedItems); + menuSetState(() {}); + }, + child: Container( + height: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Row( + children: [ + if (isSelected) + const Icon(Icons.check_box_outlined) + else + const Icon(Icons.check_box_outline_blank), + const SizedBox(width: 16), + Expanded( + child: Text( + item, + style: const TextStyle( + fontSize: 14, + ), + ), + ), + ], + ), + ), + ); + }, + ), + ); + }).toList(), + //Use last selected item as the current value so if we've limited menu height, it scroll to last item. + value: selectedItems.isEmpty ? null : selectedItems.last, + onChanged: (value) {}, + selectedItemBuilder: (context) { + return items.map( + (item) { + return Container( + alignment: AlignmentDirectional.center, + child: Text( + selectedItems.join(', '), + style: const TextStyle( + fontSize: 14, + overflow: TextOverflow.ellipsis, + ), + maxLines: 1, + ), + ); + }, + ).toList(); + }, + buttonStyleData: const ButtonStyleData( + padding: EdgeInsets.only(left: 16, right: 8), + height: 40, + width: 140, + ), + menuItemStyleData: const MenuItemStyleData( + height: 40, + padding: EdgeInsets.zero, + ), + ), + ), + ), + ); + } +} diff --git a/lib/app/widgets/product_card.dart b/lib/app/widgets/product_card.dart new file mode 100644 index 00000000..0eb108c6 --- /dev/null +++ b/lib/app/widgets/product_card.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +class ProductCard extends StatelessWidget { + const ProductCard( + {super.key, + required this.name, + required this.imageUrl, + required this.offerTag, + required this.price, + required this.productTap}); + final String name; + final String imageUrl; + final double price; + final String offerTag; + final Function productTap; + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () { + productTap(); + }, + child: Card( + elevation: 2, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + imageUrl, + fit: BoxFit.cover, + width: double.maxFinite, + height: 120, + ), + ), + SizedBox( + height: 8, + ), + Text( + name, + style: TextStyle(fontSize: 16), + overflow: TextOverflow.ellipsis, + ), + SizedBox( + height: 8, + ), + Text( + 'Rs. $price', + style: TextStyle(fontSize: 16), + overflow: TextOverflow.ellipsis, + ), + SizedBox( + height: 4, + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(4)), + child: Text( + offerTag, + style: const TextStyle(color: Colors.white), + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/models/product_category.dart b/lib/models/product_category.dart new file mode 100644 index 00000000..95fc3d80 --- /dev/null +++ b/lib/models/product_category.dart @@ -0,0 +1,20 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'product_category.g.dart'; + +@JsonSerializable() +class ProductCategory { + @JsonKey(name: "id") + String? id; + + @JsonKey(name: "name") + String? name; + + ProductCategory({ + this.id, + this.name, + }); + + factory ProductCategory.fromJson(Map json) => + _$ProductCategoryFromJson(json); + Map toJson() => _$ProductCategoryToJson(this); +} diff --git a/lib/models/product_category.g.dart b/lib/models/product_category.g.dart new file mode 100644 index 00000000..ed3fa193 --- /dev/null +++ b/lib/models/product_category.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'product_category.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ProductCategory _$ProductCategoryFromJson(Map json) => + ProductCategory( + id: json['id'] as String?, + name: json['name'] as String?, + ); + +Map _$ProductCategoryToJson(ProductCategory instance) => + { + 'id': instance.id, + 'name': instance.name, + }; diff --git a/lib/models/products_admin.dart b/lib/models/products_admin.dart index 01f216f2..54a64e6b 100644 --- a/lib/models/products_admin.dart +++ b/lib/models/products_admin.dart @@ -1,60 +1,44 @@ +import 'package:json_annotation/json_annotation.dart'; +part 'products_admin.g.dart'; + +@JsonSerializable() class Product { - final String id; - final String name; - final String description; - final double price; - final String imageUrl; - final bool isAvailable; + @JsonKey(name: "id") + String? id; + + @JsonKey(name: "name") + String? name; + + @JsonKey(name: "description") + String? description; + + @JsonKey(name: "category") + String? category; + + @JsonKey(name: "image") + String? image; + + @JsonKey(name: "price") + double? price; + + @JsonKey(name: "brand") + String? brand; + + @JsonKey(name: "offer") + bool? offer; Product({ - required this.id, - required this.name, - required this.description, - required this.price, - required this.imageUrl, - this.isAvailable = true, + this.id, + this.name, + this.description, + this.category, + this.image, + this.offer, + this.price, + this.brand, }); - // Factory constructor to create a Product from JSON - factory Product.fromJson(Map json) { - return Product( - id: json['id'] as String, - name: json['name'] as String, - description: json['description'] as String, - price: (json['price'] as num).toDouble(), - imageUrl: json['imageUrl'] as String, - isAvailable: json['isAvailable'] as bool? ?? true, - ); - } - - // Method to convert Product to JSON - Map toJson() { - return { - 'id': id, - 'name': name, - 'description': description, - 'price': price, - 'imageUrl': imageUrl, - 'isAvailable': isAvailable, - }; - } - - // Optional: Add methods to copy the object or handle other operations - Product copyWith({ - String? id, - String? name, - String? description, - double? price, - String? imageUrl, - bool? isAvailable, - }) { - return Product( - id: id ?? this.id, - name: name ?? this.name, - description: description ?? this.description, - price: price ?? this.price, - imageUrl: imageUrl ?? this.imageUrl, - isAvailable: isAvailable ?? this.isAvailable, - ); - } + factory Product.fromJson(Map json) => + _$ProductFromJson(json); + Map toJson() => _$ProductToJson(this); } diff --git a/lib/models/products_admin.g.dart b/lib/models/products_admin.g.dart new file mode 100644 index 00000000..58d707f2 --- /dev/null +++ b/lib/models/products_admin.g.dart @@ -0,0 +1,29 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'products_admin.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Product _$ProductFromJson(Map json) => Product( + id: json['id'] as String?, + name: json['name'] as String?, + description: json['description'] as String?, + category: json['category'] as String?, + image: json['image'] as String?, + offer: json['offer'] as bool?, + price: (json['price'] as num?)?.toDouble(), + brand: json['brand'] as String?, + ); + +Map _$ProductToJson(Product instance) => { + 'id': instance.id, + 'name': instance.name, + 'description': instance.description, + 'category': instance.category, + 'image': instance.image, + 'price': instance.price, + 'brand': instance.brand, + 'offer': instance.offer, + }; diff --git a/pubspec.lock b/pubspec.lock index fa65a210..8cc6581b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + url: "https://pub.dev" + source: hosted + version: "72.0.0" _flutterfire_internals: dependency: transitive description: @@ -9,6 +17,19 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.35" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + url: "https://pub.dev" + source: hosted + version: "6.7.0" args: dependency: transitive description: @@ -33,6 +54,70 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 + url: "https://pub.dev" + source: hosted + version: "2.4.12" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.dev" + source: hosted + version: "7.3.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" characters: dependency: transitive description: @@ -41,6 +126,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" clock: dependency: transitive description: @@ -49,6 +142,38 @@ 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" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" collection: dependency: transitive description: @@ -57,6 +182,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" cross_file: dependency: transitive description: @@ -81,6 +214,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" desktop_webview_auth: dependency: transitive description: @@ -89,6 +230,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.15" + dropdown_button2: + dependency: "direct main" + description: + name: dropdown_button2 + sha256: b0fe8d49a030315e9eef6c7ac84ca964250155a6224d491c1365061bc974a9e1 + url: "https://pub.dev" + source: hosted + version: "2.3.9" email_validator: dependency: transitive description: @@ -113,6 +262,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" file_picker: dependency: "direct main" description: @@ -329,6 +486,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -373,6 +538,14 @@ packages: description: flutter source: sdk version: "0.0.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" get: dependency: "direct main" description: @@ -389,6 +562,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" google_fonts: dependency: "direct main" description: @@ -445,6 +626,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.12.4+2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" http: dependency: transitive description: @@ -453,6 +642,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" http_parser: dependency: transitive description: @@ -533,6 +730,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + url: "https://pub.dev" + source: hosted + version: "6.8.0" leak_tracker: dependency: transitive description: @@ -565,6 +794,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -597,6 +842,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: "direct main" description: @@ -685,11 +938,67 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.dev" + source: hosted + version: "2.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" source_span: dependency: transitive description: @@ -714,6 +1023,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" string_scanner: dependency: transitive description: @@ -738,6 +1055,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.2" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" typed_data: dependency: transitive description: @@ -786,6 +1111,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.5" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" web: dependency: transitive description: @@ -794,6 +1127,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" win32: dependency: transitive description: @@ -818,6 +1167,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.5.0 <4.0.0" flutter: ">=3.24.0" diff --git a/pubspec.yaml b/pubspec.yaml index aebfdc67..41d8b7be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,17 +15,21 @@ dependencies: firebase_auth: ^4.19.5 google_sign_in: ^6.2.1 firebase_ui_oauth_google: ^1.3.2 + cloud_firestore: ^4.17.5 google_fonts: ^6.2.1 firebase_storage: ^11.7.5 image_picker: ^1.1.1 file_picker: ^8.0.3 + json_annotation: ^4.9.0 path: ^1.9.0 get_storage: ^2.1.1 firebase_ui_localizations: ^1.12.0 firebase_remote_config: ^4.4.7 firebase_analytics: ^10.10.7 - + dropdown_button2: ^2.3.9 dev_dependencies: + json_serializable: ^6.8.0 + build_runner: ^2.4.12 flutter_lints: 3.0.2 flutter_test: sdk: flutter From 5e413dd5db2c65dfd046af31d140626045c112c3 Mon Sep 17 00:00:00 2001 From: winterpigeon Date: Sun, 1 Sep 2024 03:12:33 +0530 Subject: [PATCH 3/4] feat: cart setup --- .../modules/cart/bindings/cart_binding.dart | 6 +- lib/app/modules/cart/views/cart_view.dart | 2 + .../controllers/products_controller.dart | 32 ++++++++++ .../modules/products/views/products_view.dart | 19 +++++- lib/app/modules/root/views/drawer.dart | 32 ++++++++-- lib/app/routes/app_pages.dart | 2 +- lib/app/widgets/product_card.dart | 64 ++++++++++++------- lib/main.dart | 2 + lib/services/auth_service.dart | 55 ++++------------ 9 files changed, 137 insertions(+), 77 deletions(-) diff --git a/lib/app/modules/cart/bindings/cart_binding.dart b/lib/app/modules/cart/bindings/cart_binding.dart index 009c52ae..a2fa356c 100644 --- a/lib/app/modules/cart/bindings/cart_binding.dart +++ b/lib/app/modules/cart/bindings/cart_binding.dart @@ -5,8 +5,8 @@ import '../controllers/cart_controller.dart'; class CartBinding extends Bindings { @override void dependencies() { - Get.lazyPut( - () => CartController(), - ); + // Get.lazyPut( + // () => CartController(), + // ); } } diff --git a/lib/app/modules/cart/views/cart_view.dart b/lib/app/modules/cart/views/cart_view.dart index 8f216126..bae263bc 100644 --- a/lib/app/modules/cart/views/cart_view.dart +++ b/lib/app/modules/cart/views/cart_view.dart @@ -7,6 +7,8 @@ import '../../../../services/auth_service.dart'; import '../controllers/cart_controller.dart'; class CartView extends GetView { + const CartView({super.key}); + @override Widget build(BuildContext context) { return GetBuilder(builder: (cartController) { diff --git a/lib/app/modules/products/controllers/products_controller.dart b/lib/app/modules/products/controllers/products_controller.dart index f74d1b54..c2324521 100644 --- a/lib/app/modules/products/controllers/products_controller.dart +++ b/lib/app/modules/products/controllers/products_controller.dart @@ -8,9 +8,14 @@ class ProductsController extends GetxController { FirebaseFirestore firestore = FirebaseFirestore.instance; late CollectionReference prodCollection; late CollectionReference categoryCollection; + List products = []; List productsInUI = []; List categories = []; + + // Map to track quantities of each product in the UI + RxMap productQuantities = {}.obs; + @override Future onInit() async { prodCollection = firestore.collection('products'); @@ -20,6 +25,7 @@ class ProductsController extends GetxController { super.onInit(); } + // Fetch products from Firestore fetchProducts() async { try { QuerySnapshot productSnapshot = await prodCollection.get(); @@ -30,6 +36,13 @@ class ProductsController extends GetxController { products.assignAll(retrievedProducts); productsInUI.clear(); productsInUI.assignAll(products); + + // Initialize product quantities to 0 for all products + for (var product in products) { + if (!productQuantities.containsKey(product)) { + productQuantities[product] = 0; + } + } } on Exception catch (e) { Get.snackbar('Error', e.toString(), colorText: Colors.red); } finally { @@ -37,6 +50,7 @@ class ProductsController extends GetxController { } } + // Fetch categories from Firestore fetchCategory() async { try { QuerySnapshot categorySnapshot = await categoryCollection.get(); @@ -53,6 +67,7 @@ class ProductsController extends GetxController { } } + // Filter products by category filterByCategory(String category) { productsInUI.clear(); productsInUI = @@ -60,6 +75,7 @@ class ProductsController extends GetxController { update(); } + // Filter products by brand filterByBrand(List brands) { if (brands.isEmpty) { productsInUI = products; @@ -70,6 +86,7 @@ class ProductsController extends GetxController { update(); } + // Sort products by price sortByPrice({required bool ascending}) { List sortedProducts = List.from(productsInUI); sortedProducts.sort((a, b) => ascending @@ -78,4 +95,19 @@ class ProductsController extends GetxController { productsInUI = sortedProducts; update(); } + + // Increment the quantity of a product + void incrementProductQuantity(Product product) { + productQuantities[product] = (productQuantities[product]!) + 1; + update(); + } + + // Decrement the quantity of a product + void decrementProductQuantity(Product product) { + final currentQuantity = productQuantities[product]!; + if (currentQuantity > 0) { + productQuantities[product] = currentQuantity - 1; + update(); + } + } } diff --git a/lib/app/modules/products/views/products_view.dart b/lib/app/modules/products/views/products_view.dart index b271ae01..69fc9221 100644 --- a/lib/app/modules/products/views/products_view.dart +++ b/lib/app/modules/products/views/products_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:get_flutter_fire/app/modules/cart/controllers/cart_controller.dart'; import 'package:get_flutter_fire/app/modules/products/views/products_descriptions_view.dart'; import 'package:get_flutter_fire/app/widgets/drop_down_button.dart'; import 'package:get_flutter_fire/app/widgets/drop_down_checklist.dart'; @@ -13,6 +14,7 @@ class ProductsView extends GetView { @override Widget build(BuildContext context) { + var cartController = Get.find(); return GetBuilder(builder: (ctrl) { return RefreshIndicator( onRefresh: () async { @@ -66,7 +68,7 @@ class ProductsView extends GetView { child: GridView.builder( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, - childAspectRatio: 0.8, + childAspectRatio: 0.6, crossAxisSpacing: 8, mainAxisSpacing: 8), itemCount: ctrl.productsInUI.length, @@ -76,6 +78,21 @@ class ProductsView extends GetView { imageUrl: ctrl.productsInUI[index].image ?? 'url', price: ctrl.productsInUI[index].price ?? 200, offerTag: '20% off', + quantity: ctrl.productQuantities[ + ctrl.productsInUI[index]]!, // Show product quantity + onIncrease: () { + ctrl.incrementProductQuantity(ctrl.productsInUI[index]); + cartController.addToCart(ctrl.productsInUI[index], 1); + }, + onDecrease: () { + if (ctrl.productQuantities[ctrl.productsInUI[index]]! > + 0) { + ctrl.decrementProductQuantity( + ctrl.productsInUI[index]); + cartController + .removeFromCart(ctrl.productsInUI[index]); + } + }, productTap: () { Get.to( ProductsDescriptionsView( diff --git a/lib/app/modules/root/views/drawer.dart b/lib/app/modules/root/views/drawer.dart index 908d0223..7beddff6 100644 --- a/lib/app/modules/root/views/drawer.dart +++ b/lib/app/modules/root/views/drawer.dart @@ -38,12 +38,32 @@ class DrawerWidget extends StatelessWidget { height: 100, color: Colors.red, //adding content in the highlighted part of the drawer - child: Align( - alignment: Alignment.centerLeft, - child: Container( - margin: const EdgeInsets.only(left: 15), - child: const Text('User Name', //Profile Icon also - style: TextStyle(fontWeight: FontWeight.bold)))), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: CircleAvatar( + backgroundColor: Colors.white, + backgroundImage: AuthService.to.user != null && + AuthService.to.user!.photoURL != null + ? NetworkImage(AuthService.to.user!.photoURL!) + : null, + child: AuthService.to.user == null || + AuthService.to.user!.photoURL == null + ? Icon(Icons.person, color: Colors.grey[800]) + : null, + radius: 30, + ), + ), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Text('${AuthService.to.userName}', // Profile Icon also + style: TextStyle(fontWeight: FontWeight.bold)), + ), + ), + ], + ), ) ]; diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 3a9dee6c..62457c0a 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -115,7 +115,7 @@ class AppPages { binding: CategoriesBinding(), ), Screen.CART.getPage( - page: () => CartView(), + page: () => const CartView(), binding: CartBinding(), role: Role.buyer, children: [ diff --git a/lib/app/widgets/product_card.dart b/lib/app/widgets/product_card.dart index 0eb108c6..6995d331 100644 --- a/lib/app/widgets/product_card.dart +++ b/lib/app/widgets/product_card.dart @@ -1,18 +1,27 @@ import 'package:flutter/material.dart'; class ProductCard extends StatelessWidget { - const ProductCard( - {super.key, - required this.name, - required this.imageUrl, - required this.offerTag, - required this.price, - required this.productTap}); + const ProductCard({ + super.key, + required this.name, + required this.imageUrl, + required this.offerTag, + required this.price, + required this.quantity, + required this.onIncrease, + required this.onDecrease, + required this.productTap, + }); + final String name; final String imageUrl; final double price; final String offerTag; + final int quantity; + final Function onIncrease; + final Function onDecrease; final Function productTap; + @override Widget build(BuildContext context) { return InkWell( @@ -32,38 +41,49 @@ class ProductCard extends StatelessWidget { imageUrl, fit: BoxFit.cover, width: double.maxFinite, - height: 120, + height: 170, + // alignment: Alignment.topCenter, ), ), - SizedBox( - height: 8, - ), + const SizedBox(height: 8), Text( name, - style: TextStyle(fontSize: 16), + style: const TextStyle(fontSize: 16), overflow: TextOverflow.ellipsis, ), - SizedBox( - height: 8, - ), + const SizedBox(height: 8), Text( 'Rs. $price', - style: TextStyle(fontSize: 16), + style: const TextStyle(fontSize: 16), overflow: TextOverflow.ellipsis, ), - SizedBox( - height: 4, - ), + const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( - color: Colors.green, - borderRadius: BorderRadius.circular(4)), + color: Colors.green, + borderRadius: BorderRadius.circular(4), + ), child: Text( offerTag, style: const TextStyle(color: Colors.white), ), - ) + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + onPressed: () => onDecrease(), + icon: const Icon(Icons.remove), + ), + Text(quantity.toString()), + IconButton( + onPressed: () => onIncrease(), + icon: const Icon(Icons.add), + ), + ], + ), ], ), ), diff --git a/lib/main.dart b/lib/main.dart index 30c258f2..33d75b7e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -3,6 +3,7 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:get_flutter_fire/app/modules/cart/controllers/cart_controller.dart'; import 'package:get_storage/get_storage.dart'; import 'app/routes/app_pages.dart'; @@ -23,6 +24,7 @@ void main() async { title: 'Application', initialBinding: BindingsBuilder( () { + Get.put(CartController(), permanent: true); Get.put(AuthService()); }, ), diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart index 8bf72aaa..22ab9415 100644 --- a/lib/services/auth_service.dart +++ b/lib/services/auth_service.dart @@ -1,5 +1,3 @@ -// ignore_for_file: avoid_print - import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_ui_auth/firebase_ui_auth.dart' as fbui; import 'package:firebase_ui_localizations/firebase_ui_localizations.dart'; @@ -24,7 +22,7 @@ class AuthService extends GetxService { Role get maxRole => _userRole.value; @override - onInit() { + void onInit() { super.onInit(); if (useEmulator) _auth.useAuthEmulator(emulatorHost, 9099); _firebaseUser.bindStream(_auth.authStateChanges()); @@ -52,53 +50,30 @@ class AuthService extends GetxService { ? (user!.displayName ?? user!.email) : 'Guest'; - void login() { - // 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 try { await _auth.createUserWithEmailAndPassword( 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); Get.snackbar( - e.message!.substring(i, j), - 'Please verify your email by clicking the link on the Email sent', + 'Error', + 'Please verify your email by clicking the link sent to ${e.message}', ); } } } } - void sendSingInLink(EmailAuthCredential emailAuth) { + void sendSignInLink(EmailAuthCredential emailAuth) { var acs = ActionCodeSettings( - // URL you want to redirect back to. The domain (www.example.com) for this - // URL must be whitelisted in the Firebase Console. url: '$baseUrl:5001/flutterfast-92c25/us-central1/handleEmailLinkVerification', - // // This must be true if deep linking. - // // If deeplinking. See [https://firebase.google.com/docs/dynamic-links/flutter/receive] handleCodeInApp: true, - // iOSBundleId: '$bundleID.ios', - // androidPackageName: '$bundleID.android', - // // installIfNotAvailable - // androidInstallApp: true, - // // minimumVersion - // androidMinimumVersion: '12' ); _auth .sendSignInLinkToEmail(email: emailAuth.email, actionCodeSettings: acs) @@ -109,11 +84,9 @@ class AuthService extends GetxService { void register() { registered.value = true; - // logout(); // Uncomment if we need to enforce relogin final thenTo = Get.rootDelegate.currentConfiguration!.currentPage!.parameters?['then']; - Get.rootDelegate - .offAndToNamed(thenTo ?? Screen.PROFILE.route); //Profile has the forms + Get.rootDelegate.offAndToNamed(thenTo ?? Screen.PROFILE.route); } void logout() { @@ -141,12 +114,10 @@ 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."); + if (e.code == "operation-not-allowed") { + print("Anonymous auth hasn't been enabled for this project."); + } else { + print("Unknown error."); } Get.back(result: false); } @@ -158,15 +129,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 your email by clicking the link sent."; } else { callback(false, credential.value); } @@ -194,7 +161,7 @@ class MyCredential extends AuthCredential { } } -parseEmail(String message) { +String parseEmail(String message) { int i = message.indexOf('"message":') + 13; int j = message.indexOf('"', i); return message.substring(i, j - 1); From 6ab4c7b2de454e57d158d3ac6d39d54df86dcdd4 Mon Sep 17 00:00:00 2001 From: winterpigeon Date: Sun, 1 Sep 2024 03:29:19 +0530 Subject: [PATCH 4/4] fix: image picker button --- lib/app/widgets/image_picker_button.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/app/widgets/image_picker_button.dart b/lib/app/widgets/image_picker_button.dart index d6e87ff4..72f5a509 100644 --- a/lib/app/widgets/image_picker_button.dart +++ b/lib/app/widgets/image_picker_button.dart @@ -74,8 +74,11 @@ class ImagePickerButton extends MenuSheetButton { Iterable get values => ImageSources.values; @override - void callbackFunc(act) { - if (callback != null) callback!(act); + void callbackFunc(dynamic act) { + if (callback != null) { + String? path = act as String?; + callback!(path!); + } } @override