- m8logo.svg: inline fills (flutter_svg ignores <style> blocks; logo rendered black) - theme.dart v2.0.0: purple -> site palette (#1265ED / #3B8BFF / navy darks) - app_config.dart: appVersion 1.3.0 -> 1.6.0 (profile screen showed stale version) - manifest.json: theme/background colours to match - Remove em-dashes from user-visible UI strings (global style rule) - chat_screen.dart: fix use_build_context_synchronously in _leaveRoom - Deployed to app2.m8chat.au after full functionality audit (all green) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
333 lines
11 KiB
Dart
333 lines
11 KiB
Dart
// Version: 1.1.1 | Created: 2026-04-11 | Updated: 2026-07-03
|
|
// 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<Widget> 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),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|