Files
kmobile/lib/features/profile/profile_screen.dart

468 lines
15 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/svg.dart';
import 'package:kmobile/data/repositories/auth_repository.dart';
import 'package:kmobile/features/profile/daily_transaction_limit.dart';
import 'package:kmobile/features/profile/logout_dialog.dart';
import 'package:kmobile/features/profile/security_settings_screen.dart';
import 'package:kmobile/security/secure_storage.dart';
import 'package:local_auth/local_auth.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../di/injection.dart';
import '../../l10n/app_localizations.dart';
import 'package:kmobile/features/profile/preferences/preference_screen.dart';
class ProfileScreen extends StatefulWidget {
final String mobileNumber;
final String customerNo;
final String customerName;
const ProfileScreen(
{super.key,
required this.mobileNumber,
required this.customerNo,
required this.customerName});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
bool _isBiometricEnabled = false;
@override
void initState() {
super.initState();
_loadBiometricStatus();
}
Future<String> _getAppVersion() async {
final PackageInfo info = await PackageInfo.fromPlatform();
return 'Version ${info.version} (${info.buildNumber})';
}
Future<void> _loadBiometricStatus() async {
final storage = getIt<SecureStorage>();
final enabled = await storage.read('biometric_enabled');
setState(() {
_isBiometricEnabled = enabled ?? false;
});
}
Future<void> _handleLogout(BuildContext context) async {
final auth = getIt<AuthRepository>();
final prefs = await SharedPreferences.getInstance();
await prefs.clear(); // clear saved session/token
await auth.clearAuthTokens();
// Navigate to login and remove all previous routes
Navigator.pushNamedAndRemoveUntil(context, '/login', (route) => false);
}
Future<void> _handleBiometricToggle(bool enable) async {
final localAuth = LocalAuthentication();
final storage = getIt<SecureStorage>();
final canCheck = await localAuth.canCheckBiometrics;
if (!canCheck) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(AppLocalizations.of(context).biometricsNotAvailable)),
);
}
return;
}
if (enable) {
final optIn = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
title: Text(AppLocalizations.of(context).enableFingerprintLogin),
content: Text(AppLocalizations.of(context).enableFingerprintMessage),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: Text(AppLocalizations.of(context).no),
),
TextButton(
onPressed: () => Navigator.of(ctx).pop(true),
child: Text(AppLocalizations.of(context).yes),
),
],
),
);
if (optIn == true) {
try {
final didAuth = await localAuth.authenticate(
localizedReason: AppLocalizations.of(context).authenticateToEnable,
options: const AuthenticationOptions(
stickyAuth: true,
biometricOnly: true,
),
);
if (didAuth) {
await storage.write('biometric_enabled', true);
if (mounted) {
setState(() {
_isBiometricEnabled = true;
});
}
} else {
// Authentication failed, reload state to refresh UI
if (mounted) {
await _loadBiometricStatus();
}
}
} catch (e) {
// Handle exceptions, reload state to ensure consistency
if (mounted) {
await _loadBiometricStatus();
}
}
} else {
// User cancelled, reload state to refresh UI
if (mounted) {
await _loadBiometricStatus();
}
}
} else {
final optOut = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (ctx) => AlertDialog(
title: Text(AppLocalizations.of(context).disableFingerprintLogin),
content: Text(AppLocalizations.of(context).disableFingerprintMessage),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(false),
child: Text(AppLocalizations.of(context).no),
),
TextButton(
onPressed: () => Navigator.of(ctx).pop(true),
child: Text(AppLocalizations.of(context).yes),
),
],
),
);
if (optOut == true) {
await storage.write('biometric_enabled', false);
if (mounted) {
setState(() {
_isBiometricEnabled = false;
});
}
} else {
// User cancelled, reload state to refresh UI
if (mounted) {
await _loadBiometricStatus();
}
}
}
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
title: Text(loc.profile),
elevation: 0,
),
body: Stack(
children: [
ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
// ===== Profile Header =====
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
// Avatar
Container(
width: 56,
height: 56,
child: CircleAvatar(
radius: 50,
child: SvgPicture.asset(
'assets/images/avatar_male.svg',
fit: BoxFit.cover,
),
),
),
const SizedBox(width: 12),
// Name + mobile
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
// If you want to show the user's name instead, replace below.
widget.customerName,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w600,
),
),
const SizedBox(height: 4),
Text(
widget.customerNo,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface
.withOpacity(0.7),
),
),
],
),
),
],
),
),
),
const SizedBox(height: 16),
// ===== Section: Settings =====
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"Settings",
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
const SizedBox(height: 8),
_SectionTile(
leadingIcon: Icons.settings,
title: loc.preferences,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PreferenceScreen(),
),
);
},
),
_SectionTile(
leadingIcon: Icons.security,
title: loc.securitySettings,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecuritySettingsScreen(
mobileNumber: widget.mobileNumber,
),
),
);
},
),
_SectionTile(
leadingIcon: Icons.currency_rupee,
title: loc.dailylimit,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const DailyLimitScreen(),
),
);
},
),
Card(
child: SwitchListTile(
title: Text(loc.enableFingerprintLogin),
value: _isBiometricEnabled,
onChanged: (bool value) {
_handleBiometricToggle(value);
},
secondary: const Icon(Icons.fingerprint),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
),
),
const SizedBox(height: 16),
const Divider(height: 24),
// ===== Section: Security & App =====
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
loc.appVersion,
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
const SizedBox(height: 8),
// Fingerprint toggle inside a styled container
Card(
child: ListTile(
leading: const Icon(Icons.smartphone),
title: Text(loc.appVersion),
trailing: FutureBuilder<String>(
future: _getAppVersion(),
builder:
(BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(strokeWidth: 2),
);
} else if (snapshot.hasError) {
return Text(loc.error);
} else {
return Text(
snapshot.data ?? "N/A",
);
}
},
),
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
),
const SizedBox(height: 16),
const Divider(height: 24),
// ===== Section: Actions =====
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Text(
"Exit",
style: theme.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
letterSpacing: 0.2,
),
),
),
const SizedBox(height: 8),
_SectionTile(
leadingIcon: Icons.exit_to_app,
title: loc.logout,
trailChevron: false, // action tile, no chevron
onTap: () async {
final shouldExit = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(loc.logout),
content: Text(loc.logoutCheck),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(loc.no),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(loc.yes),
),
],
),
);
if (shouldExit == true) {
if (Platform.isAndroid) {
SystemNavigator.pop();
}
exit(0);
}
},
),
_SectionTile(
leadingIcon: Icons.logout,
title: loc.deregister,
trailChevron: false,
onTap: () async {
final shouldLogout = await showDialog<bool>(
context: context,
builder: (_) => const LogoutDialog(),
);
if (shouldLogout == true) {
await _handleLogout(context);
}
},
),
const SizedBox(height: 24),
],
),
// ===== Watermark (kept subtle, no theme change) =====
IgnorePointer(
child: Positioned.fill(
child: Center(
child: Opacity(
opacity: 0.06,
child: ClipOval(
child: Image.asset(
'assets/images/logo.png',
width: 200,
height: 200,
filterQuality: FilterQuality.medium,
),
),
),
),
),
),
],
),
);
}
}
class _SectionTile extends StatelessWidget {
const _SectionTile({
required this.leadingIcon,
required this.title,
this.onTap,
this.trailChevron = true,
});
final IconData leadingIcon;
final String title;
final VoidCallback? onTap;
final bool trailChevron;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
margin: const EdgeInsets.only(bottom: 10),
child: ListTile(
leading: Icon(leadingIcon, color: theme.colorScheme.onSurface),
title: Text(title, style: theme.textTheme.bodyLarge),
trailing: trailChevron
? Icon(Icons.chevron_right, color: theme.colorScheme.onSurface)
: null,
onTap: onTap,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
),
);
}
}