// Version: 1.5.0 | Created: 2026-04-01 | Updated: 2026-07-04 // Profile screen. Shows current user info and logout button. import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/auth/auth_notifier.dart'; import '../../../core/auth/auth_state.dart'; import '../../../core/config/app_config.dart'; import '../../../core/network/matrix_client.dart'; import '../../../shared/utils/matrix_id.dart'; import '../../../shared/widgets/matrix_avatar.dart'; import '../../panic/data/panic_repository.dart'; import '../../panic/presentation/panic_settings_dialog.dart'; import 'key_restore_dialog.dart'; import 'security_setup_dialog.dart'; class ProfileScreen extends ConsumerWidget { const ProfileScreen({super.key, this.embedded = false}); /// When true, this screen is shown inside the bottom-nav tab of RoomsScreen. final bool embedded; @override Widget build(BuildContext context, WidgetRef ref) { final authState = ref.watch(authProvider); final client = ref.watch(matrixClientProvider); final userId = authState.maybeWhen( authenticated: (userId, _, __) => userId, orElse: () => '', ); final displayName = client.userID?.matrixLocalpart ?? 'Unknown'; final body = ListView( padding: const EdgeInsets.all(24), children: [ Center(child: MatrixAvatar(name: displayName, radius: 48)), const SizedBox(height: 16), Center( child: Text( '@$displayName', style: Theme.of( context, ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), ), ), Center( child: Text( userId, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withAlpha(153), ), ), ), const SizedBox(height: 32), const Divider(), const SizedBox(height: 16), Text( 'Settings', style: Theme.of(context).textTheme.labelLarge?.copyWith( color: Theme.of(context).colorScheme.onSurface.withAlpha(153), ), ), const SizedBox(height: 8), ExpansionTile( leading: const Icon(Icons.notifications_outlined), title: const Text('Notifications'), children: [ SwitchListTile( title: const Text('Push notifications'), subtitle: const Text('Coming in Phase 2'), value: false, onChanged: null, ), SwitchListTile( title: const Text('Notification sounds'), subtitle: const Text('Coming in Phase 2'), value: false, onChanged: null, ), ], ), ExpansionTile( leading: const Icon(Icons.security_outlined), title: const Text('Security & Privacy'), children: [ ListTile( leading: const Icon(Icons.lock_outline), title: const Text('End-to-end encryption'), subtitle: Text( client.encryptionEnabled ? 'Active. Messages are encrypted' : 'Not available in this browser', ), enabled: false, ), ListTile( leading: const Icon(Icons.key_outlined), title: const Text('Restore message keys'), subtitle: const Text( 'Enter your recovery key to decrypt old messages', ), enabled: client.encryptionEnabled, onTap: client.encryptionEnabled ? () async { final restored = await showKeyRestoreDialog(context, client); if (restored && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Keys restored. Reopen rooms ' 'to see decrypted messages.'), ), ); } } : null, ), ListTile( leading: const Icon(Icons.shield_outlined), title: const Text('Security setup'), subtitle: Text( client.encryption?.crossSigning.enabled == true ? 'Cross-signing active. Device verified' : 'Set up cross-signing and key backup', ), enabled: client.encryptionEnabled, onTap: client.encryptionEnabled ? () async { final completed = await showSecuritySetupDialog(context, client); if (completed && context.mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Security setup complete.'), ), ); } } : null, ), ListTile( leading: const Icon(Icons.password_outlined), title: const Text('Change password'), subtitle: const Text('Managed via m8chat.au account settings'), enabled: false, ), ], ), ListTile( leading: const Icon(Icons.emergency_share, color: Colors.red), title: const Text('Emergency panic button'), subtitle: Text( ref.watch(panicRepositoryProvider).config != null ? 'Armed. Hold the alert icon on the rooms screen to trigger.' : 'Send your location and an alert to a room of your choice', ), onTap: () => showPanicSettingsDialog(context, ref), ), const SizedBox(height: 16), const Divider(), const SizedBox(height: 16), _LogoutButton( onLogout: () async { await ref.read(authProvider.notifier).logout(); }, ), const SizedBox(height: 16), Center( child: Text( '${AppConfig.appName} ${AppConfig.appVersion} ยท ${AppConfig.matrixServerName}', style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurface.withAlpha(77), ), ), ), ], ); if (embedded) return body; return Scaffold( appBar: AppBar(title: const Text('Profile')), body: body, ); } } class _LogoutButton extends StatelessWidget { const _LogoutButton({required this.onLogout}); final VoidCallback onLogout; @override Widget build(BuildContext context) { return OutlinedButton.icon( onPressed: () { showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Sign out'), content: Text( 'Are you sure you want to sign out of ${AppConfig.appName}?', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), TextButton( onPressed: () { Navigator.of(context).pop(); onLogout(); }, child: Text( 'Sign out', style: TextStyle(color: Theme.of(context).colorScheme.error), ), ), ], ), ); }, icon: const Icon(Icons.logout), label: const Text('Sign out'), style: OutlinedButton.styleFrom( foregroundColor: Theme.of(context).colorScheme.error, side: BorderSide(color: Theme.of(context).colorScheme.error), minimumSize: const Size.fromHeight(48), ), ); } }