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:
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.',
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 => [
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user