Branch and ATM Locator added

This commit is contained in:
2025-11-14 14:36:06 +05:30
parent 39165d631e
commit 3135116f26
5 changed files with 347 additions and 175 deletions

View File

@@ -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<String, dynamic> 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<Branch> listFromJson(List<dynamic> 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<String, dynamic> json) {
return Atm(
name: json['name'] ?? '', // Assuming the API returns a 'name' field
);
}
static List<Atm> listFromJson(List<dynamic> jsonList) {
return jsonList.map((atm) => Atm.fromJson(atm)).toList();
}
}
class BranchService {
final Dio _dio;
BranchService(this._dio);
Future<List<Branch>> 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<List<Atm>> 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 [];
}
}
}

View File

@@ -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/limit_service.dart';
import 'package:kmobile/api/services/rtgs_service.dart'; import 'package:kmobile/api/services/rtgs_service.dart';
import 'package:kmobile/api/services/neft_service.dart'; import 'package:kmobile/api/services/neft_service.dart';
@@ -51,6 +52,7 @@ Future<void> setupDependencies() async {
getIt.registerSingleton<NeftService>(NeftService(getIt<Dio>())); getIt.registerSingleton<NeftService>(NeftService(getIt<Dio>()));
getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>())); getIt.registerSingleton<RtgsService>(RtgsService(getIt<Dio>()));
getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>())); getIt.registerSingleton<ImpsService>(ImpsService(getIt<Dio>()));
getIt.registerSingleton<BranchService>(BranchService(getIt<Dio>()));
getIt.registerLazySingleton<ChangePasswordService>( getIt.registerLazySingleton<ChangePasswordService>(
() => ChangePasswordService(getIt<Dio>()), () => ChangePasswordService(getIt<Dio>()),
); );
@@ -69,9 +71,9 @@ Dio _createDioClient() {
final dio = Dio( final dio = Dio(
BaseOptions( BaseOptions(
baseUrl: 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 //'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), connectTimeout: const Duration(seconds: 60),
receiveTimeout: const Duration(seconds: 60), receiveTimeout: const Duration(seconds: 60),
headers: { headers: {

View File

@@ -1,19 +1,10 @@
// ignore_for_file: unused_element
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.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 // Removed: The local 'Location' class is no longer needed as we use the 'Atm' model from branch_service.dart
class Location {
final String name;
final String address;
Location({
required this.name,
required this.address,
});
}
class ATMLocatorScreen extends StatefulWidget { class ATMLocatorScreen extends StatefulWidget {
const ATMLocatorScreen({super.key}); const ATMLocatorScreen({super.key});
@@ -24,58 +15,36 @@ class ATMLocatorScreen extends StatefulWidget {
class _ATMLocatorScreenState extends State<ATMLocatorScreen> { class _ATMLocatorScreenState extends State<ATMLocatorScreen> {
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
var service = getIt<BranchService>(); // Added: Instance of BranchService for API calls
final List<Location> _allLocations = [ bool _isLoading = true; // State variable to manage loading status
Location( List<Atm> _allAtms = []; // Changed: List to hold all fetched Atm objects
name: "Dharamsala ATM", List<Atm> _filteredAtms = []; // Changed: List to hold filtered Atm objects for display
address: "Near Main Square, Dharamsala",
),
Location(
name: "Kangra ATM",
address: "Opposite Bus Stand, Kangra",
),
];
List<Location> _filteredLocations = [];
bool _isLoading = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
// _fetchAndSetLocations(); _loadAtms(); // Changed: Call _loadAtms to fetch data on initialization
_filteredLocations = _allLocations;
} }
// Example of a future API fetching function /// Fetches the list of ATMs from the API using BranchService.
/* Future<void> _loadAtms() async {
Future<void> _fetchAndSetLocations() async { final data = await service.fetchAtmList(); // Call the new fetchAtmList method
setState(() {
_isLoading = true;
});
try {
// final locations = await yourApiService.getLocations();
// setState(() {
// _allLocations = locations;
// _filteredLocations = locations;
// });
} catch (e) {
// Handle error
} finally {
setState(() { 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
}); });
} }
}
*/ /// Filters the list of ATMs based on the search query.
void _filterLocations(String query) { void _filterAtms(String query) { // Changed: Renamed from _filterLocations
setState(() { setState(() {
if (query.isEmpty) { if (query.isEmpty) {
_filteredLocations = _allLocations; _filteredAtms = _allAtms; // If query is empty, show all ATMs
} else { } else {
_filteredLocations = _allLocations.where((location) { _filteredAtms = _allAtms.where((atm) { // Changed: Filter based on Atm object
final lowerQuery = query.toLowerCase(); final lowerQuery = query.toLowerCase();
return location.name.toLowerCase().contains(lowerQuery) || return atm.name.toLowerCase().contains(lowerQuery); // Filter by atm.name
location.address.toLowerCase().contains(lowerQuery);
}).toList(); }).toList();
} }
}); });
@@ -85,7 +54,7 @@ Future<void> _fetchAndSetLocations() async {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("ATM Locator"), title: const Text("ATM Locator"), // Title for the app bar
), ),
body: Stack( body: Stack(
children: [ children: [
@@ -95,10 +64,10 @@ Future<void> _fetchAndSetLocations() async {
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
onChanged: _filterLocations, onChanged: _filterAtms, // Updated: Call _filterAtms on text change
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Name/Address", hintText: "Name/Address", // Hint text for the search bar
prefixIcon: const Icon(Icons.search), prefixIcon: const Icon(Icons.search), // Search icon
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@@ -106,18 +75,18 @@ Future<void> _fetchAndSetLocations() async {
), ),
), ),
// Content area // Content area: Displays loading shimmer, "no ATMs found", or the list of ATMs
Expanded( Expanded(
child: _isLoading child: _isLoading
? const Center(child: CircularProgressIndicator()) ? _buildShimmerList() // Display shimmer while loading
: _filteredLocations.isEmpty : _filteredAtms.isEmpty
? const Center( ? const Center(
child: Text("No matching locations found")) child: Text("No matching ATMs found")) // Message if no ATMs found
: ListView.builder( : ListView.builder(
itemCount: _filteredLocations.length, itemCount: _filteredAtms.length, // Number of items in the filtered list
itemBuilder: (context, index) { itemBuilder: (context, index) {
final location = _filteredLocations[index]; final atm = _filteredAtms[index]; // Get the current Atm object
return _buildLocationItem(location); return _buildAtmItem(atm); // Build the ATM list item
}, },
), ),
), ),
@@ -126,9 +95,9 @@ Future<void> _fetchAndSetLocations() async {
IgnorePointer( IgnorePointer(
child: Center( child: Center(
child: Opacity( child: Opacity(
opacity: 0.1, // Low opacity opacity: 0.1, // Low opacity for background logo
child: Image.asset( child: Image.asset(
'assets/images/logo.png', 'assets/images/logo.png', // Background logo image
width: 200, // Adjust size as needed width: 200, // Adjust size as needed
height: 200, // Adjust size as needed height: 200, // Adjust size as needed
), ),
@@ -140,36 +109,43 @@ Future<void> _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 /// Helper widget to build a single ATM list item.
Widget _buildLocationItem(Location location) { Widget _buildAtmItem(Atm atm) { // Changed: Takes an Atm object
return Card( return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile( child: ListTile(
leading: const Icon(Icons.currency_rupee), leading: const Icon(Icons.currency_rupee), // Icon for ATM
title: Text(location.name, title: Text(atm.name, // Display the ATM's name
style: const TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(
"Address: ${location.address}",
),
onTap: () { onTap: () {
ScaffoldMessenger.of(context).showSnackBar( 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),
),
),
),
);
}
} }

View File

@@ -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),
],
),
);
}
}

View File

@@ -2,20 +2,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../l10n/app_localizations.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 { class BranchLocatorScreen extends StatefulWidget {
const BranchLocatorScreen({super.key}); const BranchLocatorScreen({super.key});
@@ -24,76 +15,48 @@ class BranchLocatorScreen extends StatefulWidget {
State<BranchLocatorScreen> createState() => _BranchLocatorScreenState(); State<BranchLocatorScreen> createState() => _BranchLocatorScreenState();
} }
class _BranchLocatorScreenState extends State<BranchLocatorScreen> { class _BranchLocatorScreenState extends State<BranchLocatorScreen> {
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
var service = getIt<BranchService>();
bool _isLoading = true;
List<Branch> _allBranches = [];
List<Branch> _filteredBranches = [];
final List<Location> _allLocations = [ @override
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<Location> _filteredLocations = [];
bool _isLoading = false;
@override
void initState() { void initState() {
super.initState(); super.initState();
// _fetchAndSetLocations(); // _fetchAndSetLocations();
_filteredLocations = _allLocations; _loadBranches();
} }
// Example of a future API fetching function Future<void> _loadBranches() async {
/* final data = await service.fetchBranchList();
Future<void> _fetchAndSetLocations() async { setState(() {
setState(() { _allBranches = data;
_isLoading = true; _filteredBranches = data;
}); _isLoading = false;
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();
}
});
}
@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) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context).branchLocator), title: const Text("Branch Locator"),
), ),
body: Stack( body: Stack(
children: [ children: [
@@ -103,9 +66,9 @@ Future<void> _fetchAndSetLocations() async {
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: TextField( child: TextField(
controller: _searchController, controller: _searchController,
onChanged: _filterLocations, onChanged: _filterBranches, // Updated
decoration: InputDecoration( decoration: InputDecoration(
hintText: AppLocalizations.of(context).searchbranchby, hintText: "Branch Name",
prefixIcon: const Icon(Icons.search), prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@@ -117,15 +80,15 @@ Future<void> _fetchAndSetLocations() async {
// Content area // Content area
Expanded( Expanded(
child: _isLoading child: _isLoading
? const Center(child: CircularProgressIndicator()) ? _buildShimmerList() // Changed to shimmer
: _filteredLocations.isEmpty : _filteredBranches.isEmpty
? const Center( ? const Center(
child: Text("No matching locations found")) child: Text("No matching branches found")) // Updated tex
: ListView.builder( : ListView.builder(
itemCount: _filteredLocations.length, itemCount: _filteredBranches.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final location = _filteredLocations[index]; final branch = _filteredBranches[index]; // Changed to
return _buildLocationItem(location); return _buildBranchItem(branch); // Updated
}, },
), ),
), ),
@@ -161,24 +124,50 @@ Future<void> _fetchAndSetLocations() async {
); );
} }
// Helper widget to build a single location item // Helper widget to build a single branch item
Widget _buildLocationItem(Location location) {
Widget _buildBranchItem(Branch branch) {
return Card( return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
child: ListTile( child: ListTile(
leading: const CircleAvatar( leading: const CircleAvatar(
child: Icon(Icons.location_city), child: Icon(Icons.location_city),
), ),
title: Text(location.name, title: Text(branch.branch_name,
style: const TextStyle(fontWeight: FontWeight.bold)), style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(
"Code: ${location.code} | IFSC: ${location.ifsc}\nAddress: ${location.address}"),
onTap: () { onTap: () {
ScaffoldMessenger.of(context).showSnackBar( // This is the updated part
SnackBar(content: Text("Selected ${location.name}")), 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),
),
),
),
);
}
} }