feat: brand retheme to m8chat.au blues, fix black logo, v1.6.0+8

- 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>
This commit is contained in:
2026-07-03 05:16:11 +10:00
parent 3865391829
commit 3568b17f0f
14 changed files with 61 additions and 62 deletions

View File

@@ -1,25 +1,27 @@
// Version: 1.0.0 | Created: 2026-04-01
// Version: 2.0.0 | Created: 2026-04-01 | Updated: 2026-07-03
// M8Chat brand theme — dark and light variants.
// Primary brand colour: #5C35C9 (deep purple). Accent: #7B5CF6.
// Palette from the m8chat.au Elementor kit (kit post 8):
// Accent #1 "#1265ED", Blue Element "#3B8BFF", Ancop Blue "#010B46".
// v2.0.0 replaces the old purple scheme, which did not match the website.
import 'package:flutter/material.dart';
/// M8Chat brand colours.
abstract final class M8Colours {
static const Color brandPurple = Color(0xFF5C35C9);
static const Color accentPurple = Color(0xFF7B5CF6);
static const Color darkBackground = Color(0xFF0F0F1A);
static const Color darkSurface = Color(0xFF1A1A2E);
static const Color darkSurfaceVariant = Color(0xFF22223A);
static const Color onDarkSurface = Color(0xFFE8E8F0);
static const Color subtleText = Color(0xFF9898B0);
static const Color brandBlue = Color(0xFF1265ED);
static const Color accentBlue = Color(0xFF3B8BFF);
static const Color darkBackground = Color(0xFF0A0F2E);
static const Color darkSurface = Color(0xFF131A42);
static const Color darkSurfaceVariant = Color(0xFF1C2452);
static const Color onDarkSurface = Color(0xFFE8ECF5);
static const Color subtleText = Color(0xFF96A0C2);
static const Color errorRed = Color(0xFFCF6679);
static const Color unreadGreen = Color(0xFF4CAF50);
}
/// Dark theme — default for M8Chat.
ThemeData buildDarkTheme() {
const seed = M8Colours.brandPurple;
const seed = M8Colours.brandBlue;
final scheme =
ColorScheme.fromSeed(
@@ -28,7 +30,7 @@ ThemeData buildDarkTheme() {
).copyWith(
surface: M8Colours.darkBackground,
surfaceContainerHighest: M8Colours.darkSurface,
primary: M8Colours.accentPurple,
primary: M8Colours.accentBlue,
onSurface: M8Colours.onDarkSurface,
error: M8Colours.errorRed,
);
@@ -45,7 +47,7 @@ ThemeData buildDarkTheme() {
),
navigationBarTheme: NavigationBarThemeData(
backgroundColor: M8Colours.darkSurface,
indicatorColor: M8Colours.accentPurple.withAlpha(51),
indicatorColor: M8Colours.accentBlue.withAlpha(51),
labelTextStyle: WidgetStateProperty.all(
const TextStyle(fontSize: 12, color: M8Colours.onDarkSurface),
),
@@ -59,14 +61,14 @@ ThemeData buildDarkTheme() {
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: M8Colours.accentPurple, width: 2),
borderSide: const BorderSide(color: M8Colours.accentBlue, width: 2),
),
labelStyle: const TextStyle(color: M8Colours.subtleText),
hintStyle: const TextStyle(color: M8Colours.subtleText),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: M8Colours.accentPurple,
backgroundColor: M8Colours.accentBlue,
foregroundColor: Colors.white,
minimumSize: const Size.fromHeight(52),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
@@ -97,7 +99,7 @@ ThemeData buildDarkTheme() {
/// Light theme — follows system preference if user selects it.
ThemeData buildLightTheme() {
const seed = M8Colours.brandPurple;
const seed = M8Colours.brandBlue;
final scheme = ColorScheme.fromSeed(
seedColor: seed,

View File

@@ -1,4 +1,4 @@
// Version: 2.0.0 | Created: 2026-04-01 | Updated: 2026-04-11
// Version: 2.1.0 | Created: 2026-04-01 | Updated: 2026-07-03
// App-wide constants. Change matrixBaseUrl for different environments.
abstract final class AppConfig {
@@ -11,7 +11,7 @@ abstract final class AppConfig {
'turns:matrix.m8chat.au:5349',
];
static const String appName = 'M8Chat';
static const String appVersion = '1.3.0';
static const String appVersion = '1.6.0';
// Jitsi conferencing
static const String jitsiDomain = 'conf.m8chat.au';

View File

@@ -1,4 +1,4 @@
// Version: 1.3.0 | Created: 2026-04-01 | Updated: 2026-04-27
// Version: 1.3.1 | Created: 2026-04-01 | Updated: 2026-07-03
// Full chat screen — timeline + input + typing indicators + read receipts
// + long-press context menu (reply, react, copy, delete) + room options sheet.
@@ -265,7 +265,7 @@ class _RoomOptionsSheet extends ConsumerStatefulWidget {
class _RoomOptionsSheetState extends ConsumerState<_RoomOptionsSheet> {
bool _leaving = false;
Future<void> _leaveRoom(BuildContext context) async {
Future<void> _leaveRoom() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (_) => AlertDialog(
@@ -421,7 +421,7 @@ class _RoomOptionsSheetState extends ConsumerState<_RoomOptionsSheet> {
style: TextStyle(color: theme.colorScheme.error),
),
enabled: !_leaving,
onTap: () => _leaveRoom(context),
onTap: _leaveRoom,
),
],
),

View File

@@ -1,4 +1,4 @@
// Version: 1.3.0 | Created: 2026-04-01 | Updated: 2026-04-27
// Version: 1.3.1 | Created: 2026-04-01 | Updated: 2026-07-03
// Message bubble widget. Handles text, images, files, redacted, replies.
import 'package:cached_network_image/cached_network_image.dart';
@@ -145,7 +145,7 @@ class _MessageContentBody extends StatelessWidget {
const SizedBox(width: 6),
Flexible(
child: Text(
'Encrypted — use Element to read',
'Encrypted. Use Element to read',
style: TextStyle(
color: textColour.withAlpha(153),
fontStyle: FontStyle.italic,

View File

@@ -1,4 +1,4 @@
// Version: 1.1.0 | Created: 2026-04-11 | Updated: 2026-04-11
// 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';
@@ -36,7 +36,7 @@ class HelpTab extends ConsumerWidget {
// --- Encrypted messages ---
_HelpCard(
icon: Icons.lock_outline,
title: 'I see "Encrypted — use Element to read"',
title: 'I see "Encrypted. Use Element to read"',
children: [
const _HelpParagraph(
'This means your device does not have the keys needed to '
@@ -98,7 +98,7 @@ class HelpTab extends ConsumerWidget {
'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 '
'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.',
@@ -156,7 +156,7 @@ class HelpTab extends ConsumerWidget {
_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 '
'end-to-end encryption. Not even the server operator can '
'read your messages.',
),
SizedBox(height: 8),

View File

@@ -1,4 +1,4 @@
// Version: 1.0.0 | Created: 2026-04-05
// Version: 1.0.1 | Created: 2026-04-05 | Updated: 2026-07-03
// Welcome/landing screen — first thing unauthenticated users see.
// Two paths: join a Jitsi conference (no login) or sign in for Matrix chat.
@@ -67,7 +67,7 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
),
const SizedBox(height: 8),
Text(
'Chat and conference all in one place',
'Chat and conference, all in one place',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurface.withAlpha(153),
@@ -84,7 +84,7 @@ class _WelcomeScreenState extends State<WelcomeScreen> {
isDark: isDark,
icon: Icons.videocam_outlined,
title: 'Join a Conference',
subtitle: 'Paste your meeting link to join instantly — no account needed.',
subtitle: 'Paste your meeting link to join instantly. No account needed.',
child: Column(
children: [
TextField(

View File

@@ -1,4 +1,4 @@
// Version: 1.0.0 | Created: 2026-04-11
// Version: 1.0.1 | Created: 2026-04-11 | Updated: 2026-07-03
// Dialog for entering a recovery key to restore encrypted message keys
// from the server-side key backup (SSSS / Secure Secret Storage).
@@ -147,7 +147,7 @@ class _KeyRestoreDialogState extends State<_KeyRestoreDialog> {
SizedBox(height: 16),
Text(
'Message keys have been restored. '
'Go back to your rooms — previously encrypted '
'Go back to your rooms. Previously encrypted '
'messages should now be readable.',
),
],

View File

@@ -1,4 +1,4 @@
// Version: 1.4.0 | Created: 2026-04-01 | Updated: 2026-04-11
// Version: 1.4.1 | Created: 2026-04-01 | Updated: 2026-07-03
// Profile screen. Shows current user info and logout button.
import 'package:flutter/material.dart';
@@ -89,7 +89,7 @@ class ProfileScreen extends ConsumerWidget {
title: const Text('End-to-end encryption'),
subtitle: Text(
client.encryptionEnabled
? 'Active — messages are encrypted'
? 'Active. Messages are encrypted'
: 'Not available in this browser',
),
enabled: false,
@@ -108,7 +108,7 @@ class ProfileScreen extends ConsumerWidget {
if (restored && context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Keys restored — reopen rooms '
content: Text('Keys restored. Reopen rooms '
'to see decrypted messages.'),
),
);
@@ -121,7 +121,7 @@ class ProfileScreen extends ConsumerWidget {
title: const Text('Security setup'),
subtitle: Text(
client.encryption?.crossSigning.enabled == true
? 'Cross-signing active — device verified'
? 'Cross-signing active. Device verified'
: 'Set up cross-signing and key backup',
),
enabled: client.encryptionEnabled,

View File

@@ -1,4 +1,4 @@
// Version: 1.0.0 | Created: 2026-04-11
// Version: 1.0.1 | Created: 2026-04-11 | Updated: 2026-07-03
// Security setup dialog: drives the Matrix SDK Bootstrap to set up
// SSSS, cross-signing, and online key backup in one flow.
// For new users: creates everything, shows recovery key.
@@ -298,7 +298,7 @@ class _SecuritySetupDialogState extends State<_SecuritySetupDialog> {
children: [
const Text(
'Your recovery key has been created. Save it somewhere '
'safe — you will need it to restore your messages on '
'safe. You will need it to restore your messages on '
'new devices.',
),
const SizedBox(height: 16),
@@ -389,7 +389,7 @@ class _SecuritySetupDialogState extends State<_SecuritySetupDialog> {
onPressed: () async {
await _restoreKeys();
},
child: const Text('I saved it continue'),
child: const Text('I saved it, continue'),
),
],
_Phase.done => [

View File

@@ -1,4 +1,4 @@
// Version: 1.1.0 | Created: 2026-04-02 | Updated: 2026-04-10
// Version: 1.1.1 | Created: 2026-04-02 | Updated: 2026-07-03
// Shared utility for generating a last-message preview string from a Room.
// Used by both RoomsRepository and SyncPersistenceService to avoid duplication.
@@ -12,7 +12,7 @@ String? lastMessagePreview(Room room) {
return switch (lastEvent.type) {
EventTypes.Message => lastEvent.body,
EventTypes.Encrypted => '\u{1F512} Encrypted — use Element to read',
EventTypes.Encrypted => '\u{1F512} Encrypted. Use Element to read',
EventTypes.Sticker => 'Sticker',
_ => null,
};