implemented TPIN and quick pay within bank

This commit is contained in:
2025-06-23 04:47:05 +05:30
parent 0d2dfc817e
commit 77a2654401
44 changed files with 1692 additions and 1153 deletions

View File

@@ -1,9 +1,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="kmobile"
android:name="${applicationName}"
android:usesCleartextTraffic="true"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip

View File

@@ -19,8 +19,8 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false
id "org.jetbrains.kotlin.android" version "2.1.21" apply false
id "com.android.application" version "8.7.2" apply false
id "org.jetbrains.kotlin.android" version "2.1.20" apply false
}
include ":app"

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -51,4 +51,43 @@ class AuthService {
}
}
Future<bool> checkTpin() async {
try {
final response = await _dio.get('/api/auth/tpin');
if (response.statusCode == 200) {
return response.data['tpinSet'] as bool;
} else {
throw AuthException('Failed to check TPIN status');
}
} on DioException catch (e) {
if (kDebugMode) {
print(e.toString());
}
throw NetworkException('Network error during TPIN check');
} catch (e) {
throw UnexpectedException('Unexpected error during TPIN check: ${e.toString()}');
}
}
Future<void> setTpin(String tpin) async {
try {
final response = await _dio.post(
'/api/auth/tpin',
data: {'tpin': tpin},
);
if (response.statusCode != 200) {
throw AuthException('Failed to set TPIN');
}
} on DioException catch (e) {
if (kDebugMode) {
print(e.toString());
}
throw NetworkException('Network error during TPIN setup');
} catch (e) {
throw UnexpectedException('Unexpected error during TPIN setup: ${e.toString()}');
}
}
}

View File

@@ -0,0 +1,43 @@
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:kmobile/data/models/payment_response.dart';
import 'package:kmobile/data/models/transfer.dart';
class PaymentService {
final Dio _dio;
PaymentService(this._dio);
Future<PaymentResponse> processQuickPayWithinBank(Transfer transfer) async {
try {
await Future.delayed(const Duration(seconds: 3)); // Simulate delay
final response =
await _dio.post('/api/payment/transfer', data: transfer.toJson());
return PaymentResponse(
isSuccess: true,
date: DateTime.now(),
creditedAccount: transfer.toAccount,
amount: transfer.amount,
currency: "INR",
errorMessage: response.data['errorMessage'],
errorCode: response.data['errorCode'],
);
} on DioException catch (e) {
log('DioException: ${e.toString()}');
if (e.response?.data != null) {
return PaymentResponse(
isSuccess: false,
errorMessage: e.response?.data['error'] ?? 'Unknown error',
errorCode: e.response?.data['status'] ?? 'UNKNOWN_ERROR',
);
}
throw Exception(
'Failed to process quick pay within bank: ${e.toString()}');
} catch (e) {
log('Unexpected error: ${e.toString()}');
throw Exception(
'Failed to process quick pay within bank: ${e.toString()}');
}
}
}

View File

@@ -60,9 +60,9 @@ class _KMobileState extends State<KMobile> {
],
child: MaterialApp(
title: 'kMobile',
debugShowCheckedModeBanner: false,
// debugShowCheckedModeBanner: false,
theme: AppThemes.lightTheme,
darkTheme: AppThemes.darkTheme,
// darkTheme: AppThemes.darkTheme,
themeMode: ThemeMode.system, // Use system theme by default
onGenerateRoute: AppRoutes.generateRoute,
initialRoute: AppRoutes.splash,

View File

@@ -9,13 +9,13 @@ class AppThemes {
static const Color _secondaryColorLight = Color(0xFF26A69A); // Teal 400
static const Color _errorColorLight = Color(0xFFE53935); // Red 600
static const Color _surfaceColorLight = Colors.white;
// Dark theme colors
static const Color _primaryColorDark = Color(0xFF42A5F5); // Blue 400
static const Color _secondaryColorDark = Color(0xFF4DB6AC); // Teal 300
static const Color _errorColorDark = Color(0xFFEF5350); // Red 400
static const Color _surfaceColorDark = Color(0xFF1E1E1E);
// Text themes
static const TextTheme _textThemeLight = TextTheme(
displayLarge: TextStyle(
@@ -79,12 +79,13 @@ class AppThemes {
fontFamily: 'Rubik',
),
);
static final TextTheme _textThemeDark = _textThemeLight.copyWith(
displayLarge: _textThemeLight.displayLarge?.copyWith(color: Colors.white),
displayMedium: _textThemeLight.displayMedium?.copyWith(color: Colors.white),
displaySmall: _textThemeLight.displaySmall?.copyWith(color: Colors.white),
headlineMedium: _textThemeLight.headlineMedium?.copyWith(color: Colors.white),
headlineMedium:
_textThemeLight.headlineMedium?.copyWith(color: Colors.white),
headlineSmall: _textThemeLight.headlineSmall?.copyWith(color: Colors.white),
titleLarge: _textThemeLight.titleLarge?.copyWith(color: Colors.white),
bodyLarge: _textThemeLight.bodyLarge?.copyWith(color: Colors.white),
@@ -115,7 +116,7 @@ class AppThemes {
foregroundColor: Color(0xFF212121),
centerTitle: true,
),
cardTheme: CardTheme(
cardTheme: CardThemeData(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
@@ -198,7 +199,7 @@ class AppThemes {
foregroundColor: Colors.white,
centerTitle: true,
),
cardTheme: CardTheme(
cardTheme: CardThemeData(
elevation: 2,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
@@ -260,4 +261,4 @@ class AppThemes {
backgroundColor: _surfaceColorDark,
),
);
}
}

View File

@@ -0,0 +1,19 @@
class PaymentResponse {
final bool isSuccess;
final DateTime? date;
final String? creditedAccount;
final String? amount;
final String? currency;
final String? errorMessage;
final String? errorCode;
PaymentResponse({
required this.isSuccess,
this.date,
this.creditedAccount,
this.amount,
this.currency,
this.errorMessage,
this.errorCode,
});
}

View File

@@ -2,7 +2,7 @@ class Transaction {
final String? id;
final String? name;
final String? date;
final int? amount;
final String? amount;
final String? type;
Transaction({this.id, this.name, this.date, this.amount, this.type});
@@ -21,7 +21,7 @@ class Transaction {
id: json['id'] as String?,
name: json['name'] as String?,
date: json['date'] as String?,
amount: json['amount'] as int?,
amount: json['amount'] as String?,
type: json['type'] as String?,
);
}

View File

@@ -0,0 +1,45 @@
class Transfer {
final String fromAccount;
final String toAccount;
final String toAccountType;
final String amount;
String? tpin;
Transfer({
required this.fromAccount,
required this.toAccount,
required this.toAccountType,
required this.amount,
this.tpin,
});
Map<String, dynamic> toJson() {
return {
'fromAccount': fromAccount,
'toAccount': toAccount,
'toAccountType': toAccountType,
'amount': amount,
'tpin': tpin,
};
}
}
class TransferResponse {
final String? status;
final String? message;
final String? error;
TransferResponse({
required this.status,
required this.message,
required this.error,
});
factory TransferResponse.fromJson(Map<String, dynamic> json) {
return TransferResponse(
status: json['status'] as String?,
message: json['message'] as String?,
error: json['error'] as String?,
);
}
}

View File

@@ -0,0 +1,28 @@
import 'package:dio/dio.dart';
import 'package:kmobile/data/models/transaction.dart';
abstract class TransactionRepository {
Future<List<Transaction>> fetchTransactions(String accountNo);
}
class TransactionRepositoryImpl implements TransactionRepository {
final Dio _dio;
TransactionRepositoryImpl(this._dio);
@override
Future<List<Transaction>> fetchTransactions(String accountNo) async {
final resp = await _dio.get('/api/transactions/account/$accountNo');
if (resp.statusCode != 200) {
throw Exception(
'Error fetching transactions: ${resp.statusCode} ${resp.statusMessage}',
);
}
final List<dynamic> data = resp.data as List<dynamic>;
return data
.map((e) => Transaction.fromJson(e as Map<String, dynamic>))
.toList();
}
}

View File

@@ -0,0 +1,28 @@
import 'package:kmobile/data/models/transfer.dart';
import 'package:dio/dio.dart';
abstract class TransferRepository {
Future<TransferResponse> transfer(Transfer transfer);
}
class TransferRepositoryImpl implements TransferRepository {
final Dio _dio;
TransferRepositoryImpl(this._dio);
@override
Future<TransferResponse> transfer(Transfer transfer) async {
final resp = await _dio.post(
'/api/payment/transfer',
data: transfer.toJson(),
);
if (resp.statusCode != 200) {
throw Exception(
'Error transferring funds: ${resp.statusCode} ${resp.statusMessage}',
);
}
return TransferResponse.fromJson(resp.data as Map<String, dynamic>);
}
}

View File

@@ -1,6 +1,8 @@
import 'package:get_it/get_it.dart';
import 'package:dio/dio.dart';
import 'package:kmobile/api/services/payment_service.dart';
import 'package:kmobile/api/services/user_service.dart';
import 'package:kmobile/data/repositories/transaction_repository.dart';
import '../api/services/auth_service.dart';
import '../api/interceptors/auth_interceptor.dart';
import '../data/repositories/auth_repository.dart';
@@ -27,6 +29,10 @@ Future<void> setupDependencies() async {
AuthRepository(
getIt<AuthService>(), getIt<UserService>(), getIt<SecureStorage>()),
);
getIt.registerSingleton<TransactionRepository>(
TransactionRepositoryImpl(getIt<Dio>()));
getIt.registerSingleton<PaymentService>(PaymentService(getIt<Dio>()));
// Add auth interceptor after repository is available
getIt<Dio>().interceptors.add(
@@ -34,13 +40,15 @@ Future<void> setupDependencies() async {
);
// Register controllers/cubits
getIt.registerFactory<AuthCubit>(() => AuthCubit(getIt<AuthRepository>(), getIt<UserService>()));
getIt.registerFactory<AuthCubit>(
() => AuthCubit(getIt<AuthRepository>(), getIt<UserService>()));
}
Dio _createDioClient() {
final dio = Dio(
BaseOptions(
baseUrl: 'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080',
baseUrl:
'http://lb-test-mobile-banking-app-192209417.ap-south-1.elb.amazonaws.com:8080',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3),
headers: {

View File

@@ -1,8 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:shimmer/shimmer.dart';
import 'package:kmobile/data/models/transaction.dart';
import 'package:kmobile/data/repositories/transaction_repository.dart';
import 'package:kmobile/di/injection.dart';
class AccountStatementScreen extends StatefulWidget {
const AccountStatementScreen({super.key});
final String accountNo;
const AccountStatementScreen({
super.key,
required this.accountNo,
});
@override
State<AccountStatementScreen> createState() => _AccountStatementScreen();
@@ -11,94 +20,71 @@ class AccountStatementScreen extends StatefulWidget {
class _AccountStatementScreen extends State<AccountStatementScreen> {
DateTime? fromDate;
DateTime? toDate;
final _amountRangeController = TextEditingController(text: "");
final transactions = [
{
"desc": "Transfer From ICICI Bank subsidy",
"amount": "+₹133.98",
"type": "Cr",
"time": "21-02-2024 13:09:30"
},
{
"desc": "Mobile recharge",
"amount": "-₹299.00",
"type": "Dr",
"time": "21-02-2024 13:09:30"
},
{
"desc": "NEFT received from Jaya Saha",
"amount": "+₹987.80",
"type": "Cr",
"time": "21-02-2024 13:09:30"
},
{
"desc": "Transfer From ICICI Bank subsidy",
"amount": "+₹100.00",
"type": "Cr",
"time": "21-02-2024 13:09:30"
},
{
"desc": "Transfer From ICICI Bank subsidy",
"amount": "-₹100.00",
"type": "Dr",
"time": "21-02-2024 13:09:30"
},
{
"desc": "Transfer From ICICI Bank subsidy",
"amount": "+₹100.00",
"type": "Cr",
"time": "21-02-2024 13:09:30"
},
{
"desc": "Transfer From ICICI Bank subsidy",
"amount": "+₹100.00",
"type": "Cr",
"time": "21-02-2024 13:09:30"
},
];
bool _txLoading = true;
List<Transaction> _transactions = [];
final _minAmountController = TextEditingController();
final _maxAmountController = TextEditingController();
@override
void initState() {
super.initState();
_loadTransactions();
}
Future<void> _loadTransactions() async {
setState(() {
_txLoading = true;
_transactions = [];
});
try {
final repo = getIt<TransactionRepository>();
final txs = await repo.fetchTransactions(widget.accountNo);
setState(() => _transactions = txs);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to load transactions: $e')),
);
} finally {
setState(() => _txLoading = false);
}
}
Future<void> _selectFromDate(BuildContext context) async {
final DateTime now = DateTime.now();
final DateTime? picked = await showDatePicker(
final now = DateTime.now();
final picked = await showDatePicker(
context: context,
initialDate: fromDate ?? now,
firstDate: DateTime(2020), // or your app's start limit
lastDate: now, // No future date
firstDate: DateTime(2020),
lastDate: now,
);
if (picked != null) {
setState(() {
fromDate = picked;
toDate = null; // reset toDate when fromDate changes
toDate = null;
});
}
}
Future<void> _selectToDate(BuildContext context) async {
if (fromDate == null) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('Please select From Date first'),
));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Please select From Date first')),
);
return;
}
final DateTime now = DateTime.now();
final DateTime lastAllowedDate = fromDate!.add(const Duration(days: 31));
final DateTime maxToDate = lastAllowedDate.isBefore(now) ? lastAllowedDate : now;
final DateTime? picked = await showDatePicker(
final now = DateTime.now();
final maxToDate = fromDate!.add(const Duration(days: 31)).isBefore(now)
? fromDate!.add(const Duration(days: 31))
: now;
final picked = await showDatePicker(
context: context,
initialDate: toDate ?? fromDate!,
firstDate: fromDate!, // 🔒 can't be before fromDate
lastDate: maxToDate, // 🔒 not more than 1 month or future
firstDate: fromDate!,
lastDate: maxToDate,
);
if (picked != null) {
setState(() {
toDate = picked;
});
setState(() => toDate = picked);
}
}
@@ -110,7 +96,8 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
@override
void dispose() {
_amountRangeController.dispose();
_minAmountController.dispose();
_maxAmountController.dispose();
super.dispose();
}
@@ -120,22 +107,25 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
appBar: AppBar(
leading: IconButton(
icon: const Icon(Symbols.arrow_back_ios_new),
onPressed: () {
Navigator.pop(context);
},
onPressed: () => Navigator.pop(context),
),
title: const Text(
'Account Statement',
style: TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
),
centerTitle: false,
actions: const [
actions: [
Padding(
padding: EdgeInsets.only(right: 10.0),
padding: const EdgeInsets.only(right: 10.0),
child: CircleAvatar(
backgroundImage: AssetImage('assets/images/avatar.jpg'),
// Replace with your image
backgroundColor: Colors.grey[200],
radius: 20,
child: SvgPicture.asset(
'assets/images/avatar_male.svg',
width: 40,
height: 40,
fit: BoxFit.cover,
),
),
),
],
@@ -145,58 +135,158 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Filters',
style: TextStyle(fontSize: 17),
),
const SizedBox(
height: 15,
),
const Text('Filters', style: TextStyle(fontSize: 17)),
const SizedBox(height: 15),
Row(
children: [
Expanded(child: GestureDetector(
onTap: () => _selectFromDate(context),
child: buildDateBox("From Date", fromDate),
),),
Expanded(
child: GestureDetector(
onTap: () => _selectFromDate(context),
child: buildDateBox("From Date", fromDate),
),
),
const SizedBox(width: 10),
Expanded(child: GestureDetector(
onTap: () => _selectToDate(context),
child: buildDateBox("To Date", toDate),
),),
Expanded(
child: GestureDetector(
onTap: () => _selectToDate(context),
child: buildDateBox("To Date", toDate),
),
),
],
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
child: _buildFilterBox(""),
child: TextFormField(
controller: _minAmountController,
decoration: const InputDecoration(
labelText: 'Min Amount',
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Colors.white,
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
),
),
const SizedBox(
width: 8,
const SizedBox(width: 8),
Expanded(
child: TextFormField(
controller: _maxAmountController,
decoration: const InputDecoration(
labelText: 'Max Amount',
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Colors.white,
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.done,
),
),
const SizedBox(width: 8),
SizedBox(
width: 75,
width: 70,
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
),
child: const Text(
'Go',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
)),
)
onPressed: _loadTransactions,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
),
child: const Icon(
Symbols.arrow_forward,
color: Colors.white,
size: 30,
),
),
),
],
),
const SizedBox(height: 35),
Expanded(
child: ListView.builder(
itemCount: transactions.length,
itemBuilder: (context, index) {
final txn = transactions[index];
return _buildTransactionTile(txn);
},
if (!_txLoading && _transactions.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Text(
'Showing last 10 transactions.',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
),
),
Expanded(
child: _txLoading
? ListView.builder(
itemCount: 3,
itemBuilder: (_, __) => ListTile(
leading: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: const CircleAvatar(
radius: 12, backgroundColor: Colors.white),
),
title: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 10, width: 100, color: Colors.white),
),
subtitle: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 8, width: 60, color: Colors.white),
),
),
)
: _transactions.isEmpty
? Center(
child: Text(
'No transactions found for this account.',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
)
: ListView.builder(
itemCount: _transactions.length,
itemBuilder: (context, index) {
final txn = _transactions[index];
final isCredit = txn.type == 'CR';
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(txn.date ?? '',
style: const TextStyle(color: Colors.grey)),
const SizedBox(height: 4),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(txn.name ?? '',
style: const TextStyle(fontSize: 16)),
),
Text(
"${isCredit ? '+' : '-'}${txn.amount}",
style: TextStyle(
color: isCredit
? Colors.green
: Colors.red,
// fontWeight: FontWeight.bold,
fontSize: 16,
),
),
],
),
const SizedBox(height: 25),
],
);
},
),
),
],
),
@@ -216,65 +306,14 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
children: [
Text(
date != null ? _formatDate(date) : label,
style: TextStyle(fontSize: 16,
color: date != null ? Colors.black: Colors.grey),
style: TextStyle(
fontSize: 16,
color: date != null ? Colors.black : Colors.grey,
),
),
const Icon(Icons.arrow_drop_down),
],
),
);
}
Widget _buildFilterBox(String text) {
return TextFormField(
controller: _amountRangeController,
decoration: const InputDecoration(
labelText: 'Amount Range',
// 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),
),
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter amount range';
}
return null;
},
);
}
Widget _buildTransactionTile(Map<String, String> txn) {
final isCredit = txn['type'] == "Cr";
final amountColor = isCredit ? Colors.green : Colors.red;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(txn['time']!, style: const TextStyle(color: Colors.grey)),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child:
Text(txn['desc']!, style: const TextStyle(fontSize: 16))),
Text(
"${txn['amount']} ${txn['type']}",
style: TextStyle(color: amountColor, fontWeight: FontWeight.bold),
),
],
),
const SizedBox(height: 25),
],
);
}
}

