// Version: 1.1.0 | Created: 2026-04-11 | Updated: 2026-04-11 // Help tab with in-app guidance for common tasks and troubleshooting. import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:web/web.dart' as web; import '../../../core/network/matrix_client.dart'; import '../../profile/presentation/security_setup_dialog.dart'; void _launchUrl(BuildContext context, String url) { web.window.open(url, '_blank'); } class HelpTab extends ConsumerWidget { const HelpTab({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final client = ref.watch(matrixClientProvider); return ListView( padding: const EdgeInsets.all(16), children: [ Padding( padding: const EdgeInsets.only(bottom: 16), child: Text( 'Help & Troubleshooting', style: theme.textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, ), ), ), // --- Encrypted messages --- _HelpCard( icon: Icons.lock_outline, title: 'I see "Encrypted — use Element to read"', children: [ const _HelpParagraph( 'This means your device does not have the keys needed to ' 'read those messages. This happens when:', ), const _HelpBullet( 'You are using M8Chat on a new device or browser.'), const _HelpBullet( 'The messages were sent before you set up security.'), const _HelpBullet( 'Your browser data was cleared.'), const SizedBox(height: 12), const _HelpSubheading('How to fix it'), const _HelpParagraph( '1. Go to the Profile tab.\n' '2. Tap Security & Privacy to expand it.\n' '3. Tap Security setup.\n' '4. If you have set up security before, enter your ' 'recovery key. If not, a new one will be created for you.\n' '5. Save the recovery key somewhere safe.\n' '6. After setup, go back to your rooms. Messages should ' 'now be readable.', ), const SizedBox(height: 12), const _HelpSubheading('What is a recovery key?'), const _HelpParagraph( 'A recovery key is a long code starting with "EsTc" that ' 'unlocks your encrypted messages. It was shown to you when ' 'you first set up security in Element or M8Chat. If you ' 'saved it, you can use it to restore your messages on any ' 'device.', ), const SizedBox(height: 12), Builder( builder: (context) => OutlinedButton.icon( onPressed: () => showSecuritySetupDialog(context, client), icon: const Icon(Icons.shield_outlined), label: const Text('Open Security Setup'), ), ), ], ), // --- New to M8Chat --- const _HelpCard( icon: Icons.chat_bubble_outline, title: 'Getting started with M8Chat', children: [ _HelpParagraph( 'M8Chat is a secure messaging app built on the Matrix ' 'protocol. All your messages are end-to-end encrypted, ' 'meaning only you and the people you chat with can read ' 'them.', ), SizedBox(height: 12), _HelpSubheading('First steps'), _HelpParagraph( '1. Set up security: go to Profile > Security & Privacy ' '> Security setup. This creates your encryption keys and ' 'backs them up.\n' '2. Save your recovery key — you will need it if you ever ' 'log in on a new device.\n' '3. Start chatting. Tap the pencil icon at the top right ' 'to search for users and start a conversation.', ), ], ), // --- Joining a conference --- const _HelpCard( icon: Icons.videocam_outlined, title: 'Joining a video conference', children: [ _HelpParagraph( 'M8Chat supports Jitsi video conferences. To join one:', ), SizedBox(height: 8), _HelpParagraph( '1. Tap the Conference tab at the bottom.\n' '2. Paste the meeting link (e.g. conf.m8chat.au/MyMeeting).\n' '3. Tap Join Meeting.\n\n' 'You can also make voice and video calls directly from any ' 'chat room using the phone and camera icons at the top of ' 'the conversation.', ), ], ), // --- Video call issues --- const _HelpCard( icon: Icons.videocam_off_outlined, title: 'Video call problems', children: [ _HelpSubheading('Scrambled or black video'), _HelpParagraph( 'If video appears scrambled when calling someone on Element X, ' 'this is an encryption compatibility issue that is being ' 'worked on. Try calling from the same app on both ends.', ), SizedBox(height: 8), _HelpSubheading('No audio or robotic sound'), _HelpParagraph( 'This is usually caused by browser permissions. Make sure ' 'you have allowed microphone and camera access when ' 'prompted. On some devices (especially Sailfish OS), the ' 'audio codec support may be limited.', ), ], ), // --- Account & privacy --- const _HelpCard( icon: Icons.security_outlined, title: 'Account & privacy', children: [ _HelpSubheading('Who can read my messages?'), _HelpParagraph( 'Only you and the people in the conversation. M8Chat uses ' 'end-to-end encryption — not even the server operator can ' 'read your messages.', ), SizedBox(height: 8), _HelpSubheading('Can I use multiple devices?'), _HelpParagraph( 'Yes. Set up security on each device using the same ' 'recovery key. Your messages will be available on all ' 'devices where security is set up.', ), SizedBox(height: 8), _HelpSubheading('What happens if I lose my recovery key?'), _HelpParagraph( 'You will not be able to read old messages on new devices. ' 'New messages will still work. You can create a new ' 'recovery key from the Security setup, but old messages ' 'encrypted with the previous key will remain unreadable.', ), ], ), // --- Account management --- _HelpCard( icon: Icons.manage_accounts_outlined, title: 'Account management', children: [ const _HelpParagraph( 'All account changes need to be made through the M8Chat ' 'website. This includes:', ), const _HelpBullet('Changing your password'), const _HelpBullet('Membership settings and billing'), const _HelpBullet('Updating your profile or email'), const _HelpBullet('Deleting your account'), const SizedBox(height: 12), Builder( builder: (context) => OutlinedButton.icon( onPressed: () => _launchUrl(context, 'https://m8chat.au'), icon: const Icon(Icons.open_in_new), label: const Text('Go to m8chat.au'), ), ), const SizedBox(height: 12), const _HelpSubheading('Privacy'), const _HelpParagraph( 'No account details or other privacy or user identifiers ' 'are saved or stored on this application. All data stays ' 'on the M8Chat server and is protected by end-to-end ' 'encryption.', ), const SizedBox(height: 12), const _HelpParagraph( 'For more information about what happens if things go ' 'wrong, including data recovery and account issues:', ), const SizedBox(height: 8), Builder( builder: (context) => OutlinedButton.icon( onPressed: () => _launchUrl( context, 'https://m8chat.au/when-it-all-goes-wrong/'), icon: const Icon(Icons.open_in_new), label: const Text('When it all goes wrong'), ), ), ], ), const SizedBox(height: 32), Center( child: Text( 'Need more help? Visit m8chat.au or contact the M8Chat team.', style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurface.withAlpha(102), ), ), ), const SizedBox(height: 16), ], ); } } // --- Reusable help widgets --- class _HelpCard extends StatelessWidget { const _HelpCard({ required this.icon, required this.title, required this.children, }); final IconData icon; final String title; final List children; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Card( margin: const EdgeInsets.only(bottom: 12), child: ExpansionTile( leading: Icon(icon, color: theme.colorScheme.primary), title: Text( title, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), childrenPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), expandedCrossAxisAlignment: CrossAxisAlignment.start, children: children, ), ); } } class _HelpParagraph extends StatelessWidget { const _HelpParagraph(this.text); final String text; @override Widget build(BuildContext context) { return Text( text, style: Theme.of(context).textTheme.bodyMedium?.copyWith(height: 1.5), ); } } class _HelpSubheading extends StatelessWidget { const _HelpSubheading(this.text); final String text; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 4), child: Text( text, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ); } } class _HelpBullet extends StatelessWidget { const _HelpBullet(this.text); final String text; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(left: 8, top: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('\u2022 '), Expanded( child: Text( text, style: Theme.of(context) .textTheme .bodyMedium ?.copyWith(height: 1.5), ), ), ], ), ); } }