From 3135116f26f46e2d86d1734019aa18b82cd797d2 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 14 Nov 2025 14:36:06 +0530 Subject: [PATCH] Branch and ATM Locator added --- lib/api/services/branch_service.dart | 137 +++++++++++++++ lib/di/injection.dart | 6 +- .../service/screens/atm_locator_screen.dart | 146 +++++++--------- .../screens/branch_details_screen.dart | 68 ++++++++ .../screens/branch_locator_screen.dart | 165 ++++++++---------- 5 files changed, 347 insertions(+), 175 deletions(-) create mode 100644 lib/api/services/branch_service.dart create mode 100644 lib/features/service/screens/branch_details_screen.dart diff --git a/lib/api/services/branch_service.dart b/lib/api/services/branch_service.dart new file mode 100644 index 0000000..5aa5b68 --- /dev/null +++ b/lib/api/services/branch_service.dart @@ -0,0 +1,137 @@ +import 'package:dio/dio.dart'; + +class Branch{ +final String branch_code; +final String branch_name; +final String zone; +final String tehsil; +final String block; +final String block_code; +final String distt_name; +final String distt_code_slbc; +final String date_of_opening; +final String rbi_code_1; +final String rbi_code_2; +final String telephone_no; +final String type_of_branch; +final String rtgs_acct_no; +final String br_lattitude; +final String br_longitude; +final String pincode; +final String post_office; + +Branch({ +required this.branch_code, +required this.branch_name, +required this.zone, +required this.tehsil, +required this.block, +required this.block_code, +required this.distt_name, +required this.distt_code_slbc, +required this.date_of_opening, +required this.rbi_code_1, +required this.rbi_code_2, +required this.telephone_no, +required this.type_of_branch, +required this.rtgs_acct_no, +required this.br_lattitude, +required this.br_longitude, +required this.pincode, +required this.post_office, +}); + +factory Branch.fromJson(Map json) { + return Branch( + branch_code: json['branch_code'] ?? json['branch_code'] ?? '', + branch_name: json['branch_name'] ?? json['branch_name'] ?? '', + zone: json['zone'] ?? json['zone'] ?? '', + tehsil: json['tehsil'] ?? json['tehsil'] ?? '', + block: json['block'] ?? json['block'] ?? '', + block_code: json['block_code'] ?? json['block_code'] ?? '', + distt_name: json['distt_name'] ?? json['distt_name'] ?? '', + distt_code_slbc: json['distt_code_slbc'] ?? json['distt_code_slbc'] ?? '', + date_of_opening: json['date_of_opening'] ?? json['date_of_opening'] ?? '', + rbi_code_1: json['rbi_code_1'] ?? json['rbi_code_1'] ?? '', + rbi_code_2: json['rbi_code_2'] ?? json['rbi_code_2'] ?? '', + telephone_no: json['telephone_no'] ?? json['telephone_no'] ?? '', + type_of_branch: json['type_of_branch'] ?? json['type_of_branch'] ?? '', + rtgs_acct_no: json['rtgs_acct_no'] ?? json['rtgs_acct_no'] ?? '', + br_lattitude: json['br_lattitude'] ?? json['br_lattitude'] ?? '', + br_longitude: json['br_longitude'] ?? json['br_longitude'] ?? '', + pincode: json['pincode'] ?? json['pincode'] ?? '', + post_office: json['post_office'] ?? json['post_office'] ?? '', + ); + } + +static List listFromJson(List jsonList) { + final beneficiaryList = jsonList + .map((beneficiary) => Branch.fromJson(beneficiary)) + .toList(); + return beneficiaryList; + } +} + + class Atm { + final String name; + + Atm({required this.name}); + + factory Atm.fromJson(Map json) { + return Atm( + name: json['name'] ?? '', // Assuming the API returns a 'name' field + ); + } + + static List listFromJson(List jsonList) { + return jsonList.map((atm) => Atm.fromJson(atm)).toList(); + } + } + +class BranchService { + final Dio _dio; + BranchService(this._dio); + + Future> fetchBranchList() async { + try { + final response = await _dio.get( + "/api/branch", + options: Options( + headers: { + "Content-Type": "application/json", + }, + ), + ); + + if (response.statusCode == 200) { + return Branch.listFromJson(response.data); + } else { + throw Exception("Failed to fetch beneficiaries"); + } + } catch (e) { + return []; + } + } + + Future> fetchAtmList() async { + try { + final response = await _dio.get( + "/api/atm", + options: Options( + headers: { + "Content-Type": "application/json", + }, + ), + ); + if (response.statusCode == 200) { + return Atm.listFromJson(response.data); + } else { + throw Exception("Failed to fetch ATM list: ${response.statusCode}"); + } + } catch (e) { + // You might want to log the error here for debugging + print("Error fetching ATM list: $e"); + return []; + } + } +} \ No newline at end of file diff --git a/lib/di/injection.dart b/lib/di/injection.dart index 63dffe9..2e705c3 100644 --- a/lib/di/injection.dart +++ b/lib/di/injection.dart @@ -1,3 +1,4 @@ +import 'package:kmobile/api/services/branch_service.dart'; import 'package:kmobile/api/services/limit_service.dart'; import 'package:kmobile/api/services/rtgs_service.dart'; import 'package:kmobile/api/services/neft_service.dart'; @@ -51,6 +52,7 @@ Future setupDependencies() async { getIt.registerSingleton(NeftService(getIt())); getIt.registerSingleton(RtgsService(getIt())); getIt.registerSingleton(ImpsService(getIt())); + getIt.registerSingleton(BranchService(getIt())); getIt.registerLazySingleton( () => ChangePasswordService(getIt()), ); @@ -69,9 +71,9 @@ Dio _createDioClient() { final dio = Dio( BaseOptions( baseUrl: - 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test + // 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080', //test //'http://lb-kccb-mobile-banking-app-848675342.ap-south-1.elb.amazonaws.com', //prod - //'https://kccbmbnk.net', //prod small + 'https://kccbmbnk.net', //prod small connectTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60), headers: { diff --git a/lib/features/service/screens/atm_locator_screen.dart b/lib/features/service/screens/atm_locator_screen.dart index cfc66c2..ba1ef74 100644 --- a/lib/features/service/screens/atm_locator_screen.dart +++ b/lib/features/service/screens/atm_locator_screen.dart @@ -1,19 +1,10 @@ -// ignore_for_file: unused_element - import 'package:flutter/material.dart'; import '../../../l10n/app_localizations.dart'; +import 'package:kmobile/api/services/branch_service.dart'; // Added: Import BranchService for Atm model and API calls +import 'package:kmobile/di/injection.dart'; // Added: Import for dependency injection (getIt) +import 'package:shimmer/shimmer.dart'; // Added: Import for shimmer loading effect -// Enum to define the type of location - -class Location { - final String name; - final String address; - - Location({ - required this.name, - required this.address, - }); -} +// Removed: The local 'Location' class is no longer needed as we use the 'Atm' model from branch_service.dart class ATMLocatorScreen extends StatefulWidget { const ATMLocatorScreen({super.key}); @@ -24,58 +15,36 @@ class ATMLocatorScreen extends StatefulWidget { class _ATMLocatorScreenState extends State { final TextEditingController _searchController = TextEditingController(); - - final List _allLocations = [ - Location( - name: "Dharamsala ATM", - address: "Near Main Square, Dharamsala", - ), - Location( - name: "Kangra ATM", - address: "Opposite Bus Stand, Kangra", - ), - ]; - - List _filteredLocations = []; - bool _isLoading = false; + var service = getIt(); // Added: Instance of BranchService for API calls + bool _isLoading = true; // State variable to manage loading status + List _allAtms = []; // Changed: List to hold all fetched Atm objects + List _filteredAtms = []; // Changed: List to hold filtered Atm objects for display @override void initState() { super.initState(); - // _fetchAndSetLocations(); - _filteredLocations = _allLocations; + _loadAtms(); // Changed: Call _loadAtms to fetch data on initialization } -// Example of a future API fetching function -/* -Future _fetchAndSetLocations() async { - setState(() { - _isLoading = true; - }); - try { - // final locations = await yourApiService.getLocations(); - // setState(() { - // _allLocations = locations; - // _filteredLocations = locations; - // }); - } catch (e) { - // Handle error - } finally { + /// Fetches the list of ATMs from the API using BranchService. + Future _loadAtms() async { + final data = await service.fetchAtmList(); // Call the new fetchAtmList method setState(() { - _isLoading = false; + _allAtms = data; // Update the list of all ATMs + _filteredAtms = data; // Initialize filtered list with all ATMs + _isLoading = false; // Set loading to false once data is fetched }); } -} -*/ - void _filterLocations(String query) { + + /// Filters the list of ATMs based on the search query. + void _filterAtms(String query) { // Changed: Renamed from _filterLocations setState(() { if (query.isEmpty) { - _filteredLocations = _allLocations; + _filteredAtms = _allAtms; // If query is empty, show all ATMs } else { - _filteredLocations = _allLocations.where((location) { + _filteredAtms = _allAtms.where((atm) { // Changed: Filter based on Atm object final lowerQuery = query.toLowerCase(); - return location.name.toLowerCase().contains(lowerQuery) || - location.address.toLowerCase().contains(lowerQuery); + return atm.name.toLowerCase().contains(lowerQuery); // Filter by atm.name }).toList(); } }); @@ -85,7 +54,7 @@ Future _fetchAndSetLocations() async { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text("ATM Locator"), + title: const Text("ATM Locator"), // Title for the app bar ), body: Stack( children: [ @@ -95,10 +64,10 @@ Future _fetchAndSetLocations() async { padding: const EdgeInsets.all(12.0), child: TextField( controller: _searchController, - onChanged: _filterLocations, + onChanged: _filterAtms, // Updated: Call _filterAtms on text change decoration: InputDecoration( - hintText: "Name/Address", - prefixIcon: const Icon(Icons.search), + hintText: "Name/Address", // Hint text for the search bar + prefixIcon: const Icon(Icons.search), // Search icon border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), ), @@ -106,18 +75,18 @@ Future _fetchAndSetLocations() async { ), ), - // Content area + // Content area: Displays loading shimmer, "no ATMs found", or the list of ATMs Expanded( child: _isLoading - ? const Center(child: CircularProgressIndicator()) - : _filteredLocations.isEmpty + ? _buildShimmerList() // Display shimmer while loading + : _filteredAtms.isEmpty ? const Center( - child: Text("No matching locations found")) + child: Text("No matching ATMs found")) // Message if no ATMs found : ListView.builder( - itemCount: _filteredLocations.length, + itemCount: _filteredAtms.length, // Number of items in the filtered list itemBuilder: (context, index) { - final location = _filteredLocations[index]; - return _buildLocationItem(location); + final atm = _filteredAtms[index]; // Get the current Atm object + return _buildAtmItem(atm); // Build the ATM list item }, ), ), @@ -126,9 +95,9 @@ Future _fetchAndSetLocations() async { IgnorePointer( child: Center( child: Opacity( - opacity: 0.1, // Low opacity + opacity: 0.1, // Low opacity for background logo child: Image.asset( - 'assets/images/logo.png', + 'assets/images/logo.png', // Background logo image width: 200, // Adjust size as needed height: 200, // Adjust size as needed ), @@ -140,36 +109,43 @@ Future _fetchAndSetLocations() async { ); } - Widget _buildHeader(String title) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - child: Text( - title, - style: Theme.of(context).textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ), - ); - } -// Helper widget to build a single location item - Widget _buildLocationItem(Location location) { + /// Helper widget to build a single ATM list item. + Widget _buildAtmItem(Atm atm) { // Changed: Takes an Atm object return Card( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: ListTile( - leading: const Icon(Icons.currency_rupee), - title: Text(location.name, + leading: const Icon(Icons.currency_rupee), // Icon for ATM + title: Text(atm.name, // Display the ATM's name style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text( - "Address: ${location.address}", - ), onTap: () { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Selected ${location.name}")), + SnackBar(content: Text("Selected ${atm.name}")), // Show snackbar on tap ); }, ), ); } + + /// Helper widget to display a shimmer loading effect. + Widget _buildShimmerList() { + return ListView.builder( + itemCount: 10, // Number of shimmer items to display + itemBuilder: (context, index) => Shimmer.fromColors( + baseColor: Colors.grey.shade300, + highlightColor: Colors.grey.shade100, + child: ListTile( + leading: const CircleAvatar( + radius: 24, + backgroundColor: Colors.white, + ), + title: Container( + height: 16, + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 4), + ), + ), + ), + ); + } } diff --git a/lib/features/service/screens/branch_details_screen.dart b/lib/features/service/screens/branch_details_screen.dart new file mode 100644 index 0000000..737b5c5 --- /dev/null +++ b/lib/features/service/screens/branch_details_screen.dart @@ -0,0 +1,68 @@ + import 'package:flutter/material.dart'; + import 'package:kmobile/api/services/branch_service.dart'; + + class BranchDetailsScreen extends StatelessWidget { + final Branch branch; + + const BranchDetailsScreen({super.key, required this.branch}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(branch.branch_name), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildDetailRow("Branch Name", branch.branch_name), + _buildDetailRow("Branch Code", branch.branch_code), + _buildDetailRow("Zone", branch.zone), + _buildDetailRow("Tehsil", branch.tehsil), + _buildDetailRow("Block", branch.block), + _buildDetailRow("District", branch.distt_name), + _buildDetailRow("Pincode", branch.pincode), + _buildDetailRow("Post Office", branch.post_office), + _buildDetailRow("Date of Opening", branch.date_of_opening), + _buildDetailRow("Branch Type", branch.type_of_branch), + _buildDetailRow("Telephone No.", branch.telephone_no), + _buildDetailRow("RTGS Account No.", branch.rtgs_acct_no), + _buildDetailRow("RBI Code 1", branch.rbi_code_1), + _buildDetailRow("RBI Code 2", branch.rbi_code_2), + _buildDetailRow("Latitude", branch.br_lattitude), + _buildDetailRow("Longitude", branch.br_longitude), + ], + ), + ), + ); + } + + Widget _buildDetailRow(String label, String value) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + const SizedBox(height: 4), + Text( + value, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + const Divider(height: 16), + ], + ), + ); + } + } diff --git a/lib/features/service/screens/branch_locator_screen.dart b/lib/features/service/screens/branch_locator_screen.dart index 580316a..cf3daf9 100644 --- a/lib/features/service/screens/branch_locator_screen.dart +++ b/lib/features/service/screens/branch_locator_screen.dart @@ -2,20 +2,11 @@ import 'package:flutter/material.dart'; import '../../../l10n/app_localizations.dart'; +import 'package:kmobile/api/services/branch_service.dart'; +import 'package:kmobile/di/injection.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:kmobile/features/service/screens/branch_details_screen.dart'; -class Location { - final String name; - final String? code; // Nullable for ATMs - final String? ifsc; // Nullable for ATMs - final String address; - - Location({ - required this.name, - this.code, - this.ifsc, - required this.address, - }); -} class BranchLocatorScreen extends StatefulWidget { const BranchLocatorScreen({super.key}); @@ -24,76 +15,48 @@ class BranchLocatorScreen extends StatefulWidget { State createState() => _BranchLocatorScreenState(); } -class _BranchLocatorScreenState extends State { - final TextEditingController _searchController = TextEditingController(); + class _BranchLocatorScreenState extends State { + final TextEditingController _searchController = TextEditingController(); + var service = getIt(); + bool _isLoading = true; + List _allBranches = []; + List _filteredBranches = []; - final List _allLocations = [ - Location( - name: "Dharamsala - Head Office", - code: "002", - ifsc: "KACE0000002", - address: "Civil Lines Dharmashala, Kangra, HP - 176215", - ), - Location( - name: "Kangra", - code: "033", - ifsc: "KACE0000033", - address: "Rajput Bhawankangrapo, Kangra, HP ", - ), - ]; - - List _filteredLocations = []; - bool _isLoading = false; - - @override + @override void initState() { super.initState(); // _fetchAndSetLocations(); - _filteredLocations = _allLocations; + _loadBranches(); } -// Example of a future API fetching function -/* -Future _fetchAndSetLocations() async { - setState(() { - _isLoading = true; - }); - try { - // final locations = await yourApiService.getLocations(); - // setState(() { - // _allLocations = locations; - // _filteredLocations = locations; - // }); - } catch (e) { - // Handle error - } finally { - setState(() { - _isLoading = false; - }); - } -} -*/ - void _filterLocations(String query) { - setState(() { - if (query.isEmpty) { - _filteredLocations = _allLocations; - } else { - _filteredLocations = _allLocations.where((location) { - final lowerQuery = query.toLowerCase(); - return location.name.toLowerCase().contains(lowerQuery) || - (location.code?.toLowerCase().contains(lowerQuery) ?? false) || - (location.ifsc?.toLowerCase().contains(lowerQuery) ?? false) || - location.address.toLowerCase().contains(lowerQuery); - }).toList(); - } - }); - } + Future _loadBranches() async { + final data = await service.fetchBranchList(); + setState(() { + _allBranches = data; + _filteredBranches = data; + _isLoading = false; + }); + } - @override + void _filterBranches(String query) { + setState(() { + if (query.isEmpty) { + _filteredBranches = _allBranches; + } else { + _filteredBranches = _allBranches.where((branch) { + final lowerQuery = query.toLowerCase(); + return branch.branch_name.toLowerCase().contains(lowerQuery); + }).toList(); + } + }); + } + + + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text(AppLocalizations.of(context).branchLocator), + title: const Text("Branch Locator"), ), body: Stack( children: [ @@ -103,9 +66,9 @@ Future _fetchAndSetLocations() async { padding: const EdgeInsets.all(12.0), child: TextField( controller: _searchController, - onChanged: _filterLocations, + onChanged: _filterBranches, // Updated decoration: InputDecoration( - hintText: AppLocalizations.of(context).searchbranchby, + hintText: "Branch Name", prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), @@ -117,15 +80,15 @@ Future _fetchAndSetLocations() async { // Content area Expanded( child: _isLoading - ? const Center(child: CircularProgressIndicator()) - : _filteredLocations.isEmpty + ? _buildShimmerList() // Changed to shimmer + : _filteredBranches.isEmpty ? const Center( - child: Text("No matching locations found")) + child: Text("No matching branches found")) // Updated tex : ListView.builder( - itemCount: _filteredLocations.length, + itemCount: _filteredBranches.length, itemBuilder: (context, index) { - final location = _filteredLocations[index]; - return _buildLocationItem(location); + final branch = _filteredBranches[index]; // Changed to + return _buildBranchItem(branch); // Updated }, ), ), @@ -161,24 +124,50 @@ Future _fetchAndSetLocations() async { ); } -// Helper widget to build a single location item - Widget _buildLocationItem(Location location) { + // Helper widget to build a single branch item + + Widget _buildBranchItem(Branch branch) { return Card( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: ListTile( leading: const CircleAvatar( child: Icon(Icons.location_city), ), - title: Text(location.name, + title: Text(branch.branch_name, style: const TextStyle(fontWeight: FontWeight.bold)), - subtitle: Text( - "Code: ${location.code} | IFSC: ${location.ifsc}\nAddress: ${location.address}"), onTap: () { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("Selected ${location.name}")), + // This is the updated part + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => BranchDetailsScreen(branch: branch), + ), ); }, ), ); } + + + // Shimmer loading list + Widget _buildShimmerList() { + return ListView.builder( + itemCount: 10, // Number of shimmer items + itemBuilder: (context, index) => Shimmer.fromColors( + baseColor: Colors.grey.shade300, + highlightColor: Colors.grey.shade100, + child: ListTile( + leading: const CircleAvatar( + radius: 24, + backgroundColor: Colors.white, + ), + title: Container( + height: 16, + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 4), + ), + ), + ), + ); + } }