View File

@@ -106,7 +106,7 @@ class _BlockCardScreen extends State<BlockCardScreen> {
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
validator: (value) => value != null && value.length == 16
validator: (value) => value != null && value.length == 11
? null
: 'Enter valid card number',
),

View File

@@ -96,7 +96,7 @@ class _CardPinChangeDetailsScreen extends State<CardPinChangeDetailsScreen> {
),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
validator: (value) => value != null && value.length == 16
validator: (value) => value != null && value.length == 11
? null
: 'Enter valid card number',
),

View File

@@ -1,6 +1,9 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:kmobile/data/repositories/transaction_repository.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/accounts/screens/account_info_screen.dart';
import 'package:kmobile/features/accounts/screens/account_statement_screen.dart';
@@ -8,7 +11,6 @@ import 'package:kmobile/features/auth/controllers/auth_cubit.dart';
import 'package:kmobile/features/auth/controllers/auth_state.dart';
import 'package:kmobile/features/customer_info/screens/customer_info_screen.dart';
import 'package:kmobile/features/beneficiaries/screens/manage_beneficiaries_screen.dart';
import 'package:kmobile/features/dashboard/widgets/transaction_list_placeholder.dart';
import 'package:kmobile/features/enquiry/screens/enquiry_screen.dart';
import 'package:kmobile/features/fund_transfer/screens/fund_transfer_beneficiary_screen.dart';
import 'package:kmobile/features/quick_pay/screens/quick_pay_screen.dart';
@@ -17,6 +19,7 @@ import 'package:local_auth/local_auth.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:shimmer/shimmer.dart';
import 'package:kmobile/data/models/transaction.dart';
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@@ -31,6 +34,34 @@ class _DashboardScreenState extends State<DashboardScreen> {
bool isRefreshing = false;
bool isBalanceLoading = false;
bool _biometricPromptShown = false;
bool _txLoading = false;
List<Transaction> _transactions = [];
bool _txInitialized = false;
Future<void> _loadTransactions(String accountNo) async {
setState(() {
_txLoading = true;
_transactions = [];
});
try {
final repo = getIt<TransactionRepository>();
final txs = await repo.fetchTransactions(accountNo);
var fiveTxns = <Transaction>[];
//only take the first 5 transactions
if (txs.length > 5) {
fiveTxns = txs.sublist(0, 5);
} else {
fiveTxns = txs;
}
setState(() => _transactions = fiveTxns);
} catch (e) {
log(accountNo, error: e);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to load transactions: $e')));
} finally {
setState(() => _txLoading = false);
}
}
Future<void> _refreshAccountData(BuildContext context) async {
setState(() {
@@ -200,6 +231,13 @@ class _DashboardScreenState extends State<DashboardScreen> {
if (state is Authenticated) {
final users = state.users;
final currAccount = users[selectedAccountIndex];
// firsttime load
if (!_txInitialized) {
_txInitialized = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadTransactions(currAccount.accountNo!);
});
}
final firstName = getProcessedFirstName(currAccount.name);
return SingleChildScrollView(
@@ -267,7 +305,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
selectedAccountIndex = newIndex;
});
await Future.delayed(
const Duration(seconds: 1));
const Duration(milliseconds: 200));
setState(() {
isBalanceLoading = false;
});
@@ -276,6 +314,8 @@ class _DashboardScreenState extends State<DashboardScreen> {
selectedAccountIndex = newIndex;
});
}
await _loadTransactions(
users[newIndex].accountNo!);
},
),
const Spacer(),
@@ -381,8 +421,8 @@ class _DashboardScreenState extends State<DashboardScreen> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const QuickPayScreen()));
builder: (context) => QuickPayScreen(
debitAccount: currAccount.accountNo!)));
}),
_buildQuickLink(Symbols.send_money, "Fund \n Transfer",
() {
@@ -391,7 +431,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
MaterialPageRoute(
builder: (context) =>
const FundTransferBeneficiaryScreen()));
}),
}, disable: true),
_buildQuickLink(
Symbols.server_person, "Account \n Info", () {
Navigator.push(
@@ -405,8 +445,10 @@ class _DashboardScreenState extends State<DashboardScreen> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const AccountStatementScreen()));
builder: (context) => AccountStatementScreen(
accountNo: users[selectedAccountIndex]
.accountNo!,
)));
}),
_buildQuickLink(
Symbols.checkbook, "Handle \n Cheque", () {},
@@ -418,7 +460,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
MaterialPageRoute(
builder: (context) =>
const ManageBeneficiariesScreen()));
}),
}, disable: true),
_buildQuickLink(Symbols.support_agent, "Contact \n Us",
() {
Navigator.push(
@@ -436,25 +478,43 @@ class _DashboardScreenState extends State<DashboardScreen> {
style: TextStyle(fontSize: 17),
),
const SizedBox(height: 16),
if (currAccount.transactions != null &&
currAccount.transactions!.isNotEmpty)
...currAccount.transactions!.map((tx) => ListTile(
if (_txLoading)
..._buildTransactionShimmer()
else if (_transactions.isNotEmpty)
..._transactions.map((tx) => ListTile(
leading: Icon(
tx.type == 'CR'
? Symbols.call_received
: Symbols.call_made,
color: tx.type == 'CR'
? Colors.green
: Colors.red),
title: Text(tx.name ?? ''),
subtitle: Text(tx.date ?? ''),
tx.type == 'CR'
? Symbols.call_received
: Symbols.call_made,
color:
tx.type == 'CR' ? Colors.green : Colors.red,
),
title: Text(
tx.name != null
? (tx.name!.length > 18
? tx.name!.substring(0, 20)
: tx.name!)
: '',
style: const TextStyle(fontSize: 14),
),
subtitle: Text(tx.date ?? '',
style: const TextStyle(fontSize: 12)),
trailing: Text("${tx.amount}",
style: const TextStyle(
fontSize: 16,
)),
style: const TextStyle(fontSize: 16)),
))
else
const EmptyTransactionsPlaceholder(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Center(
child: Text(
'No transactions found for this account.',
style: TextStyle(
fontSize: 16,
color: Colors.grey[600],
),
),
),
),
],
),
),
@@ -466,6 +526,28 @@ class _DashboardScreenState extends State<DashboardScreen> {
);
}
List<Widget> _buildTransactionShimmer() {
return List.generate(3, (i) {
return ListTile(
leading: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: const CircleAvatar(radius: 12, backgroundColor: Colors.white),
),
title: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(height: 10, width: 100, color: Colors.white),
),
subtitle: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(height: 8, width: 60, color: Colors.white),
),
);
});
}
Widget _buildQuickLink(IconData icon, String label, VoidCallback onTap,
{bool disable = false}) {
return InkWell(

View File

@@ -11,27 +11,43 @@ class EnquiryScreen extends StatefulWidget {
}
class _EnquiryScreen extends State<EnquiryScreen> {
Future<void> _launchEmail() async {
final Uri emailUri = Uri(
scheme: 'mailto',
path: 'helpdesk@kccb.in',
);
Future<void> _launchEmailAddress(String email) async {
final Uri emailUri = Uri(scheme: 'mailto', path: email);
if (await canLaunchUrl(emailUri)) {
await launchUrl(emailUri);
} else {
debugPrint('Could not launch email client');
debugPrint('Could not launch email client for $email');
}
}
Future<void> _launchPhone() async {
final Uri phoneUri = Uri(scheme: 'tel', path: '0651-312861');
Future<void> _launchPhoneNumber(String phone) async {
final Uri phoneUri = Uri(scheme: 'tel', path: phone);
if (await canLaunchUrl(phoneUri)) {
await launchUrl(phoneUri);
} else {
debugPrint('Could not launch phone dialer');
debugPrint('Could not launch dialer for $phone');
}
}
Widget _buildContactItem(String role, String email, String phone) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(role, style: const TextStyle(color: Colors.grey)),
const SizedBox(height: 4),
GestureDetector(
onTap: () => _launchEmailAddress(email),
child: Text(email, style: const TextStyle(color: Colors.blue)),
),
const SizedBox(height: 4),
GestureDetector(
onTap: () => _launchPhoneNumber(phone),
child: Text(phone, style: const TextStyle(color: Colors.blue)),
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -68,32 +84,50 @@ class _EnquiryScreen extends State<EnquiryScreen> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("Mail us at", style: TextStyle(color: Colors.grey)),
const SizedBox(height: 4),
GestureDetector(
onTap: _launchEmail,
child: const Text(
"helpdesk@kccb.in",
style: TextStyle(color: Colors.blue),
),
),
const SizedBox(height: 20),
const Text("Call us at", style: TextStyle(color: Colors.grey)),
const SizedBox(height: 4),
GestureDetector(
onTap: _launchPhone,
child: const Text(
"0651-312861",
style: TextStyle(color: Colors.blue),
),
),
// … existing Mail us / Call us / Write to us …
const SizedBox(height: 20),
const Text("Write to us", style: TextStyle(color: Colors.grey)),
const SizedBox(height: 4),
const Text(
"101 Street, Some Street, Some Address\nSome Address",
"complaint@kccb.in",
style: TextStyle(color: Colors.blue),
),
const SizedBox(height: 20),
Text(
"Key Contacts",
style: TextStyle(
fontSize: 17,
color: Theme.of(context).primaryColor,
),
// horizontal line
),
Divider(color: Colors.grey[300]),
const SizedBox(height: 16),
_buildContactItem(
"Chairman",
"chairman@kccb.in",
"01892-222677",
),
const SizedBox(height: 16),
_buildContactItem(
"Managing Director",
"md@kccb.in",
"01892-224969",
),
const SizedBox(height: 16),
_buildContactItem(
"General Manager (West)",
"gmw@kccb.in",
"01892-223280",
),
const SizedBox(height: 16),
_buildContactItem(
"General Manager (North)",
"gmn@kccb.in",
"01892-224607",
),
],
),
),

View File

@@ -22,10 +22,13 @@ class _FundTransferScreen extends State<FundTransferScreen> {
} else if (key == 'done') {
if (kDebugMode) {
print('Amount entered: $amount');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TransactionPinScreen()));
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => const TransactionPinScreen(
// transactionData: {},
// transactionCode: 'TRANSFER'
// )));
}
} else {
amount += key;

View File

@@ -0,0 +1,205 @@
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:kmobile/data/models/payment_response.dart';
import 'package:lottie/lottie.dart';
import 'package:share_plus/share_plus.dart';
import 'package:path_provider/path_provider.dart';
class PaymentAnimationScreen extends StatefulWidget {
final Future<PaymentResponse> paymentResponse;
const PaymentAnimationScreen({
super.key,
required this.paymentResponse,
});
@override
State<PaymentAnimationScreen> createState() => _PaymentAnimationScreenState();
}
class _PaymentAnimationScreenState extends State<PaymentAnimationScreen> {
final GlobalKey _shareKey = GlobalKey();
Future<void> _shareScreenshot() async {
try {
RenderRepaintBoundary boundary =
_shareKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
ui.Image image = await boundary.toImage(pixelRatio: 3.0);
ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
Uint8List pngBytes = byteData!.buffer.asUint8List();
final tempDir = await getTemporaryDirectory();
final file = await File('${tempDir.path}/payment_result.png').create();
await file.writeAsBytes(pngBytes);
await Share.shareXFiles([XFile(file.path)], text: 'Payment Result');
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to share screenshot: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<PaymentResponse>(
future: widget.paymentResponse,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: Lottie.asset(
'assets/animations/rupee.json',
width: 200,
height: 200,
repeat: true,
),
);
}
final response = snapshot.data!;
final isSuccess = response.isSuccess;
return Stack(
children: [
Center(
child: RepaintBoundary(
key: _shareKey,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 80),
Lottie.asset(
isSuccess
? 'assets/animations/done.json'
: 'assets/animations/error.json',
width: 200,
height: 200,
repeat: false,
),
const SizedBox(height: 10),
isSuccess
? Column(
children: [
const Text(
'Payment Successful!',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.green),
),
const SizedBox(height: 16),
if (response.amount != null)
Text(
'Amount: ${response.amount} ${response.currency ?? ''}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
fontFamily: 'Rubik'),
),
if (response.creditedAccount != null)
Text(
'Credited Account: ${response.creditedAccount}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
fontFamily: 'Rubik'),
),
if (response.date != null)
Text(
'Date: ${response.date!.toLocal().toIso8601String()}',
style: const TextStyle(fontSize: 16),
),
],
)
: Column(
children: [
const Text(
'Payment Failed',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.red),
),
const SizedBox(height: 16),
if (response.errorMessage != null)
Text(
response.errorMessage!,
style: const TextStyle(fontSize: 16),
),
],
),
const SizedBox(height: 40),
],
),
),
),
),
// Buttons at the bottom
Positioned(
left: 0,
right: 0,
bottom: 80,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: _shareScreenshot,
icon: Icon(
Icons.share_rounded,
color: Theme.of(context).primaryColor,
),
label: Text('Share',
style:
TextStyle(color: Theme.of(context).primaryColor)),
style: ElevatedButton.styleFrom(
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
padding: const EdgeInsets.symmetric(
horizontal: 32, vertical: 12),
shape: RoundedRectangleBorder(
side: BorderSide(
color: Theme.of(context).primaryColor,
width: 1,
),
borderRadius: BorderRadius.circular(30),
),
textStyle: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black),
),
),
ElevatedButton.icon(
onPressed: () {
Navigator.of(context)
.popUntil((route) => route.isFirst);
},
label: const Text('Done'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 45, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
textStyle: const TextStyle(
fontSize: 18, fontWeight: FontWeight.w600),
),
),
],
),
),
],
);
},
),
);
}
}

