diff --git a/.gitignore b/.gitignore index 38e6681..5de637e 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ app.*.map.json # GoogleMap api (android, ios) hidden /android/local.properties /ios/Runner/Storage.swift + +# API +lib/services/api_constants.dart \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index f43ff6a..6a1de46 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,46 +1,44 @@ import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'screen/signInUp/signIn.dart'; import 'services/product_list_api.dart'; import 'onboarding.dart'; void main() { - runApp(const MyApp()); + runApp(const ProviderScope(child: MyApp())); } -class MyApp extends StatelessWidget { +class MyApp extends ConsumerWidget { const MyApp({super.key}); @override - Widget build(BuildContext context) { - return ChangeNotifierProvider( - create: (_) => ItemListApi(), - child: MaterialApp( - theme: ThemeData( - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - elevation: 0, - shape: const StadiumBorder(), - minimumSize: const Size(327, 48), - backgroundColor: const Color(0xff54408C), - textStyle: - const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), - ), - textButtonTheme: TextButtonThemeData( - style: TextButton.styleFrom( - foregroundColor: const Color(0xff54408C), - textStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + Widget build(BuildContext context, WidgetRef ref) { + final itemListApi = ref.watch(itemListProvider); + return MaterialApp( + theme: ThemeData( + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + elevation: 0, + shape: const StadiumBorder(), + minimumSize: const Size(327, 48), + backgroundColor: const Color(0xff54408C), + textStyle: + const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + foregroundColor: const Color(0xff54408C), + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), ), ), - home: const OnBoarding(), - routes: { - '/signIn': (context) => const SignIn(), - }, ), + home: const OnBoarding(), + routes: { + '/signIn': (context) => const SignIn(), + }, ); } } diff --git a/lib/screen/home/category.dart b/lib/screen/home/category.dart index 6576afb..3a30d1d 100644 --- a/lib/screen/home/category.dart +++ b/lib/screen/home/category.dart @@ -1,20 +1,16 @@ import 'package:flutter/material.dart'; import 'package:quick_drop/screen/home/item_bottom_modal.dart'; import '../../services/product_list_api.dart'; // Import your product list API +import 'package:flutter_riverpod/flutter_riverpod.dart'; -class Category extends StatefulWidget { +class Category extends ConsumerWidget { const Category({Key? key}) : super(key: key); @override - State createState() => _CategoryState(); -} - -class _CategoryState extends State { - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Scaffold( body: FutureBuilder>( - future: ItemListApi.fetchData(), + future: ref.watch(itemListProvider.future), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center( diff --git a/lib/screen/home/item_list.dart b/lib/screen/home/item_list.dart index 6a30820..5f1ed98 100644 --- a/lib/screen/home/item_list.dart +++ b/lib/screen/home/item_list.dart @@ -1,26 +1,29 @@ import 'package:flutter/material.dart'; -import 'item_bottom_modal.dart'; -import '../../services/product_list_api.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:quick_drop/screen/home/item_bottom_modal.dart'; +import 'package:quick_drop/services/product_list_api.dart'; -class ItemList extends StatefulWidget { - const ItemList({Key? key}) : super(key: key); +final selectedCategoryProvider = StateProvider((ref) => ''); - @override - State createState() => _ItemListState(); -} +final itemListProvider = FutureProvider.autoDispose>((ref) { + final category = ref.watch(selectedCategoryProvider); + return fetchData( + category: category); // Replace with your actual fetchData function +}); -class _ItemListState extends State { - late Future> _productInfoList; - String _selectedCategory = ''; // Track the selected category +class ItemList extends ConsumerStatefulWidget { + const ItemList({Key? key}) : super(key: key); @override - void initState() { - super.initState(); - _productInfoList = ItemListApi.fetchData(); - } + _ItemListState createState() => _ItemListState(); +} +class _ItemListState extends ConsumerState { @override Widget build(BuildContext context) { + final productInfoList = ref.watch(itemListProvider); + final selectedCategory = ref.watch(selectedCategoryProvider); + return Scaffold( body: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -46,66 +49,50 @@ class _ItemListState extends State { ), ), Expanded( - child: FutureBuilder>( - future: _productInfoList, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center( - child: CircularProgressIndicator(), - ); - } else if (snapshot.hasError) { - print(snapshot.error); - return const Center( - child: Text('Something went wrong'), - ); - } else { - final List productInfoList = snapshot.data!; - // Filter the list based on selected category - final filteredList = _selectedCategory.isEmpty - ? productInfoList - : productInfoList - .where((item) => - item.category == _selectedCategory || - _selectedCategory == - 'All') // Include 'All' category - .toList(); - return ListView.builder( - itemCount: filteredList.length, - itemBuilder: (context, index) { - final productInfo = filteredList[index]; - return InkWell( - onTap: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) { - return Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(25.0), - topRight: Radius.circular(25.0), - ), + child: productInfoList.when( + data: (productInfoList) { + return ListView.builder( + itemCount: productInfoList.length, + itemBuilder: (context, index) { + final productInfo = productInfoList[index]; + return InkWell( + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) { + return Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(25.0), + topRight: Radius.circular(25.0), ), - height: - MediaQuery.of(context).size.height * 0.77, - child: - ItemBottomModal(productInfo: productInfo), - ); - }, - ); - }, - child: ListTile( - title: Text(productInfo.title), - subtitle: Text(productInfo.category), - trailing: const Icon(Icons.arrow_forward), - ), - ); - }, - ); - } + ), + height: MediaQuery.of(context).size.height * 0.77, + child: ItemBottomModal( + productInfo: + productInfo), // Replace with your actual ItemBottomModal widget + ); + }, + ); + }, + child: ListTile( + title: Text(productInfo.title), + subtitle: Text(productInfo.category), + trailing: const Icon(Icons.arrow_forward), + ), + ); + }, + ); }, + loading: () => const Center( + child: CircularProgressIndicator(), + ), + error: (_, __) => const Center( + child: Text('Something went wrong'), + ), ), ), ], @@ -114,17 +101,18 @@ class _ItemListState extends State { } Widget buildCategoryButton(String category) { + final selectedCategory = ref.watch(selectedCategoryProvider); return TextButton( onPressed: () { - setState(() { - _selectedCategory = category; // Update the selected category - }); + ref.watch(selectedCategoryProvider.notifier).state = category; + ref.watch(itemListProvider); }, child: Text( category, style: TextStyle( - fontWeight: FontWeight.normal, - color: _selectedCategory == category ? Colors.black : Colors.grey), + fontWeight: FontWeight.normal, + color: selectedCategory == category ? Colors.black : Colors.grey, + ), ), ); } diff --git a/lib/screen/home/search/search_delegate.dart b/lib/screen/home/search/search_delegate.dart index 5caf42e..054bf1e 100644 --- a/lib/screen/home/search/search_delegate.dart +++ b/lib/screen/home/search/search_delegate.dart @@ -1,6 +1,16 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quick_drop/services/product_list_api.dart'; +final searchQueryProvider = FutureProvider.autoDispose + .family, String>((ref, query) async { + if (query.isEmpty) { + return []; + } else { + return await fetchData(searchKeyword: query); + } +}); + class ProductSearchDelegate extends SearchDelegate { ProductSearchDelegate(); @@ -16,38 +26,34 @@ class ProductSearchDelegate extends SearchDelegate { @override Widget buildSuggestions(BuildContext context) { - if (query.isEmpty) { - return Container(); - } - - return FutureBuilder>( - future: ItemListApi.fetchData(searchKeyword: query), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return Text('Error: ${snapshot.error}'); - } else { - final results = snapshot.data!.where((productInfo) { - return productInfo.title - .toLowerCase() - .contains(query.toLowerCase()); - }).toList(); - return ListView.builder( - itemCount: results.length, - itemBuilder: (context, index) { - final item = results[index]; - return ListTile( - title: Text(item.title), - onTap: () { - close(context, item); - }, - ); - }, - ); - } - }, - ); + return query.isEmpty + ? Container() + : Consumer(builder: (context, ref, child) { + final searchResultsAsync = ref.watch(searchQueryProvider(query)); + return searchResultsAsync.when( + data: (results) { + final filteredResults = results.where((productInfo) { + return productInfo.title + .toLowerCase() + .contains(query.toLowerCase()); + }).toList(); + return ListView.builder( + itemCount: filteredResults.length, + itemBuilder: (context, index) { + final item = filteredResults[index]; + return ListTile( + title: Text(item.title), + onTap: () { + close(context, item); + }, + ); + }, + ); + }, + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, __) => Text('Error: $error'), + ); + }); } @override diff --git a/lib/services/product_list_api.dart b/lib/services/product_list_api.dart index 465d725..b6eac04 100644 --- a/lib/services/product_list_api.dart +++ b/lib/services/product_list_api.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quick_drop/services/api_constants.dart'; class ProductInfo { @@ -38,35 +39,38 @@ class ProductInfo { } } -class ItemListApi with ChangeNotifier { - static Future> fetchData( - {String? category, String? searchKeyword}) async { - final Uri uri = Uri.parse('${ApiConstants.BASE_URL}/product'); - final Map headers = { - 'Content-Type': 'application/json; charset=UTF-8', - }; - final Map queryParams = {}; +final itemListProvider = + FutureProvider.autoDispose>((ref) async { + return await fetchData(); +}); - if (category != null) { - queryParams['category'] = category; - } +Future> fetchData( + {String? category, String? searchKeyword}) async { + final Uri uri = Uri.parse('${ApiConstants.BASE_URL}/product'); + final Map headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }; + final Map queryParams = {}; - if (searchKeyword != null) { - queryParams['search'] = searchKeyword; - } + if (category != null) { + queryParams['category'] = category; + } - final Uri modifiedUri = uri.replace(queryParameters: queryParams); - final response = await http.get( - modifiedUri, - headers: headers, - ); + if (searchKeyword != null) { + queryParams['search'] = searchKeyword; + } - if (response.statusCode == 200) { - final List jsonData = jsonDecode(response.body); - return jsonData.map((item) => ProductInfo.fromJson(item)).toList(); - } else { - print(response.statusCode); - throw Exception('Failed to load Item List'); - } + final Uri modifiedUri = uri.replace(queryParameters: queryParams); + final response = await http.get( + modifiedUri, + headers: headers, + ); + + if (response.statusCode == 200) { + final List jsonData = jsonDecode(response.body); + return jsonData.map((item) => ProductInfo.fromJson(item)).toList(); + } else { + print(response.statusCode); + throw Exception('Failed to load Item List'); } } diff --git a/pubspec.lock b/pubspec.lock index f3d6d3d..ab9ce35 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -182,6 +182,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.17" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "4bce556b7ecbfea26109638d5237684538d4abc509d253e6c5c4c5733b360098" + url: "https://pub.dev" + source: hosted + version: "2.4.10" flutter_secure_storage: dependency: "direct main" description: @@ -488,14 +496,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - nested: - dependency: transitive - description: - name: nested - sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" - source: hosted - version: "1.0.0" path: dependency: transitive description: @@ -592,14 +592,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.7.4" - provider: - dependency: "direct main" + riverpod: + dependency: transitive description: - name: provider - sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + name: riverpod + sha256: "548e2192eb7aeb826eb89387f814edb76594f3363e2c0bb99dd733d795ba3589" url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "2.5.0" sanitize_html: dependency: transitive description: @@ -629,6 +629,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.0" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" stream_channel: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b5e0513..b767bf0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,12 +37,12 @@ dependencies: flutter_svg: ^2.0.9 cupertino_icons: ^1.0.2 http: ^1.1.0 - provider: ^6.1.1 image_picker: ^1.0.7 intl: ^0.19.0 flutter_secure_storage: ^9.0.0 google_maps_flutter: ^2.5.3 geocoding: ^2.1.1 + flutter_riverpod: ^2.4.10 dev_dependencies: flutter_test: