IFSC Code Validation and Add Beneficiary Validation API integration

This commit is contained in:
2025-08-07 18:12:31 +05:30
parent c4d4261afc
commit 99e23bf21d
17 changed files with 416 additions and 271 deletions

View File

@@ -250,9 +250,9 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
leading: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: const CircleAvatar(
child: CircleAvatar(
radius: 12,
backgroundColor: Colors.white,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
),
),
title: Shimmer.fromColors(
@@ -261,7 +261,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
child: Container(
height: 10,
width: 100,
color: Colors.white,
color: Theme.of(context).scaffoldBackgroundColor,
),
),
subtitle: Shimmer.fromColors(
@@ -270,7 +270,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
child: Container(
height: 8,
width: 60,
color: Colors.white,
color: Theme.of(context).scaffoldBackgroundColor,
),
),
),

View File

@@ -1,50 +1,60 @@
import 'dart:developer';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'theme_state.dart';
import 'package:kmobile/config/theme_type.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:kmobile/config/themes.dart';
// import 'package:kmobile/config/themes.dart';
class ThemeCubit extends Cubit<ThemeState> {
ThemeCubit()
: super(ThemeState(
lightTheme: AppThemes.getLightTheme(ThemeType.violet),
themeMode: ThemeMode.light,
themeType: ThemeType.violet,
)) {
ThemeCubit(): super(ThemeViolet()) {
loadTheme();
}
Future<void> loadTheme() async {
final prefs = await SharedPreferences.getInstance();
final themeIndex = prefs.getInt('theme_type') ?? 0;
final isDark = prefs.getBool('is_dark_mode') ?? false;
// final isDark = prefs.getBool('is_dark_mode') ?? false;
final type = ThemeType.values[themeIndex];
emit(state.copyWith(
lightTheme: AppThemes.getLightTheme(type),
themeMode: isDark ? ThemeMode.dark : ThemeMode.light,
themeType: type,
));
switch(type) {
case ThemeType.blue:
emit(ThemeBlue());
case ThemeType.violet:
emit(ThemeViolet());
default:
emit(ThemeViolet());
}
}
Future<void> changeTheme(ThemeType type) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('theme_type', type.index);
emit(state.copyWith(
lightTheme: AppThemes.getLightTheme(type),
themeType: type,
));
log("Mode Change");
print("mode changed");
switch(type) {
case ThemeType.blue:
emit(ThemeBlue());
print('blue matched');
break;
case ThemeType.violet:
emit(ThemeViolet());
print('violet matched');
break;
default:
emit(ThemeBlue());
print('default macthed');
}
}
Future<void> toggleDarkMode(bool isDark) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('is_dark_mode', isDark);
// Future<void> toggleDarkMode(bool isDark) async {
// final prefs = await SharedPreferences.getInstance();
// await prefs.setBool('is_dark_mode', isDark);
emit(state.copyWith(
themeMode: isDark ? ThemeMode.dark : ThemeMode.light,
));
}
// emit(state.copyWith(
// themeMode: isDark ? ThemeMode.dark : ThemeMode.light,
// ));
// }
}

View File

@@ -1,27 +1,52 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:kmobile/config/theme_type.dart';
import 'package:kmobile/config/themes.dart';
class ThemeState {
final ThemeData lightTheme;
final ThemeMode themeMode;
final ThemeType themeType;
// class ThemeState {
// final ThemeData lightTheme;
// final ThemeMode themeMode;
// final ThemeType themeType;
ThemeState({
required this.lightTheme,
required this.themeMode,
required this.themeType,
});
// ThemeState({
// required this.lightTheme,
// required this.themeMode,
// required this.themeType,
// });
ThemeState copyWith({
ThemeData? lightTheme,
ThemeMode? themeMode,
ThemeType? themeType,
}) {
return ThemeState(
lightTheme: lightTheme ?? this.lightTheme,
themeMode: themeMode ?? this.themeMode,
themeType: themeType ?? this.themeType,
);
// ThemeState copyWith({
// ThemeData? lightTheme,
// ThemeMode? themeMode,
// ThemeType? themeType,
// }) {
// return ThemeState(
// lightTheme: lightTheme ?? this.lightTheme,
// themeMode: themeMode ?? this.themeMode,
// themeType: themeType ?? this.themeType,
// );
// }
// bool get isDarkMode => themeMode == ThemeMode.dark;
// }
abstract class ThemeState extends Equatable {
getThemeData();
@override
List<Object?> get props => [];
}
class ThemeBlue extends ThemeState {
@override
getThemeData() {
print('returning blue theme');
return AppThemes.getLightTheme(ThemeType.blue);
}
}
class ThemeViolet extends ThemeState {
@override
getThemeData() {
print('returning violet theme');
return AppThemes.getLightTheme(ThemeType.violet);
}
bool get isDarkMode => themeMode == ThemeMode.dark;
}

View File

@@ -1,6 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:kmobile/api/services/beneficiary_service.dart';
import 'package:kmobile/data/models/ifsc.dart';
import 'package:kmobile/data/models/beneficiary.dart';
import 'beneficiary_result_page.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../../di/injection.dart';
import '../../../l10n/app_localizations.dart';
class AddBeneficiaryScreen extends StatefulWidget {
@@ -21,7 +26,9 @@ class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> {
final TextEditingController branchNameController = TextEditingController();
final TextEditingController ifscController = TextEditingController();
final TextEditingController phoneController = TextEditingController();
bool _isLoading2 = false;
late String accountType;
@override
@@ -34,7 +41,7 @@ class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> {
});
}
void _submitForm() {
/*void _submitForm() {
if (_formKey.currentState!.validate()) {
// Handle successful submission
ScaffoldMessenger.of(context).showSnackBar(
@@ -56,11 +63,11 @@ class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> {
onPressed: () {
// Navigate to Payment Screen or do something
},
style: TextButton.styleFrom(foregroundColor: Colors.blue[200]),
style: TextButton.styleFrom(foregroundColor: Theme.of(context).primaryColorLight),
child: Text(AppLocalizations.of(context).payNow),
),
IconButton(
icon: const Icon(Icons.close, color: Colors.white),
icon: Icon(Icons.close, color: Theme.of(context).scaffoldBackgroundColor),
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
@@ -70,58 +77,82 @@ class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> {
),
);
}
}*/
ifsc? _ifscData;
bool _isLoading = false;
void _validateIFSC() async {
var beneficiaryService = getIt<BeneficiaryService>();
final ifsc = ifscController.text.trim().toUpperCase();
if (ifsc.isEmpty) return;
setState(() {
_isLoading = true;
_ifscData = null;
});
final result = await beneficiaryService.validateIFSC(ifsc);
setState(() {
_isLoading = false;
_ifscData = result;
});
if (result == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Invalid IFSC code')),
);
bankNameController.clear();
branchNameController.clear();
} else {
print("Valid IFSC: ${result.bankName}, ${result.branchName}");
bankNameController.text = result.bankName;
branchNameController.text = result.branchName;
}
}
void _validateIFSC() async {
final ifsc = ifscController.text.trim().toUpperCase();
// 🔹 Format check
final isValidFormat = RegExp(r'^[A-Z]{4}0[A-Z0-9]{6}$').hasMatch(ifsc);
if (!isValidFormat) {
bankNameController.clear();
branchNameController.clear();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(AppLocalizations.of(context).invalidIfscFormat)),
);
return;
}
await Future.delayed(
const Duration(seconds: 2),
); //Mock delay for 2 sec to imitate api call
// 🔹 Mock IFSC lookup (you can replace this with API)
const mockIfscData = {
'KCCB0001234': {
'bank': 'Kangra Central Co-operative Bank',
'branch': 'Dharamshala',
},
'SBIN0004567': {
'bank': 'State Bank of India',
'branch': 'Connaught Place',
},
};
String _selectedAccountType = 'Savings'; // default value
if (mockIfscData.containsKey(ifsc)) {
final data = mockIfscData[ifsc]!;
bankNameController.text = data['bank']!;
branchNameController.text = data['branch']!;
} else {
bankNameController.clear();
branchNameController.clear();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(AppLocalizations.of(context).noIfscDetails)),
void validateAndAddBeneficiary() async {
setState(() {
_isLoading = true;
});
final beneficiary = Beneficiary(
accountNo: accountNumberController.text.trim(),
accountType: _selectedAccountType,
name: nameController.text.trim(),
ifscCode: ifscController.text.trim(),
);
var service = getIt<BeneficiaryService>();
try {
await service.sendForValidation(beneficiary);
bool isFound = await service.checkIfFound(beneficiary.accountNo);
if (context.mounted) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BeneficiaryResultPage(isSuccess: isFound),
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Something went wrong during validation.")),
);
} finally {
if (mounted) {
setState(() {
_isLoading2 = false;
});
}
}
}
/*
🔸 Optional: Use real IFSC API like:
final response = await http.get(Uri.parse('https://ifsc.razorpay.com/$ifsc'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
bankNameController.text = data['BANK'];
branchNameController.text = data['BRANCH'];
}
*/
}
@override
Widget build(BuildContext context) {
@@ -263,110 +294,7 @@ class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> {
? AppLocalizations.of(context).nameRequired
: null,
),
/*const SizedBox(height: 24),
TextFormField(
controller: bankNameController,
decoration: const InputDecoration(
labelText: 'Beneficiary Bank Name',
// prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2),
),
),
textInputAction: TextInputAction.next,
validator: (value) =>
value == null || value.isEmpty ? "Bank name is required" : null,
),
const SizedBox(height: 24),
TextFormField(
controller: branchNameController,
decoration: const InputDecoration(
labelText: 'Branch Name',
// prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2),
),
),
textInputAction: TextInputAction.next,
validator: (value) =>
value == null || value.isEmpty ? "Branch name is required" : null,
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
flex: 2,
child: TextFormField(
controller: ifscController,
decoration: const InputDecoration(
labelText: 'IFSC Code',
// prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2),
),
),
textInputAction: TextInputAction.next,
validator: (value) => value == null || value.length < 5
? "Enter a valid IFSC"
: null,
),
),
const SizedBox(width: 16),
Expanded(
flex: 2,
child: DropdownButtonFormField<String>(
value: accountType,
decoration: const InputDecoration(
labelText: 'Account Type',
// prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Colors.white,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black, width: 2),
),
),
items: ['Savings', 'Current']
.map((type) => DropdownMenuItem(
value: type,
child: Text(type),
))
.toList(),
onChanged: (value) {
setState(() {
accountType = value!;
});
},
),
),
],
),*/
const SizedBox(height: 24),
// 🔹 IFSC Code Field
TextFormField(
@@ -538,7 +466,8 @@ class _AddBeneficiaryScreen extends State<AddBeneficiaryScreen> {
child: SizedBox(
width: 250,
child: ElevatedButton(
onPressed: _submitForm,
onPressed: _isLoading2 ? null :
validateAndAddBeneficiary,
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(vertical: 16),

View File

@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class BeneficiaryResultPage extends StatelessWidget {
final bool isSuccess;
const BeneficiaryResultPage({super.key, required this.isSuccess});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
isSuccess ? 'Beneficiary Added Successfully!' : 'Beneficiary Addition Failed!',
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
);
}
}

View File

@@ -482,7 +482,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
MaterialPageRoute(
builder: (context) =>
const FundTransferBeneficiaryScreen()));
}, disable: true),
}, disable: false),
_buildQuickLink(Symbols.server_person,
AppLocalizations.of(context).accountInfo, () {
Navigator.push(
@@ -518,7 +518,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
MaterialPageRoute(
builder: (context) =>
const ManageBeneficiariesScreen()));
}, disable: true),
}, disable: false),
_buildQuickLink(Symbols.support_agent,
AppLocalizations.of(context).contactUs, () {
Navigator.push(

View File

@@ -70,7 +70,7 @@ class AccountCard extends StatelessWidget {
const SizedBox(height: 5),
Text(
AppLocalizations.of(context).availableBalance,
style: TextStyle(color: Colors.white70, fontSize: 12),
style: TextStyle(color: Theme.of(context).dialogBackgroundColor, fontSize: 12),
),
],
),

View File

@@ -106,7 +106,7 @@ class _TpinOtpScreenState extends State<TpinOtpScreen> {
decoration: InputDecoration(
counterText: '',
filled: true,
fillColor: Colors.blue[50],
fillColor: Theme.of(context).primaryColorLight,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(

View File

@@ -172,7 +172,7 @@ class _TpinSetScreenState extends State<TpinSetScreen> {
key == '<' ? '' : key,
style: TextStyle(
fontSize: 20,
color: key == 'Enter' ? Colors.blue : Colors.black,
color: key == 'Enter' ? Theme.of(context).primaryColor : Colors.black,
),
),
),
@@ -201,7 +201,7 @@ class _TpinSetScreenState extends State<TpinSetScreen> {
child: Column(
children: [
const Spacer(),
const Icon(Icons.lock_outline, size: 60, color: Colors.blue),
Icon(Icons.lock_outline, size: 60, color: Theme.of(context).primaryColor),
const SizedBox(height: 20),
Text(
getTitle(),

View File

@@ -5,7 +5,7 @@ import 'package:kmobile/features/auth/controllers/theme_cubit.dart';
import 'package:kmobile/features/auth/controllers/theme_state.dart';
void showColorThemeDialog(BuildContext context) {
/*void showColorThemeDialog(BuildContext context) {
showDialog(
context: context,
builder: (_) => AlertDialog(
@@ -32,4 +32,50 @@ void showColorThemeDialog(BuildContext context) {
),
),
);
}
}*/
class ColorThemeDialog extends StatelessWidget {
const ColorThemeDialog({super.key});
@override
Widget build(BuildContext context) {
return SimpleDialog(
title: const Text('Select Color Theme'),
children: [
ListTile(
leading: const CircleAvatar(backgroundColor: Colors.deepPurple),
title: const Text('Violet'),
onTap: () {
context.read<ThemeCubit>().changeTheme(ThemeType.violet);
Navigator.pop(context);
},
),
// ListTile(
// leading: const CircleAvatar(backgroundColor: Colors.green),
// title: const Text('Green'),
// onTap: () {
// context.read<ThemeCubit>().changeTheme(ThemeType.green);
// Navigator.pop(context);
// },
// ),
// ListTile(
// leading: const CircleAvatar(backgroundColor: Colors.orange),
// title: const Text('Orange'),
// onTap: () {
// context.read<ThemeCubit>().changeTheme(ThemeType.orange);
// Navigator.pop(context);
// },
// ),
ListTile(
leading: const CircleAvatar(backgroundColor: Colors.blue),
title: const Text('Blue'),
onTap: () {
context.read<ThemeCubit>().changeTheme(ThemeType.blue);
Navigator.pop(context);
},
),
],
);
}
}

View File

@@ -22,21 +22,26 @@ class PreferenceScreen extends StatelessWidget {
return ListView(
children: [
// Theme Mode Switch (Light/Dark)
ListTile(
leading: const Icon(Icons.brightness_6),
title: const Text("Theme Mode"),
trailing: Switch(
value: state.isDarkMode,
onChanged: (val) {
context.read<ThemeCubit>().toggleDarkMode(val);
},
),
),
// ListTile(
// leading: const Icon(Icons.brightness_6),
// title: const Text("Theme Mode"),
// trailing: Switch(
// value: state.isDarkMode,
// onChanged: (val) {
// context.read<ThemeCubit>().toggleDarkMode(val);
// },
// ),
// ),
//Color_Theme_Selection
ListTile(
leading: const Icon(Icons.color_lens),
title: const Text('Theme Color'),
onTap: () => showColorThemeDialog(context),
onTap: () {
showDialog(
context: context,
builder: (_) => const ColorThemeDialog(),
);
}
),
// Language Selection
ListTile(