View File

@@ -0,0 +1,159 @@
import 'package:flutter/material.dart';
import 'package:kmobile/features/fund_transfer/screens/tpin_set_screen.dart';
class TpinOtpScreen extends StatefulWidget {
const TpinOtpScreen({super.key});
@override
State<TpinOtpScreen> createState() => _TpinOtpScreenState();
}
class _TpinOtpScreenState extends State<TpinOtpScreen> {
final List<FocusNode> _focusNodes = List.generate(4, (_) => FocusNode());
final List<TextEditingController> _controllers =
List.generate(4, (_) => TextEditingController());
@override
void dispose() {
for (final node in _focusNodes) {
node.dispose();
}
for (final ctrl in _controllers) {
ctrl.dispose();
}
super.dispose();
}
void _onOtpChanged(int idx, String value) {
if (value.length == 1 && idx < 3) {
_focusNodes[idx + 1].requestFocus();
}
if (value.isEmpty && idx > 0) {
_focusNodes[idx - 1].requestFocus();
}
setState(() {});
}
String get _enteredOtp => _controllers.map((c) => c.text).join();
void _verifyOtp() {
if (_enteredOtp == '0000') {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => TpinSetScreen(),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Invalid OTP')),
);
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Enter OTP'),
centerTitle: true,
elevation: 0,
),
body: Center(
child: SingleChildScrollView(
child: Column(
// mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.lock_outline,
size: 48, color: theme.colorScheme.primary),
const SizedBox(height: 16),
Text(
'OTP Verification',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
const SizedBox(height: 8),
Text(
'Enter the 4-digit OTP sent to your mobile number.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.grey[700],
),
),
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(4, (i) {
return Container(
width: 48,
margin: const EdgeInsets.symmetric(horizontal: 8),
child: TextField(
controller: _controllers[i],
focusNode: _focusNodes[i],
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
maxLength: 1,
obscureText: true,
obscuringCharacter: '',
decoration: InputDecoration(
counterText: '',
filled: true,
fillColor: Colors.blue[50],
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: theme.colorScheme.primary,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: theme.colorScheme.primary,
width: 2.5,
),
),
),
onChanged: (val) => _onOtpChanged(i, val),
),
);
}),
),
const SizedBox(height: 32),
ElevatedButton.icon(
icon: const Icon(Icons.verified_user_rounded),
label: const Text(
'Verify OTP',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
padding:
const EdgeInsets.symmetric(vertical: 14, horizontal: 28),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: _enteredOtp.length == 4 ? _verifyOtp : null,
),
const SizedBox(height: 16),
TextButton(
onPressed: () {
// Resend OTP logic here
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('OTP resent (mock)')),
);
},
child: const Text('Resend OTP'),
),
const SizedBox(height: 60),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,77 @@
import 'package:flutter/material.dart';
import 'package:kmobile/features/fund_transfer/screens/tpin_otp_screen.dart';
class TpinSetupPromptScreen extends StatelessWidget {
const TpinSetupPromptScreen({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Set TPIN'),
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 100.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.lock_person_rounded,
size: 60, color: theme.colorScheme.primary),
const SizedBox(height: 18),
Text(
'TPIN Required',
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
color: theme.colorScheme.primary,
),
),
const SizedBox(height: 12),
Text(
'You need to set your TPIN to continue with secure transactions.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.grey[700],
),
),
const SizedBox(height: 32),
ElevatedButton.icon(
icon: const Icon(Icons.arrow_forward_rounded),
label: const Text(
'Set TPIN',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
),
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
padding:
const EdgeInsets.symmetric(vertical: 14, horizontal: 32),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const TpinOtpScreen(),
),
);
},
),
const SizedBox(height: 18),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: Text(
'Your TPIN is a 6-digit code used to authorize transactions. Keep it safe and do not share it with anyone.',
textAlign: TextAlign.center,
style: theme.textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,215 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/auth_service.dart';
import 'package:kmobile/di/injection.dart';
enum TPinMode { set, confirm }
class TpinSetScreen extends StatefulWidget {
const TpinSetScreen({super.key});
@override
State<TpinSetScreen> createState() => _TpinSetScreenState();
}
class _TpinSetScreenState extends State<TpinSetScreen> {
TPinMode _mode = TPinMode.set;
final List<String> _tpin = [];
String? _initialTpin;
String? _errorText;
void addDigit(String digit) {
if (_tpin.length < 6) {
setState(() {
_tpin.add(digit);
_errorText = null;
});
if (_tpin.length == 6) {
_handleComplete();
}
}
}
void deleteDigit() {
if (_tpin.isNotEmpty) {
setState(() {
_tpin.removeLast();
_errorText = null;
});
}
}
void _handleComplete() async {
final pin = _tpin.join();
if (_mode == TPinMode.set) {
setState(() {
_initialTpin = pin;
_tpin.clear();
_mode = TPinMode.confirm;
});
} else if (_mode == TPinMode.confirm) {
if (_initialTpin == pin) {
final authService = getIt<AuthService>();
try {
await authService.setTpin(pin);
} catch (e) {
setState(() {
_errorText = "Failed to set TPIN. Please try again.";
_tpin.clear();
});
return;
}
if (!mounted) return;
// Show success dialog before popping
await showDialog(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
title: const Column(
children: [
Icon(Icons.check_circle, color: Colors.green, size: 60),
SizedBox(height: 12),
Text('Success!', style: TextStyle(fontWeight: FontWeight.bold)),
],
),
content: const Text(
'Your TPIN was set up successfully.',
textAlign: TextAlign.center,
),
actions: [
TextButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: const Text('OK', style: TextStyle(fontSize: 16)),
),
],
),
);
if (mounted) {
Navigator.of(context).pop();
}
} else {
setState(() {
_errorText = "Pins do not match. Try again.";
_tpin.clear();
});
}
}
}
Widget buildPinDots() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(6, (index) {
return Container(
margin: const EdgeInsets.all(8),
width: 15,
height: 15,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: index < _tpin.length ? Colors.black : Colors.grey[400],
),
);
}),
);
}
Widget buildNumberPad() {
List<List<String>> keys = [
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
['Enter', '0', '<']
];
return Column(
children: keys.map((row) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: row.map((key) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
if (key == '<') {
deleteDigit();
} else if (key == 'Enter') {
if (_tpin.length == 6) {
_handleComplete();
} else {
setState(() {
_errorText = "Please enter 6 digits";
});
}
} else if (key.isNotEmpty) {
addDigit(key);
}
},
child: Container(
width: 70,
height: 70,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
),
alignment: Alignment.center,
child: key == 'Enter'
? const Icon(Icons.check)
: Text(
key == '<' ? '' : key,
style: TextStyle(
fontSize: 20,
color: key == 'Enter' ? Colors.blue : Colors.black,
),
),
),
),
);
}).toList(),
);
}).toList(),
);
}
String getTitle() {
switch (_mode) {
case TPinMode.set:
return "Set your new TPIN";
case TPinMode.confirm:
return "Confirm your new TPIN";
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Set TPIN')),
body: SafeArea(
child: Column(
children: [
const Spacer(),
const Icon(Icons.lock_outline, size: 60, color: Colors.blue),
const SizedBox(height: 20),
Text(
getTitle(),
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
const SizedBox(height: 20),
buildPinDots(),
if (_errorText != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(_errorText!,
style: const TextStyle(color: Colors.red)),
),
const Spacer(),
buildNumberPad(),
const Spacer(),
],
),
),
);
}
}

