Account Info card UI changed
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:developer';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:kmobile/data/models/user.dart';
|
||||||
import 'package:kmobile/data/repositories/transaction_repository.dart';
|
import 'package:kmobile/data/repositories/transaction_repository.dart';
|
||||||
import 'package:kmobile/di/injection.dart';
|
import 'package:kmobile/di/injection.dart';
|
||||||
import 'package:kmobile/features/accounts/screens/account_info_screen.dart';
|
import 'package:kmobile/features/accounts/screens/account_info_screen.dart';
|
||||||
@@ -35,13 +36,167 @@ class DashboardScreen extends StatefulWidget {
|
|||||||
class _DashboardScreenState extends State<DashboardScreen>
|
class _DashboardScreenState extends State<DashboardScreen>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
int selectedAccountIndex = 0;
|
int selectedAccountIndex = 0;
|
||||||
bool isVisible = false;
|
Map<String, bool> _visibilityMap = {};
|
||||||
bool isRefreshing = false;
|
bool isRefreshing = false;
|
||||||
bool isBalanceLoading = false;
|
bool isBalanceLoading = false;
|
||||||
bool _biometricPromptShown = false;
|
bool _biometricPromptShown = false;
|
||||||
bool _txLoading = false;
|
bool _txLoading = false;
|
||||||
List<Transaction> _transactions = [];
|
List<Transaction> _transactions = [];
|
||||||
bool _txInitialized = false;
|
bool _txInitialized = false;
|
||||||
|
PageController? _pageController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_pageController?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAccountCard(User user, bool isSelected) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final bool isCardVisible = _visibilityMap[user.accountNo] ?? false;
|
||||||
|
// Animated scale for the selected card
|
||||||
|
final scale = isSelected ? 1.0 : 0.9;
|
||||||
|
return AnimatedScale(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
scale: scale,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 18,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFF01A04C),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
getFullAccountType(user.accountType),
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
user.accountNo ?? 'N/A',
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w700, // Changed to bold
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isSelected) // Only show refresh for selected card
|
||||||
|
IconButton(
|
||||||
|
icon: isRefreshing
|
||||||
|
? SizedBox(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
strokeWidth: 2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Symbols.refresh,
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
onPressed: isRefreshing
|
||||||
|
? null
|
||||||
|
: () => _refreshAccountData(context),
|
||||||
|
tooltip: 'Refresh',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"₹ ",
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
fontSize: 32, // Adjusted for space
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isRefreshing || (isBalanceLoading && isSelected))
|
||||||
|
Expanded(child: _buildBalanceShimmer())
|
||||||
|
else
|
||||||
|
Expanded(
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
isCardVisible ? user.currentBalance ?? '0.00' : '*****',
|
||||||
|
style: TextStyle(
|
||||||
|
color: theme.colorScheme.onPrimary,
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (isSelected) // Only show toggle for selected card
|
||||||
|
InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
if (isBalanceLoading) return;
|
||||||
|
final accountNo = user.accountNo;
|
||||||
|
if (accountNo == null) return;
|
||||||
|
|
||||||
|
final bool currentVisibility =
|
||||||
|
_visibilityMap[accountNo] ?? false;
|
||||||
|
|
||||||
|
if (!currentVisibility) {
|
||||||
|
// If it's currently hidden, we show it after a delay
|
||||||
|
setState(() {
|
||||||
|
isBalanceLoading = true;
|
||||||
|
});
|
||||||
|
await Future.delayed(
|
||||||
|
const Duration(milliseconds: 500)); // Shorter delay
|
||||||
|
setState(() {
|
||||||
|
_visibilityMap[accountNo] = true;
|
||||||
|
isBalanceLoading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If it's currently visible, we hide it immediately
|
||||||
|
setState(() {
|
||||||
|
_visibilityMap[accountNo] = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Icon(
|
||||||
|
isCardVisible
|
||||||
|
? Symbols.visibility_lock
|
||||||
|
: Symbols.visibility,
|
||||||
|
color: theme.scaffoldBackgroundColor,
|
||||||
|
weight: 800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _loadTransactions(String accountNo) async {
|
Future<void> _loadTransactions(String accountNo) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -104,7 +259,7 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
baseColor: theme.colorScheme.primary,
|
baseColor: theme.colorScheme.primary,
|
||||||
highlightColor: theme.colorScheme.onPrimary,
|
highlightColor: theme.colorScheme.onPrimary,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 200, height: 42, color: theme.scaffoldBackgroundColor),
|
height: 36, color: theme.scaffoldBackgroundColor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +441,10 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
_loadTransactions(currAccount.accountNo!);
|
_loadTransactions(currAccount.accountNo!);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
_pageController ??= PageController(
|
||||||
|
initialPage: selectedAccountIndex,
|
||||||
|
viewportFraction: 0.85,
|
||||||
|
);
|
||||||
final firstName = getProcessedFirstName(currAccount.name);
|
final firstName = getProcessedFirstName(currAccount.name);
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
@@ -307,162 +466,34 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
// Account Info Card
|
// Account Info Cards
|
||||||
Container(
|
SizedBox(
|
||||||
padding: const EdgeInsets.symmetric(
|
height: 160, // Adjusted height
|
||||||
horizontal: 18,
|
child: PageView.builder(
|
||||||
vertical: 10,
|
controller: _pageController,
|
||||||
),
|
itemCount: users.length,
|
||||||
decoration: BoxDecoration(
|
onPageChanged: (int newIndex) async {
|
||||||
color: Color(0xFF01A04C),
|
if (newIndex == selectedAccountIndex) return;
|
||||||
borderRadius: BorderRadius.circular(16),
|
|
||||||
),
|
// Hide the balance of the old card when scrolling away
|
||||||
child: Column(
|
final oldAccountNo = users[selectedAccountIndex].accountNo;
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
if (oldAccountNo != null) {
|
||||||
children: [
|
_visibilityMap[oldAccountNo] = false;
|
||||||
Row(
|
}
|
||||||
children: [
|
|
||||||
Text(
|
setState(() {
|
||||||
"${getFullAccountType(currAccount.accountType)}: ",
|
selectedAccountIndex = newIndex;
|
||||||
style: TextStyle(
|
});
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
fontSize: 18,
|
await _loadTransactions(
|
||||||
fontWeight: FontWeight.w700,
|
users[newIndex].accountNo!,
|
||||||
),
|
);
|
||||||
),
|
},
|
||||||
DropdownButton<int>(
|
itemBuilder: (context, index) {
|
||||||
value: selectedAccountIndex,
|
final user = users[index];
|
||||||
dropdownColor: theme.colorScheme.primary,
|
final isSelected = index == selectedAccountIndex;
|
||||||
underline: const SizedBox(),
|
return _buildAccountCard(user, isSelected);
|
||||||
icon: const Icon(Icons.keyboard_arrow_down),
|
},
|
||||||
iconEnabledColor: theme.colorScheme.onPrimary,
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
items: List.generate(users.length, (index) {
|
|
||||||
return DropdownMenuItem<int>(
|
|
||||||
value: index,
|
|
||||||
child: Text(
|
|
||||||
users[index].accountNo ?? 'N/A',
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
onChanged: (int? newIndex) async {
|
|
||||||
if (newIndex == null ||
|
|
||||||
newIndex == selectedAccountIndex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (isBalanceLoading) return;
|
|
||||||
if (isVisible) {
|
|
||||||
setState(() {
|
|
||||||
isBalanceLoading = true;
|
|
||||||
selectedAccountIndex = newIndex;
|
|
||||||
});
|
|
||||||
await Future.delayed(
|
|
||||||
const Duration(milliseconds: 200),
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
isBalanceLoading = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
selectedAccountIndex = newIndex;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await _loadTransactions(
|
|
||||||
users[newIndex].accountNo!,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
IconButton(
|
|
||||||
icon: isRefreshing
|
|
||||||
? SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
strokeWidth: 2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
Symbols.refresh,
|
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
),
|
|
||||||
onPressed: isRefreshing
|
|
||||||
? null
|
|
||||||
: () => _refreshAccountData(context),
|
|
||||||
tooltip: 'Refresh',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"₹ ",
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
fontSize: 40,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
isRefreshing || isBalanceLoading
|
|
||||||
? _buildBalanceShimmer()
|
|
||||||
: Text(
|
|
||||||
isVisible
|
|
||||||
? currAccount.currentBalance ??
|
|
||||||
'0.00'
|
|
||||||
: '*****',
|
|
||||||
style: TextStyle(
|
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
fontSize: 40,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
if (isBalanceLoading) return;
|
|
||||||
if (!isVisible) {
|
|
||||||
setState(() {
|
|
||||||
isBalanceLoading = true;
|
|
||||||
});
|
|
||||||
await Future.delayed(
|
|
||||||
const Duration(seconds: 1),
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
isVisible = true;
|
|
||||||
isBalanceLoading = false;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setState(() {
|
|
||||||
isVisible = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Icon(
|
|
||||||
isVisible
|
|
||||||
? Symbols.visibility_lock
|
|
||||||
: Symbols.visibility,
|
|
||||||
color: theme.scaffoldBackgroundColor,
|
|
||||||
weight: 800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 15),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
|
|||||||
Reference in New Issue
Block a user