View File

@@ -1,9 +1,16 @@
import 'package:flutter/material.dart';
import 'package:kmobile/api/services/auth_service.dart';
import 'package:kmobile/api/services/payment_service.dart';
import 'package:kmobile/data/models/transfer.dart';
import 'package:kmobile/di/injection.dart';
import 'package:kmobile/features/fund_transfer/screens/payment_animation.dart';
import 'package:kmobile/features/fund_transfer/screens/tpin_prompt_screen.dart';
import 'package:kmobile/features/fund_transfer/screens/transaction_success_screen.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
class TransactionPinScreen extends StatefulWidget {
const TransactionPinScreen({super.key});
final Transfer transactionData;
const TransactionPinScreen({super.key, required this.transactionData});
@override
State<TransactionPinScreen> createState() => _TransactionPinScreen();
@@ -11,6 +18,38 @@ class TransactionPinScreen extends StatefulWidget {
class _TransactionPinScreen extends State<TransactionPinScreen> {
final List<String> _pin = [];
bool _loading = true;
@override
void initState() {
super.initState();
_checkIfTpinIsSet();
}
Future<void> _checkIfTpinIsSet() async {
setState(() => _loading = true);
try {
final authService = getIt<AuthService>();
final isSet = await authService.checkTpin();
if (!isSet && mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (_) => const TpinSetupPromptScreen(),
),
);
} else if (mounted) {
setState(() => _loading = false);
}
} catch (e) {
if (mounted) {
setState(() => _loading = false);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Failed to check TPIN status')),
);
}
}
}
void _onKeyPressed(String value) {
setState(() {
@@ -43,16 +82,13 @@ class _TransactionPinScreen extends State<TransactionPinScreen> {
Widget _buildKey(String label, {IconData? icon}) {
return Expanded(
child: InkWell(
onTap: () {
onTap: () async {
if (label == 'back') {
_onKeyPressed('back');
} else if (label == 'done') {
// Handle submit if needed
if (_pin.length == 6) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TransactionSuccessScreen()));
await sendTransaction();
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Please enter a 6-digit TPIN")),
@@ -90,6 +126,27 @@ class _TransactionPinScreen extends State<TransactionPinScreen> {
);
}
Future<void> sendTransaction() async {
final paymentService = getIt<PaymentService>();
final transfer = widget.transactionData;
transfer.tpin = _pin.join();
try {
final paymentResponse = paymentService.processQuickPayWithinBank(transfer);
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => PaymentAnimationScreen(paymentResponse: paymentResponse)
),
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(e.toString())),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -105,35 +162,23 @@ class _TransactionPinScreen extends State<TransactionPinScreen> {
style: TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
),
centerTitle: false,
actions: const [
Padding(
padding: EdgeInsets.only(right: 10.0),
child: CircleAvatar(
backgroundImage: AssetImage('assets/images/avatar.jpg'),
// Replace with your image
radius: 20,
),
),
],
),
body: Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Column(
children: [
const Spacer(),
const Text(
'Enter Your TPIN',
style: TextStyle(fontSize: 18),
padding: const EdgeInsets.only(bottom: 20.0),
child: Column(
children: [
const Spacer(),
const Text(
'Enter Your TPIN',
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 20),
_buildPinIndicators(),
const Spacer(),
_buildKeypad(),
],
),
),
const SizedBox(height: 20),
_buildPinIndicators(),
const Spacer(),
_buildKeypad(),
],
),
),
);
}
}
}

View File

@@ -7,16 +7,14 @@ import 'package:screenshot/screenshot.dart';
import '../../../app.dart';
class TransactionSuccessScreen extends StatefulWidget {
const TransactionSuccessScreen({super.key});
final String creditAccount;
const TransactionSuccessScreen({super.key, required this.creditAccount});
@override
State<TransactionSuccessScreen> createState() => _TransactionSuccessScreen();
}
class _TransactionSuccessScreen extends State<TransactionSuccessScreen> {
final String transactionDate = "18th March, 2025 04:30 PM";
final String referenceNumber = "TXN32131093012931993";
final ScreenshotController _screenshotController = ScreenshotController();
Future<void> _shareScreenshot() async {
@@ -35,6 +33,10 @@ class _TransactionSuccessScreen extends State<TransactionSuccessScreen> {
@override
Widget build(BuildContext context) {
final String transactionDate =
DateTime.now().toLocal().toString().split(' ')[0];
final String creditAccount = widget.creditAccount;
return Scaffold(
body: SafeArea(
child: Stack(
@@ -74,7 +76,7 @@ class _TransactionSuccessScreen extends State<TransactionSuccessScreen> {
),
const SizedBox(height: 16),
Text(
"Reference No: $referenceNumber",
"To Account Number: $creditAccount",
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
@@ -102,9 +104,9 @@ class _TransactionSuccessScreen extends State<TransactionSuccessScreen> {
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.white,
foregroundColor: Colors.blueAccent,
side: const BorderSide(color: Colors.black, width: 1),
elevation: 0
),
side:
const BorderSide(color: Colors.black, width: 1),
elevation: 0),
),
),
const SizedBox(width: 12),
@@ -115,7 +117,8 @@ class _TransactionSuccessScreen extends State<TransactionSuccessScreen> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const NavigationScaffold()));
builder: (context) =>
const NavigationScaffold()));
},
style: ElevatedButton.styleFrom(
shape: const StadiumBorder(),
@@ -135,5 +138,4 @@ class _TransactionSuccessScreen extends State<TransactionSuccessScreen> {
),
);
}
}
}

View File

@@ -6,7 +6,8 @@ import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../fund_transfer/screens/transaction_pin_screen.dart';
class QuickPayOutsideBankScreen extends StatefulWidget {
const QuickPayOutsideBankScreen({super.key});
final String debitAccount;
const QuickPayOutsideBankScreen({super.key, required this.debitAccount});
@override
State<QuickPayOutsideBankScreen> createState() =>
@@ -82,12 +83,12 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
child: ListView(
children: [
const SizedBox(height: 10),
const Row(
Row(
children: [
Text('Debit from:'),
const Text('Debit from:'),
Text(
'0300015678903456',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
widget.debitAccount,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
)
],
),
@@ -113,7 +114,7 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
validator: (value) {
if (value == null || value.isEmpty) {
return 'Account number is required';
} else if (value.length != 16) {
} else if (value.length != 11) {
return 'Enter a valid account number';
}
return null;
@@ -373,11 +374,14 @@ class _QuickPayOutsideBankScreen extends State<QuickPayOutsideBankScreen> {
SnackBar(
content: Text('Paying via $selectedMode...')),
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const TransactionPinScreen()));
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) =>
// const TransactionPinScreen(
// transactionData: {},
// transactionCode: 'PAYMENT',
// )));
}
},
)),

View File

@@ -5,7 +5,8 @@ import 'package:kmobile/features/quick_pay/screens/quick_pay_within_bank_screen.
import 'package:material_symbols_icons/material_symbols_icons.dart';
class QuickPayScreen extends StatefulWidget {
const QuickPayScreen({super.key});
final String debitAccount;
const QuickPayScreen({super.key, required this.debitAccount});
@override
State<QuickPayScreen> createState() => _QuickPayScreen();
@@ -52,20 +53,21 @@ class _QuickPayScreen extends State<QuickPayScreen> {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const QuickPayWithinBankScreen()));
builder: (context) => QuickPayWithinBankScreen(debitAccount: widget.debitAccount)));
},
),
const Divider(
height: 1,
),
QuickPayManagementTile(
disable: true,
icon: Symbols.output_circle,
label: 'Outside Bank',
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const QuickPayOutsideBankScreen()));
builder: (context) => QuickPayOutsideBankScreen(debitAccount: widget.debitAccount)));
},
),
const Divider(
@@ -81,12 +83,14 @@ class QuickPayManagementTile extends StatelessWidget {
final IconData icon;
final String label;
final VoidCallback onTap;
final bool disable;
const QuickPayManagementTile({
super.key,
required this.icon,
required this.label,
required this.onTap,
this.disable = false,
});
@override
@@ -96,6 +100,7 @@ class QuickPayManagementTile extends StatelessWidget {
title: Text(label),
trailing: const Icon(Symbols.arrow_right, size: 20),
onTap: onTap,
enabled: !disable,
);
}
}

View File

@@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutter_swipe_button/flutter_swipe_button.dart';
import 'package:kmobile/data/models/transfer.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../fund_transfer/screens/transaction_pin_screen.dart';
class QuickPayWithinBankScreen extends StatefulWidget {
const QuickPayWithinBankScreen({super.key});
final String debitAccount;
const QuickPayWithinBankScreen({super.key, required this.debitAccount});
@override
State<QuickPayWithinBankScreen> createState() => _QuickPayWithinBankScreen();
@@ -18,8 +20,8 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
final TextEditingController accountNumberController = TextEditingController();
final TextEditingController confirmAccountNumberController =
TextEditingController();
final TextEditingController nameController = TextEditingController();
final TextEditingController amountController = TextEditingController();
String? _selectedAccountType;
@override
Widget build(BuildContext context) {
@@ -59,14 +61,19 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
child: Column(
children: [
const SizedBox(height: 10),
const Row(
children: [
Text('Debit from:'),
Text(
'0300015678903456',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w500),
)
],
TextFormField(
decoration: const InputDecoration(
labelText: 'Debit Account Number',
border: OutlineInputBorder(),
isDense: true,
filled: true,
fillColor: Colors.white,
),
readOnly: true,
controller: TextEditingController(text: widget.debitAccount),
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
enabled: false,
),
const SizedBox(height: 20),
TextFormField(
@@ -90,21 +97,21 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
validator: (value) {
if (value == null || value.isEmpty) {
return 'Account number is required';
} else if (value.length != 16) {
} else if (value.length != 11) {
return 'Enter a valid account number';
}
return null;
},
),
const Align(
alignment: Alignment.topLeft,
child: Padding(
padding: EdgeInsets.only(left: 15.0, top: 5),
child: Text(
'Beneficiary Account Number',
style: TextStyle(color: Colors.black54),
),
)),
// const Align(
// alignment: Alignment.topLeft,
// child: Padding(
// padding: EdgeInsets.only(left: 15.0, top: 5),
// child: Text(
// 'Beneficiary Account Number',
// style: TextStyle(color: Colors.black54),
// ),
// )),
const SizedBox(height: 25),
TextFormField(
controller: confirmAccountNumberController,
@@ -135,9 +142,9 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
},
),
const SizedBox(height: 24),
TextFormField(
DropdownButtonFormField<String>(
decoration: const InputDecoration(
labelText: 'Name',
labelText: 'Beneficiary Account Type',
border: OutlineInputBorder(),
isDense: true,
filled: true,
@@ -149,25 +156,38 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
borderSide: BorderSide(color: Colors.black, width: 2),
),
),
controller: nameController,
keyboardType: TextInputType.name,
textInputAction: TextInputAction.next,
value: _selectedAccountType,
items: const [
DropdownMenuItem(
value: 'SB',
child: Text('Savings'),
),
DropdownMenuItem(
value: 'LN',
child: Text('Loan'),
),
],
onChanged: (value) {
setState(() {
_selectedAccountType = value;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Name is required';
return 'Please select account type';
}
return null;
},
),
const Align(
alignment: Alignment.topLeft,
child: Padding(
padding: EdgeInsets.only(left: 15.0, top: 5),
child: Text(
'Beneficiary Name',
style: TextStyle(color: Colors.black54),
),
)),
// const Align(
// alignment: Alignment.topLeft,
// child: Padding(
// padding: EdgeInsets.only(left: 15.0, top: 5),
// child: Text(
// 'Beneficiary Account Type',
// style: TextStyle(color: Colors.black54),
// ),
// )),
const SizedBox(height: 25),
TextFormField(
decoration: const InputDecoration(
@@ -205,29 +225,48 @@ class _QuickPayWithinBankScreen extends State<QuickPayWithinBankScreen> {
Icons.arrow_forward,
color: Colors.white,
),
activeThumbColor: Colors.blue[900],
activeTrackColor: Colors.blue.shade100,
activeThumbColor: Theme.of(context).primaryColor,
activeTrackColor:
Theme.of(context).colorScheme.secondary.withAlpha(100),
borderRadius: BorderRadius.circular(30),
height: 56,
child: const Text(
"Swipe to Pay",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
style: TextStyle(fontSize: 16),
),
onSwipe: () {
if (_formKey.currentState!.validate()) {
// Perform payment logic
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Payment...')),
);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const TransactionPinScreen()));
builder: (context) => TransactionPinScreen(
transactionData: Transfer(
fromAccount: widget.debitAccount,
toAccount: accountNumberController.text,
toAccountType: _selectedAccountType!,
amount: amountController.text,
),
)));
}
},
),
),
// SliderButton(
// action: () async {
// ///Do something here OnSlide
// return true;
// },
// label: const Text(
// "Slide to pay",
// style: TextStyle(
// color: Color(0xff4a4a4a),
// fontWeight: FontWeight.w500,
// fontSize: 17),
// ),
// icon: Icon(Symbols.arrow_forward,
// color: Theme.of(context).primaryColor, weight: 200),
// )
],
),
),

View File

@@ -1,174 +0,0 @@
{
"@@locale": "bn",
"app_title": "আইপিকেএস ম্যাপ",
"ipks": "আইপিকেএস",
"fingerprint_reason": "অ্যাপ শুরু করতে প্রমাণীকরণ করুন",
"@fingerprint_reason": {},
"m_pin_entry_prompt": "আপনার এমপিআইএন লিখুন",
"register_prompt": "রেজিস্টার?",
"try_another_way": "অন্য উপায় চেষ্টা করুন",
"username": "ব্যবহারকারীর নাম",
"password": "পাসওয়ার্ড",
"login": "লগইন",
"register": "রেজিস্টার",
"mobile_number": "মোবাইল নম্বর",
"aadhaar_number": "আধার নম্বর",
"date_of_birth": "জন্ম তারিখ",
"pacs_id": "প্যাকস আইডি",
"view_full_kyc": "পূর্ণ কেওয়াইসি দেখুন",
"account_summary": "হিসাবের সংক্ষিপ্ত সমূহ",
"account_statement": "হিসাবের বিবৃতি",
"customer_details": "গ্রাহকের বিবরণ",
"home": "হোম",
"details": "বিস্তারিত",
"statement": "বিবৃতি",
"no_of_active_accounts": "সক্রিয় হিসাবের সংখ্যা",
"pan_number": "প্যান নম্বর",
"mirror_acct_no": "মিরর হিসাব নম্বর",
"cif": "সিআইএফ",
"product_name": "পণ্যের নাম",
"acct_opening_dt": "হিসাব খোলার তারিখ",
"account_status": "হিসাবের অবস্থা",
"available_bal": "উপলব্ধ ব্যালেন্স",
"interest_rate": "সুদের হার",
"acct_type": "হিসাবের ধরন",
"acct_no": "হিসাব নম্বর",
"date_range": "তারিখের পরিসীমা",
"amount_range": "পরিমাণের পরিসীমা",
"save": "সংরক্ষণ করুন",
"min": "সর্বনিম্ন",
"max": "সর্বোচ্চ",
"min_amt": "ন্যূনতম পরিমাণ",
"max_amt": "সর্বোচ্চ পরিমাণ",
"customer_no_search_message": "আপনার আইপিকেএস গ্রাহক নম্বর লিখুন দয়া করে। আপনার গ্রাহক নম্বরটি আইপিকেএস ডেটাবেসে আপনার বিবরণ খুঁজে পেতে ব্যবহৃত হয়।",
"search": "অনুসন্ধান",
"details_verification_message": "দয়া করে আপনার বিবরণ যাচাই করুন।",
"customer_no": "গ্রাহক নম্বর",
"name": "নাম",
"email": "ইমেল",
"pacs_name": "প্যাকস নাম",
"not_you_prompt": "না আপনি?",
"next": "পরবর্তী",
"otp_verification_message": "একটি ওটিপি মেসেজ আপনার নিবন্ধিত মোবাইল নম্বরে {masked_phone_number} প্রেরিত হয়েছে। এটি যাচাই করতে নিচে লিখুন।",
"@otp_verification_message": {
"placeholders": {
"masked_phone_number": {
"type": "String"
}
}
},
"otp": "ওটিপি",
"resend_otp_prompt": "পুনরায় ওটিপি প্রেরণ করুন?",
"new_credentials_message": "দয়া করে এমঅ্যাপ এর জন্য আপনার নতুন ব্যবহারকারী নাম এবং পাসওয়ার্ড তৈরি করুন",
"repeat_password": "পাসওয়ার্ড পুনরায় লিখুন",
"registration_success_display": "রেজিস্ট্রেশন সফল",
"goto_login_prompt": "লগইন পৃষ্ঠায় যান",
"marital_status": "বৈবাহিক অবস্থা",
"gender": "লিঙ্গ",
"address": "ঠিকানা",
"or": "অথবা",
"district": "জেলা",
"state": "রাজ্য",
"city": "শহর",
"pin_code": "পিন কোড",
"linked_sb_no": "সংযুক্ত এসবি হিসাব নম্বর",
"farmer_type": "কৃষকের ধরণ",
"guardian_name": "অভিভাবকের নাম",
"religion": "ধর্ম",
"caste": "জাতি",
"not_available": "পাওয়া যায়নি",
"no_accounts_found": "কোনও হিসাব পাওয়া যায়নি",
"account_holder": "হিসাব ধারক",
"customer_name": "গ্রাহকের নাম",
"sanction_amount": "অনুমোদিত পরিমাণ",
"sanction_date": "অনুমোদনের তারিখ",
"disbursed_amount": "বিতরণ করা পরিমাণ",
"interest_outstanding": "সুদের বকেয়া",
"principal_outstanding": "প্রধান বকেয়া",
"interest_paid": "সুদ প্রদান",
"principal_paid": "প্রধান প্রদান",
"emi_amount": "ইএমআই পরিমাণ",
"due_date": "নির্ধারিত তারিখ",
"last_repayment_date": "সর্বশেষ পরিশোধের তারিখ",
"interest_description": "সুদের বিবরণ",
"total_interest_outstanding": "মোট সুদের বকেয়া",
"total_interest_accrued": "মোট সুদ উত্থাপন",
"total_interest_paid": "মোট সুদ প্রদান",
"loan_type": "ঋণের ধরণ",
"old_account_number": "পুরাতন হিসাব নম্বর",
"penal_interest_rate": "জরিমানা সুদের হার",
"cbs_account_number": "সিবিএস হিসাব নম্বর",
"nominee_name": "নোমিনির নাম",
"open_date": "খোলার তারিখ",
"interest_available": "সুদ উপলব্ধ",
"interest_from_date": "সুদ শুরুর তারিখ",
"interest_to_date": "সুদ শেষের তারিখ",
"term_value": "মেয়াদ মান",
"maturity_value": "পরিপ্রেক্ষিত মূল্য",
"maturity_date": "পরিপ্রেক্ষিত তারিখ",
"term_start_date": "মেয়াদ শুরুর তারিখ",
"term_end_date": "মেয়াদ শেষের তারিখ",
"interest_projected": "সুদ প্রকাশ্য",
"interest_capitalized": "সুদ পুঁজিবদ্ধ",
"no_of_installments_paid": "পরিশোধিত কিস্তির সংখ্যা",
"next_due_date": "পরবর্তী মেয়াদ শেষের তারিখ",
"rd_penal_count": "আরডি জরিমানা গণনা",
"hold_value": "ধার মান",
"term_length": "মেয়াদের দৈর্ঘ্য",
"interest_repayment_method": "সুদ পরিশোধের পদ্ধতি",
"mode_of_account": "হিসাবের মোড",
"secondary_cif_number": "দ্বিতীয় সিআইএফ নম্বর",
"secondary_cif_name": "দ্বিতীয় সিআইএফ নাম",
"no_statements_found": "কোনও বিবৃতি পাওয়া যায়নি",
"date_range_exceeds_10_days": "10 দিনের বেশি সময়কাল নির্বাচন করুন না",
"last_10_transactions": "সর্বশেষ 10 লেনদেন প্রদর্শিত হচ্ছে",
"select_account_type": "হিসাবের ধরন নির্বাচন করুন",
"select_account_number": "হিসাব নম্বর নির্বাচন করুন",
"select_date_range": "তারিখের পরিসীমা নির্বাচন করুন",
"please_wait": "অপেক্ষা করুন...",
"preferences": "পছন্দসমূহ",
"everforest": "এভারফরেস্ট",
"rosy": "রোসি",
"skypeia": "স্কাইপিয়া",
"marigold": "গাঁদা ফুল",
"select_language": "ভাষা নির্বাচন করুন",
"english": "ইংরেজি",
"hindi": "হিন্দি",
"bengali": "বাংলা",
"malayalam": "মালায়ালম",
"dark_theme": "গা থিম",
"color_theme": "রঙের থিম",
"language": "ভাষা",
"deposit": "জমা",
"loan": "ঋণ",
"export": "রপ্তানি",
"invalid_credentials": "অবৈধ পরিচয়পত্র",
"logout": "লগআউট",
"backend_ip": "ব্যাকএন্ড আইপি",
"bank_branch_no": "ব্যাংক শাখা নম্বর",
"ifsc_code": "আইএফএসসি কোড",
"bank_branch_name": "ব্যাংক শাখার নাম",
"search_customer_paragraph": "অনুগ্রহ করে আপনার আইপিকেএস গ্রাহক নম্বর দিন। আপনার গ্রাহক নম্বর আইপিকেএস ডাটাবেসে আপনার বিবরণ খুঁজতে ব্যবহৃত হয়",
"invalid_customer_no": "অবৈধ গ্রাহক নম্বর",
"searching": "অনুসন্ধান করা হচ্ছে",
"invalid_otp": "অবৈধ ওটিপি",
"register_customer_message": "অনুগ্রহ করে এমঅ্যাপের জন্য আপনার নতুন ব্যবহারকারী নাম এবং পাসওয়ার্ড প্রবেশ করান",
"username_criteria": "৬টি অক্ষর, শুধুমাত্র অক্ষর এবং সংখ্যা",
"password_criteria": "একটি বড় হাতের অক্ষর, একটি ছোট হাতের অক্ষর, একটি সংখ্যা (৮টি অক্ষর)",
"passowrd_mismatch_msg": "পাসওয়ার্ডগুলি মেলে না",
"password_confirm": "পাসওয়ার্ড নিশ্চিত করুন",
"registration_successful": "নিবন্ধন সফল হয়েছে",
"registration_successful_message": "অনুগ্রহ করে আপনার নতুন পরিচয়পত্র দিয়ে লগইন করুন",
"goto_login": "লগইন পৃষ্ঠায় যান",
"socket_exception": "অনুগ্রহ করে আপনার ইন্টারনেট সংযোগ পরীক্ষা করুন",
"mpin_setup": "-সংখ্যার এমপিন সেট করুন",
"mpin_confirm": "আপনার এমপিন নিশ্চিত করুন",
"storage_permission_denied": "স্টোরেজ অনুমতি অস্বীকৃত হয়েছে",
"forgot_password": "পাসওয়ার্ড ভুলে গেছেন",
"forgot_password_search_user": "আপনার সিআইএফ নম্বর লিখুন",
"forgot_password_success": "রিসেট সফল হয়েছে",
"forgot_password_create": "আপনার নতুন পাসওয়ার্ড লিখুন",
"new_password": "নতুন পাসওয়ার্ড",
"security": "নিরাপত্তা",
"verify_mpin": "আপনার এমপিন যাচাই করুন"
}

View File

@@ -1,174 +0,0 @@
{
"@@locale": "en",
"app_title": "mApp",
"ipks": "IPKS",
"fingerprint_reason": "Authenticate to start app",
"@fingerprint_reason": {},
"m_pin_entry_prompt": "Enter your mPIN",
"register_prompt": "Register?",
"try_another_way": "Try another way",
"username": "Username",
"password": "Password",
"login": "Login",
"register": "Register",
"mobile_number": "Mobile Number",
"aadhaar_number": "Aadhaar Number",
"date_of_birth": "Date of Birth",
"pacs_id": "PACS id",
"view_full_kyc": "View full KYC",
"account_summary": "Account Summary",
"account_statement": "Account Statement",
"customer_details": "Customer Details",
"home": "Home",
"details": "Details",
"statement": "Statement",
"no_of_active_accounts": "Number of active accounts",
"pan_number": "PAN Number",
"mirror_acct_no": "Mirror Account Number",
"cif": "CIF",
"product_name": "Product Name",
"acct_opening_dt": "Account Opening Date",
"account_status": "Account Status",
"available_bal": "Available Balance",
"interest_rate": "Interest Rate",
"acct_type": "Account Type",
"acct_no": "Account Number",
"date_range": "Date Range",
"amount_range": "Amount Range",
"save": "Save",
"min": "Min",
"max": "Max",
"min_amt": "Min Amount",
"max_amt": "Max Amount",
"customer_no_search_message": "Please enter your IPKS customer number. Your customer number is used to search your details in the IPKS database.",
"search": "Search",
"details_verification_message": "Please verify your details.",
"customer_no": "Customer Number",
"name": "Name",
"email": "Email",
"pacs_name": "PACS Name",
"not_you_prompt": "Not You?",
"next": "Next",
"otp_verification_message": "An OTP message has been sent to your registered mobile number {masked_phone_number}. Enter it below to verify your phone number.",
"@otp_verification_message": {
"placeholders": {
"masked_phone_number": {
"type": "String"
}
}
},
"otp": "OTP",
"resend_otp_prompt": "Resend OTP?",
"new_credentials_message": "Please create your new username and password for mApp",
"repeat_password": "Repeat Password",
"registration_success_display": "Registration success",
"goto_login_prompt": "Go to Login Page",
"marital_status": "Marital Status",
"gender": "Gender",
"address": "Address",
"or": "OR",
"district": "District",
"state": "State",
"city": "City",
"pin_code": "Pin code",
"linked_sb_no": "Linked SB Account Number",
"farmer_type": "Farmer Type",
"guardian_name": "Guardian Name",
"religion": "Religion",
"caste": "Caste",
"not_available": "Not Available",
"no_accounts_found": "No Accounts Found",
"account_holder": "Account Holder",
"customer_name": "Customer Name",
"sanction_amount": "Sanction Amount",
"sanction_date": "Sanction Date",
"disbursed_amount": "Disbursed Amount",
"interest_outstanding": "Interest Outstanding",
"principal_outstanding": "Principal Outstanding",
"interest_paid": "Interest Paid",
"principal_paid": "Principal Paid",
"emi_amount": "EMI Amount",
"due_date": "Due Date",
"last_repayment_date": "Last Repayment Date",
"interest_description": "Interest Description",
"total_interest_outstanding": "Total Interest Outstanding",
"total_interest_accrued": "Total Interest Accrued",
"total_interest_paid": "Total Interest Paid",
"loan_type": "Loan Type",
"old_account_number": "Old Account Number",
"penal_interest_rate": "Penal Interest Rate",
"cbs_account_number": "CBS Account Number",
"nominee_name": "Nominee Name",
"open_date": "Open Date",
"interest_available": "Interest Available",
"interest_from_date": "Interest From Date",
"interest_to_date": "Interest To Date",
"term_value": "Term Value",
"maturity_value": "Maturity Value",
"maturity_date": "Maturity Date",
"term_start_date": "Term Start Date",
"term_end_date": "Term End Date",
"interest_projected": "Interest Projected",
"interest_capitalized": "Interest Capitalized",
"no_of_installments_paid": "Number of Installments Paid",
"next_due_date": "Next Due Date",
"rd_penal_count": "RD Penal Count",
"hold_value": "Hold Value",
"term_length": "Term Length",
"interest_repayment_method": "Interest Repayment Method",
"mode_of_account": "Mode of Account",
"secondary_cif_number": "Secondary CIF Number",
"secondary_cif_name": "Secondary CIF Name",
"no_statements_found": "No Statements Found",
"date_range_exceeds_10_days": "Select duration of 10 days or less",
"last_10_transactions": "Displaying last 10 transactions",
"select_account_type": "Select Account Type",
"select_account_number": "Select Account Number",
"select_date_range": "Select Date Range",
"please_wait": "Please wait...",
"preferences": "Preferences",
"everforest": "Everforest",
"rosy": "Rosy",
"skypeia": "Skypeia",
"marigold": "Marigold",
"select_language": "Select Language",
"english": "English",
"hindi": "Hindi",
"bengali": "Bengali",
"malayalam": "Malayalam",
"dark_theme": "Dark Theme",
"color_theme": "Color Theme",
"language": "Language",
"deposit": "Deposit",
"loan": "Loan",
"export": "Export",
"invalid_credentials": "Invalid credentials",
"logout": "Logout",
"backend_ip": "Backend IP",
"bank_branch_no": "Bank Branch Number",
"ifsc_code": "IFSC code",
"bank_branch_name": "Bank Branch Name",
"search_customer_paragraph": "Please enter your IPKS customer number. Your customer number is used to search your details in the IPKS database",
"invalid_customer_no": "Invalid customer number",
"searching": "Searching",
"invalid_otp": "Invalid OTP",
"register_customer_message": "Please enter your new username and password for mApp",
"username_criteria": "6 characters, only letters and digits",
"password_criteria": "one uppercase, one lowercase, one number (8 characters)",
"passowrd_mismatch_msg": "The passwords doesn't match",
"password_confirm": "Confirm Password",
"registration_successful": "Registration Successful",
"registration_successful_message": "Please login with your new credentails",
"goto_login": "Got to Login Page",
"socket_exception": "Please check your internet connection",
"mpin_setup": "Setup 4-digit MPIN",
"mpin_confirm": "Confirm your MPIN",
"storage_permission_denied": "Storage Persmission denied",
"forgot_password": "Forgot Password",
"forgot_password_search_user": "Enter your CIF number",
"forgot_password_success": "Reset Successful",
"forgot_password_create": "Enter your new password",
"new_password": "New Password",
"security": "Security",
"verify_mpin": "Verify your MPIN"
}

View File

@@ -1,174 +0,0 @@
{
"@@locale": "hi",
"app_title": "आईपीकेएस ऐप",
"ipks": "आईपीकेएस",
"fingerprint_reason": "ऐप शुरू करने के लिए प्रमाणित करें",
"@fingerprint_reason": {},
"m_pin_entry_prompt": "अपना एम पिन दर्ज करें",
"register_prompt": "रजिस्टर करें?",
"try_another_way": "किसी अन्य तरीके की कोशिश करें",
"username": "उपयोगकर्ता नाम",
"password": "पासवर्ड",
"login": "लॉगिन",
"register": "रजिस्टर",
"mobile_number": "मोबाइल नंबर",
"aadhaar_number": "आधार नंबर",
"date_of_birth": "जन्म तिथि",
"pacs_id": "पैक्स आईडी",
"view_full_kyc": "पूरा केवाईसी देखें",
"account_summary": "हिसाब सारांश",
"account_statement": "हिसाब की बयान",
"customer_details": "ग्राहक विवरण",
"home": "होम",
"details": "विवरण",
"statement": "बयान",
"no_of_active_accounts": "सक्रिय खातों की संख्या",
"pan_number": "पैन नंबर",
"mirror_acct_no": "मिरर खाता नंबर",
"cif": "सीआईएफ",
"product_name": "उत्पाद का नाम",
"acct_opening_dt": "खाता खोलने की तिथि",
"account_status": "खाता की स्थिति",
"available_bal": "उपलब्ध शेष",
"interest_rate": "ब्याज दर",
"acct_type": "खाते का प्रकार",
"acct_no": "खाता संख्या",
"date_range": "तिथि सीमा",
"amount_range": "राशि सीमा",
"save": "सहेजें",
"min": "न्यूनतम",
"max": "अधिकतम",
"min_amt": "न्यूनतम राशि",
"max_amt": "अधिकतम राशि",
"customer_no_search_message": "कृपया अपना आईपीकेएस ग्राहक संख्या दर्ज करें। आपका ग्राहक संख्या आईपीकेएस डेटाबेस में आपका विवरण खोजने के लिए उपयोग किया जाता है।",
"search": "खोज",
"details_verification_message": "कृपया अपना विवरण सत्यापित करें।",
"customer_no": "ग्राहक संख्या",
"name": "नाम",
"email": "ईमेल",
"pacs_name": "पैक्स का नाम",
"not_you_prompt": "तुम नहीं?",
"next": "अगला",
"otp_verification_message": "एक ओटीपी संदेश आपके पंजीकृत मोबाइल नंबर {masked_phone_number} पर भेजा गया है। इसे नीचे दिए गए बॉक्स में दर्ज करके अपने फोन नंबर को सत्यापित करें।",
"@otp_verification_message": {
"placeholders": {
"masked_phone_number": {
"type": "String"
}
}
},
"otp": "ओटीपी",
"resend_otp_prompt": "ओटीपी पुनः भेजें?",
"new_credentials_message": "कृपया एमएपी के लिए अपना नया उपयोगकर्ता नाम और पासवर्ड बनाएं",
"repeat_password": "पासवर्ड दोहराएं",
"registration_success_display": "रजिस्ट्रेशन सफलता",
"goto_login_prompt": "लॉगिन पेज पर जाएं",
"marital_status": "वैवाहिक स्थिति",
"gender": "लिंग",
"address": "पता",
"or": "या",
"district": "जिला",
"state": "राज्य",
"city": "शहर",
"pin_code": "पिन कोड",
"linked_sb_no": "लिंक्ड एसबी खाता नंबर",
"farmer_type": "किसान प्रकार",
"guardian_name": "संरक्षक का नाम",
"religion": "धर्म",
"caste": "जाति",
"not_available": "उपलब्ध नहीं है",
"no_accounts_found": "कोई खाते नहीं मिला",
"account_holder": "खाता धारक",
"customer_name": "ग्राहक का नाम",
"sanction_amount": "स्वीकृत राशि",
"sanction_date": "स्वीकृति तिथि",
"disbursed_amount": "वितरित राशि",
"interest_outstanding": "बकाया ब्याज",
"principal_outstanding": "मुख्य बकाया",
"interest_paid": "ब्याज दिया",
"principal_paid": "मुख्य दिया",
"emi_amount": "ईएमआई राशि",
"due_date": "निर्धारित तिथि",
"last_repayment_date": "अंतिम प्रतिपूर्ति तिथि",
"interest_description": "ब्याज विवरण",
"total_interest_outstanding": "कुल बकाया ब्याज",
"total_interest_accrued": "कुल ब्याज बनाया गया",
"total_interest_paid": "कुल ब्याज दिया",
"loan_type": "ऋण प्रकार",
"old_account_number": "पुराना खाता नंबर",
"penal_interest_rate": "जुर्माना ब्याज दर",
"cbs_account_number": "सीबीएस खाता नंबर",
"nominee_name": "नामांकित नाम",
"open_date": "खोलने की तारीख",
"interest_available": "ब्याज उपलब्ध",
"interest_from_date": "ब्याज तिथि से",
"interest_to_date": "ब्याज तारीख तक",
"term_value": "मुद्रा मूल्य",
"maturity_value": "परिपक्ष्य मूल्य",
"maturity_date": "परिपक्ष्य तिथि",
"term_start_date": "अवधि प्रारंभ तिथि",
"term_end_date": "अवधि समाप्ति तिथि",
"interest_projected": "ब्याज परियोजित",
"interest_capitalized": "ब्याज पुँजीबद्ध",
"no_of_installments_paid": "भुगतान की गई किस्तों की संख्या",
"next_due_date": "अगली निर्धारित तिथि",
"rd_penal_count": "आरडी जुर्माना गणना",
"hold_value": "होल्ड मूल्य",
"term_length": "अवधि लंबाई",
"interest_repayment_method": "ब्याज प्रतिपूर्ति विधि",
"mode_of_account": "खाते का मोड",
"secondary_cif_number": "द्वितीय सीआईएफ नंबर",
"secondary_cif_name": "द्वितीय सीआईएफ नाम",
"no_statements_found": "कोई बयान नहीं मिला",
"date_range_exceeds_10_days": "10 दिनों से अधिक अवधि का चयन नहीं करें",
"last_10_transactions": "अंतिम 10 लेनदेन दिखा रहा है",
"select_account_type": "खाता प्रकार चुनें",
"select_account_number": "खाता संख्या चुनें",
"select_date_range": "तिथि सीमा चुनें",
"please_wait": "कृपया प्रतीक्षा करें...",
"preferences": "प्राथमिकताएँ",
"everforest": "एवरफॉरेस्ट",
"rosy": "रोसी",
"skypeia": "स्काइपिया",
"marigold": "मैरीगोल्ड",
"select_language": "भाषा चुनें",
"english": "अंग्रेज़ी",
"hindi": "हिंदी",
"bengali": "बंगाली",
"malayalam": "मलयालम",
"dark_theme": "डार्क थीम",
"color_theme": "रंग थीम",
"language": "भाषा",
"deposit": "जमा",
"loan": "ऋण",
"export": "निर्यात",
"invalid_credentials": "अमान्य प्रमाण पत्र",
"logout": "लॉगआउट",
"backend_ip": "बैकएंड आईपी",
"bank_branch_no": "बैंक शाखा संख्या",
"ifsc_code": "आईएफएससी कोड",
"bank_branch_name": "बैंक शाखा का नाम",
"search_customer_paragraph": "कृपया अपना आईपीकेएस ग्राहक नंबर दर्ज करें। आपका ग्राहक नंबर आईपीकेएस डेटाबेस में आपके विवरण खोजने के लिए उपयोग किया जाता है",
"invalid_customer_no": "अमान्य ग्राहक संख्या",
"searching": "खोज रहे हैं",
"invalid_otp": "अमान्य ओटीपी",
"register_customer_message": "कृपया एमऐप के लिए अपना नया उपयोगकर्ता नाम और पासवर्ड दर्ज करें",
"username_criteria": "6 अक्षर, केवल अक्षर और अंक",
"password_criteria": "एक अपरकेस, एक लोअरकेस, एक अंक (8 अक्षर)",
"passowrd_mismatch_msg": "पासवर्ड मेल नहीं खाते",
"password_confirm": "पासवर्ड की पुष्टि करें",
"registration_successful": "पंजीकरण सफल",
"registration_successful_message": "कृपया अपने नए प्रमाण पत्रों से लॉगिन करें",
"goto_login": "लॉगिन पेज पर जाएं",
"socket_exception": "कृपया अपना इंटरनेट कनेक्शन जांचें",
"mpin_setup": "4-अंकों का एम-पिन सेट करें",
"mpin_confirm": "अपने एम-पिन की पुष्टि करें",
"storage_permission_denied": "स्टोरेज अनुमति अस्वीकृत",
"forgot_password": "पासवर्ड भूल गए",
"forgot_password_search_user": "अपना सीआईएफ नंबर दर्ज करें",
"forgot_password_success": "रीसेट सफल",
"forgot_password_create": "अपना नया पासवर्ड दर्ज करें",
"new_password": "नया पासवर्ड",
"security": "सुरक्षा",
"verify_mpin": "अपना एम-पिन सत्यापित करें"
}

View File

@@ -0,0 +1,127 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_en.dart';
// ignore_for_file: type=lint
/// Callers can lookup localized strings with an instance of AppLocalizations
/// returned by `AppLocalizations.of(context)`.
///
/// Applications need to include `AppLocalizations.delegate()` in their app's
/// `localizationDelegates` list, and the locales they support in the app's
/// `supportedLocales` list. For example:
///
/// ```dart
/// import 'l10n/app_localizations.dart';
///
/// return MaterialApp(
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
/// supportedLocales: AppLocalizations.supportedLocales,
/// home: MyApplicationHome(),
/// );
/// ```
///
/// ## Update pubspec.yaml
///
/// Please make sure to update your pubspec.yaml to include the following
/// packages:
///
/// ```yaml
/// dependencies:
/// # Internationalization support.
/// flutter_localizations:
/// sdk: flutter
/// intl: any # Use the pinned version from flutter_localizations
///
/// # Rest of dependencies
/// ```
///
/// ## iOS Applications
///
/// iOS applications define key application metadata, including supported
/// locales, in an Info.plist file that is built into the application bundle.
/// To configure the locales supported by your app, youll need to edit this
/// file.
///
/// First, open your projects ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// projects Runner folder.
///
/// Next, select the Information Property List item, select Add Item from the
/// Editor menu, then select Localizations from the pop-up menu.
///
/// Select and expand the newly-created Localizations item then, for each
/// locale your application supports, add a new item and select the locale
/// you wish to add from the pop-up menu in the Value field. This list should
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
static AppLocalizations? of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
///
/// Returns a list of localizations delegates containing this delegate along with
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
/// and GlobalWidgetsLocalizations.delegate.
///
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[Locale('en')];
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) =>
<String>['en'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'en':
return AppLocalizationsEn();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.');
}

View File

@@ -0,0 +1,10 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
}

View File

@@ -1,174 +0,0 @@
{
"@@locale": "ml",
"app_title": "ഐപികെഎസ് ആപ്പ്",
"ipks": "ഐപികെഎസ്",
"fingerprint_reason": "ആപ്പ് ആരംഭിക്കുകയാണ് പ്രമാണിക്കുക",
"@fingerprint_reason": {},
"m_pin_entry_prompt": "നിങ്ങളുടെ എം പിൻ നൽകുക",
"register_prompt": "രജിസ്റ്റർ ചെയ്യുക?",
"try_another_way": "മറ്റൊരു വഴി പരീക്ഷിക്കുക",
"username": "ഉപയോക്തൃനാമം",
"password": "പാസ്‌വേഡ്",
"login": "ലോഗിൻ",
"register": "രജിസ്റ്റർ",
"mobile_number": "മൊബൈൽ നമ്പർ",
"aadhaar_number": "ആധാർ നമ്പർ",
"date_of_birth": "ജനന തീയതി",
"pacs_id": "പക്സ് ഐഡി",
"view_full_kyc": "പൂർണ്ണമായി KYC കാണുക",
"account_summary": "അക്കൗണ്ട് സംഗ്രഹം",
"account_statement": "അക്കൗണ്ട് സ്റ്റേറ്റ്മെന്റ്",
"customer_details": "ഗ്രാഹക വിവരങ്ങൾ",
"home": "ഹോം",
"details": "വിശദങ്ങൾ",
"statement": "സ്റ്റേറ്റ്മെന്റ്",
"no_of_active_accounts": "സജീവ അക്കൗണ്ടുകൾക്കായി നിലവിലെ അക്കൗണ്ടുകൾ",
"pan_number": "പാൻ നമ്പർ",
"mirror_acct_no": "മിറർ അക്കൗണ്ട് നമ്പർ",
"cif": "സിഐഎഫ്",
"product_name": "ഉൽപ്പന്നത്തിന്റെ പേര്",
"acct_opening_dt": "അക്കൗണ്ട് തുറക്കിയ തീയതി",
"account_status": "അക്കൗണ്ട് സ്റ്റാറ്റസ്",
"available_bal": "ലഭ്യമായ ബാലൻസ്",
"interest_rate": "ബായാജ് വരുമാനം",
"acct_type": "അക്കൗണ്ട് തരം",
"acct_no": "അക്കൗണ്ട് നമ്പർ",
"date_range": "തീയതി ശ്രേണി",
"amount_range": "രൂപ ശ്രേണി",
"save": "സേവ്",
"min": "അതിന്റെ",
"max": "പരമാവധി",
"min_amt": "അതിന്റെ തിരഞ്ഞെടുക്കണം",
"max_amt": "പരമാവധി തിരഞ്ഞെടുക്കണം",
"customer_no_search_message": "ദയവായി നിങ്ങളുടെ ഐപികെഎസ് ഗ്രാഹക നമ്പർ നൽകുക. ആപ്പ് ഡാറ്റാബേസിൽ നിങ്ങളുടെ വിവരങ്ങൾ തിരയുന്നതിനായി നിങ്ങളുടെ ഗ്രാഹക നമ്പർ ഉപയോഗിക്കുന്നു.",
"search": "തിരയുക",
"details_verification_message": "ദയവായി നിങ്ങളുടെ വിശദങ്ങൾ പരിശോധിക്കുക.",
"customer_no": "ഗ്രാഹക നമ്പർ",
"name": "പേര്",
"email": "ഇമെയിൽ",
"pacs_name": "പക്സ് പേര്",
"not_you_prompt": "നിന്ന് അല്ല?",
"next": "അടുത്തത്",
"otp_verification_message": "ഒരു OTP സന്ദേശം നിങ്ങളുടെ രജിസ്റ്റർ ചെയ്യപ്പെട്ട മൊബൈൽ നമ്പറിലേക്ക് {masked_phone_number} അയച്ചിരിക്കുന്നു. നിങ്ങൾക്ക് ഫോൺ നമ്പറിനെ പരിശോധിക്കാൻ അതിനു താഴെ നൽകുന്ന ബോക്സിൽ അതിൽ നൽകുക.",
"@otp_verification_message": {
"placeholders": {
"masked_phone_number": {
"type": "String"
}
}
},
"otp": "ഓടിപി",
"resend_otp_prompt": "OTP പുനഃക്രമീകരിക്കുക?",
"new_credentials_message": "ഐപികെഎസ് ആപ്പിനായി നിങ്ങളുടെ പുതിയ ഉപയോക്തൃനാമം മറികടക്കുക",
"repeat_password": "പാസ്വേഡ് പുനരാക്ഷരിക്കുക",
"registration_success_display": "രജിസ്ട്രേഷൻ വിജയം",
"goto_login_prompt": "ലോഗിൻ പേജിലേക്ക് പോകുക",
"marital_status": "വിവാഹിത സ്ഥിതി",
"gender": "ലിംഗം",
"address": "വിലാസം",
"or": "അഥവാ",
"district": "ജില്ല",
"state": "സംസ്ഥാനം",
"city": "നഗരം",
"pin_code": "പിൻ കോഡ്",
"linked_sb_no": "ലിങ്ക്ഡ് എസ്‌ബി അക്കൗണ്ട് നമ്പർ",
"farmer_type": "കൃഷിക്കാർ തരം",
"guardian_name": "ഗാർഡിയൻ പേര്",
"religion": "മതം",
"caste": "ജാതി",
"not_available": "ലഭ്യമല്ല",
"no_accounts_found": "അക്കൗണ്ടുകൾ കണ്ടെത്തിയില്ല",
"account_holder": "അക്കൗണ്ട് ഹോൾഡർ",
"customer_name": "ഗ്രാഹകനാമം",
"sanction_amount": "അനുവദിച്ച തുക",
"sanction_date": "അനുവദിച്ച തീയതി",
"disbursed_amount": "പിന്നീട് പിഴച്ച തുക",
"interest_outstanding": "ബായാജ് അടുപ്പുകൾ",
"principal_outstanding": "പ്രധാന അടുപ്പ്",
"interest_paid": "ബായാജ് ചെലവ്",
"principal_paid": "പ്രധാന ചെലവ്",
"emi_amount": "EMI തുക",
"due_date": "നിർബന്ധമായ തീയതി",
"last_repayment_date": "അവസാന തുക തീയതി",
"interest_description": "ബായാജ് വിവരണം",
"total_interest_outstanding": "മൊത്ത ബായാജ് അടുപ്പുകൾ",
"total_interest_accrued": "മൊത്ത ബായാജ് അക്ക്രൂട്ട്",
"total_interest_paid": "മൊത്ത ബായാജ് ചെലവ്",
"loan_type": "വായ്പ തരം",
"old_account_number": "പഴയ അക്കൗണ്ട് നമ്പർ",
"penal_interest_rate": "ശിക്ഷാ ബായാജ് വരുമാനം",
"cbs_account_number": "സിബിഎസ് അക്കൗണ്ട് നമ്പർ",
"nominee_name": "നോമിനീ പേര്",
"open_date": "തുറന്ന തീയതി",
"interest_available": "ബായാജ് ലഭ്യമാണ്",
"interest_from_date": "ബായാജ് തീയതി മുതൽ",
"interest_to_date": "ബായാജ് തീയതി വരെ",
"term_value": "അവധി മൂല്യം",
"maturity_value": "പരിപാലന മൂല്യം",
"maturity_date": "പരിപാലന തീയതി",
"term_start_date": "അവധി തുടക്കം തീയതി",
"term_end_date": "അവധി അവസാനം തീയതി",
"interest_projected": "ബായാജ് പ്രൊജക്ടുചെയ്യൽ",
"interest_capitalized": "ബായാജ് ക്യാപിറ്റലൈസ്ഡ്",
"no_of_installments_paid": "പിന്നീട് നടത്തിയ കിസ്തുകൾക്കായി നിരക്കുകൾ",
"next_due_date": "അടുത്ത നിർബന്ധ തീയതി",
"rd_penal_count": "ആർഡി പെനല്‍ എണ്ണം",
"hold_value": "ഹോൾഡ് മൂല്യം",
"term_length": "അവധി നീളം",
"interest_repayment_method": "ബായാജ് തിരിച്ചറിയൽ രീതി",
"mode_of_account": "അക്കൗണ്ട് മോഡ്",
"secondary_cif_number": "സെക്കന്ററി സിഐഎഫ് നമ്പർ",
"secondary_cif_name": "സെക്കന്ററി സിഐഎഫ് പേര്",
"no_statements_found": "സ്റ്റേറ്റ്മെന്റുകൾ കണ്ടെത്തിയില്ല",
"date_range_exceeds_10_days": "10 ദിവസങ്ങൾക്ക് അധികം തീയതി ശ്രേണി തിരഞ്ഞെടുക്കരുത്",
"last_10_transactions": "അവസാന 10 ലിങ്കുകൾ പ്രദർശിപ്പിക്കുന്നു",
"select_account_type": "അക്കൗണ്ട് തരം തിരഞ്ഞെടുക്കുക",
"select_account_number": "അക്കൗണ്ട് നമ്പർ തിരഞ്ഞെടുക്കുക",
"select_date_range": "തീയതി ശ്രേണി തിരഞ്ഞെടുക്കുക",
"please_wait": "ദയവായി കാത്തിരിക്കുക...",
"preferences": "മൊത്തത്തിന്റെ അഭിരുചികൾ",
"everforest": "എവർഫോറസ്റ്റ്",
"rosy": "റോസി",
"skypeia": "സ്കൈപ്പിയ",
"marigold": "മാരിഗോൾഡ്",
"select_language": "ഭാഷ തിരഞ്ഞെടുക്കുക",
"english": "ഇംഗ്ലീഷ്",
"hindi": "ഹിന്ദി",
"bengali": "ബംഗാളി",
"malayalam": "മലയാളം",
"dark_theme": "കറുത്ത തീം",
"color_theme": "നിറ തീം",
"language": "ഭാഷ",
"deposit": "ഡപ്പോസിറ്റ്",
"loan": "വായ്പ",
"export": "കയറ്റുമതി ചെയ്യുക",
"invalid_credentials": "അസാധുവായ ക്രെഡൻഷ്യലുകൾ",
"logout": "ലോഗ്ഔട്ട്",
"backend_ip": "ബാക്ക്എൻഡ് ഐപി",
"bank_branch_no": "ബാങ്ക് ശാഖ നമ്പർ",
"ifsc_code": "ഐഎഫ്എസ്സി കോഡ്",
"bank_branch_name": "ബാങ്ക് ശാഖയുടെ പേര്",
"search_customer_paragraph": "ദയവായി നിങ്ങളുടെ ഐപികെഎസ് ഉപഭോക്താവ് നമ്പർ നൽകുക. ഐപികെഎസ് ഡാറ്റാബേസിൽ നിങ്ങളുടെ വിശദാംശങ്ങൾ തിരയാൻ ഉപഭോക്താവ് നമ്പർ ഉപയോഗിക്കുന്നു",
"invalid_customer_no": "അസാധുവായ ഉപഭോക്താവ് നമ്പർ",
"searching": "തിരയുന്നു",
"invalid_otp": "അസാധുവായ ഒറ്റത്തവണ പാസ്‌വേഡ്",
"register_customer_message": "ദയവായി mApp-നായി നിങ്ങളുടെ പുതിയ ഉപയോക്തൃനാമവും പാസ്‌വേഡും നൽകുക",
"username_criteria": "6 അക്ഷരങ്ങൾ, അക്ഷരങ്ങളും അക്കങ്ങളും മാത്രം",
"password_criteria": "ഒരു അപ്പർകേസ്, ഒരു ലോവർകേസ്, ഒരു നമ്പർ (8 അക്ഷരങ്ങൾ)",
"passowrd_mismatch_msg": "പാസ്‌വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല",
"password_confirm": "പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക",
"registration_successful": "രജിസ്ട്രേഷൻ വിജയകരമായി",
"registration_successful_message": "ദയവായി നിങ്ങളുടെ പുതിയ ക്രെഡൻഷ്യലുകൾ ഉപയോഗിച്ച് ലോഗിൻ ചെയ്യുക",
"goto_login": "ലോഗിൻ പേജിലേക്ക് പോകുക",
"socket_exception": "ദയവായി നിങ്ങളുടെ ഇന്റർനെറ്റ് കണക്ഷൻ പരിശോധിക്കുക",
"mpin_setup": "4-അക്ക എംപിൻ സജ്ജമാക്കുക",
"mpin_confirm": "നിങ്ങളുടെ എംപിൻ സ്ഥിരീകരിക്കുക",
"storage_permission_denied": "സ്റ്റോറേജ് അനുമതി നിഷേധിച്ചു",
"forgot_password": "പാസ്‌വേഡ് മറന്നോ",
"forgot_password_search_user": "നിങ്ങളുടെ സിഐഎഫ് നമ്പർ നൽകുക",
"forgot_password_success": "പുനഃസജ്ജീകരണം വിജയകരമായി",
"forgot_password_create": "നിങ്ങളുടെ പുതിയ പാസ്‌വേഡ് നൽകുക",
"new_password": "പുതിയ പാസ്‌വേഡ്",
"security": "സുരക്ഷ",
"verify_mpin": "നിങ്ങളുടെ എംപിൻ പരിശോധിക്കുക"
}

View File

@@ -1,67 +0,0 @@
import 'package:flutter/material.dart';
class KMobileColorScheme {
KMobileColorScheme._();
static Map<ThemeMode, ColorScheme> everforest = {
ThemeMode.light: ColorScheme.fromSeed(
seedColor: const Color(0xFF008442),
brightness: Brightness.light,
primary: const Color(0xff2C6A45),
onPrimary: const Color(0xffffffff),
primaryContainer: const Color(0xffB0F1C3),
onPrimaryContainer: const Color(0xFF00210F),
secondary: const Color(0xFF4F6354),
onSecondary: const Color(0xFFFFFFFF),
error: const Color(0xFFBA1A1A),
onError: const Color(0xFFFFFFFF),
background: const Color(0xFFF6FBF3),
onBackground: const Color(0xFF181D19),
surface: const Color(0xFFF6FBF3),
onSurface: const Color(0xFF181D19),
),
ThemeMode.dark: ColorScheme.fromSeed(
seedColor: const Color(0xFF008442),
brightness: Brightness.dark,
primary: const Color(0xff95D5A8),
onPrimary: const Color(0xff00391E),
primaryContainer: const Color(0xff0E512F),
onPrimaryContainer: const Color(0xFFB0F1C3),
secondary: const Color(0xFFB6CCB9),
onSecondary: const Color(0xFF213527),
error: const Color(0xFFFFB4AB),
onError: const Color(0xFF690005),
background: const Color(0xFF0F1511),
onBackground: const Color(0xFFDFE4DD),
surface: const Color(0xFF0F1511),
onSurface: const Color(0xFFDFE4DD),
),
};
static Map<ThemeMode, ColorScheme> rosy = {
ThemeMode.light: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 132, 0, 66),
),
ThemeMode.dark: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 132, 0, 66),
brightness: Brightness.dark),
};
static Map<ThemeMode, ColorScheme> skypeia = {
ThemeMode.light: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 0, 62, 132),
),
ThemeMode.dark: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 0, 62, 132),
brightness: Brightness.dark),
};
static Map<ThemeMode, ColorScheme> marigold = {
ThemeMode.light: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 123, 132, 0),
),
ThemeMode.dark: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 123, 132, 0),
brightness: Brightness.dark),
};
}

View File

@@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: async
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
url: "https://pub.dev"
source: hosted
version: "2.12.0"
version: "2.13.0"
bloc:
dependency: "direct main"
description:
@@ -61,10 +61,10 @@ packages:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
version: "2.0.4"
cli_util:
dependency: transitive
description:
@@ -89,6 +89,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.dev"
source: hosted
version: "0.3.4+2"
crypto:
dependency: transitive
description:
@@ -133,10 +141,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.3.3"
ffi:
dependency: transitive
description:
@@ -153,6 +161,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
@@ -247,10 +263,10 @@ packages:
dependency: "direct main"
description:
name: flutter_svg
sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1
sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.2.0"
flutter_swipe_button:
dependency: "direct main"
description:
@@ -321,10 +337,10 @@ packages:
dependency: "direct main"
description:
name: intl
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
url: "https://pub.dev"
source: hosted
version: "0.19.0"
version: "0.20.2"
js:
dependency: transitive
description:
@@ -345,10 +361,10 @@ packages:
dependency: transitive
description:
name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
url: "https://pub.dev"
source: hosted
version: "10.0.8"
version: "10.0.9"
leak_tracker_flutter_testing:
dependency: transitive
description:
@@ -413,6 +429,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.11"
lottie:
dependency: "direct main"
description:
name: lottie
sha256: c5fa04a80a620066c15cf19cc44773e19e9b38e989ff23ea32e5903ef1015950
url: "https://pub.dev"
source: hosted
version: "3.3.1"
matcher:
dependency: transitive
description:
@@ -445,6 +469,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.16.0"
mime:
dependency: transitive
description:
name: mime
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
nested:
dependency: transitive
description:
@@ -565,6 +597,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.0"
share_plus:
dependency: "direct main"
description:
name: share_plus
sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900"
url: "https://pub.dev"
source: hosted
version: "7.2.2"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
url: "https://pub.dev"
source: hosted
version: "3.4.0"
shared_preferences:
dependency: "direct main"
description:
@@ -642,6 +690,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.1"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
@@ -754,14 +810,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.4"
uuid:
dependency: transitive
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_graphics:
dependency: transitive
description:
name: vector_graphics
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
url: "https://pub.dev"
source: hosted
version: "1.1.18"
version: "1.1.19"
vector_graphics_codec:
dependency: transitive
description:
@@ -790,10 +854,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
url: "https://pub.dev"
source: hosted
version: "14.3.1"
version: "15.0.0"
web:
dependency: transitive
description:
@@ -806,10 +870,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
url: "https://pub.dev"
source: hosted
version: "5.13.0"
version: "5.14.0"
xdg_directories:
dependency: transitive
description:
@@ -835,5 +899,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.7.0 <4.0.0"
dart: ">=3.8.0 <4.0.0"
flutter: ">=3.27.0"

View File

@@ -55,6 +55,8 @@ dependencies:
provider: ^6.1.5
google_fonts: ^6.2.1
shimmer: ^3.0.0
lottie: ^3.3.1
share_plus: ^7.2.1
@@ -93,6 +95,9 @@ flutter:
- assets/images/icon.png
- assets/images/avatar_male.svg
- assets/images/avatar_female.svg
- assets/animations/rupee.json
- assets/animations/error.json
- assets/animations/done.json
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware

View File

@@ -1,29 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kmobile/app.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const KMobile());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}