mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
Align mobile appearance and channels with desktop (#8358)
Aligns mobile with desktop's neutral palette and six chat colors. Appearance now offers Light, Dark, and System instead of themes, while preserving community preferences and the single Home and Chat preview pair. Chat colors survive local preference reloads without historical-index remapping, and the Chat preview shows the draft outgoing bubble color. Channel and section icons are removed, section chevrons sit beside names, and section dividers use 7% opacity. DM avatars remain. Validation: `just mobile-check`, 35 focused tests, and the full mobile suite passed (3,351 tests; 4 skipped). Updated light/dark previews were rendered and visually checked. The earlier release was verified on iPhone; this follow-up has not been reinstalled. The earlier repository-wide lint passed, but full `just ci` was stopped for low disk space. Hosted CI is running for the new head. **Intentional design parity:** Section labels and chevrons retain desktop’s `--text-unavailable` colors: light `#959595`, dark `#595959`. Verified against `block/buzz-app` main at `0201b54c496120a44e020834663dc534cae9a79f`. These colors do not meet 4.5:1 text contrast on the mobile section surfaces; the product decision for this PR is to preserve desktop parity and leave them unchanged. Kenneth explicitly accepts this known contrast tradeoff for this PR/release and requests that reviewers treat it as a scoped product exception rather than a blocker. This does not claim WCAG compliance or change the repository-wide accessibility target. Any future contrast adjustment should be coordinated across desktop and mobile. ### Snapshots Channel list: physical iPhone. Appearance and chat colors: Flutter-rendered previews. | Channels | Light | Dark | Chat colors | | --- | --- | --- | --- | |  |  |  |  | --------- Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -33,8 +33,8 @@ class AppTheme {
|
||||
}) {
|
||||
final scheme = colorScheme ?? lightColorScheme;
|
||||
final appColors = AppColors(
|
||||
success: const Color(0xFF40A02B), // Catppuccin Latte Green — universal
|
||||
warning: const Color(0xFFDF8E1D), // Latte Yellow
|
||||
success: const Color(0xFF218358), // Desktop green-11
|
||||
warning: const Color(0xFFAB6400), // Desktop amber-11
|
||||
accent: scheme.tertiary,
|
||||
huddleDrawerSurface: const Color(0xFF000000),
|
||||
huddleControlSurface: const Color(0xFF333333),
|
||||
@@ -57,10 +57,8 @@ class AppTheme {
|
||||
}) {
|
||||
final scheme = colorScheme ?? darkColorScheme;
|
||||
final appColors = AppColors(
|
||||
success: const Color(
|
||||
0xFFA6DA95,
|
||||
), // Catppuccin Macchiato Green — universal
|
||||
warning: const Color(0xFFEED49F), // Macchiato Yellow
|
||||
success: const Color(0xFF3DD68C), // Desktop green-11
|
||||
warning: const Color(0xFFFFCA16), // Desktop amber-11
|
||||
accent: scheme.tertiary,
|
||||
huddleDrawerSurface: scheme.primaryContainer,
|
||||
huddleControlSurface: Color.alphaBlend(
|
||||
|
||||
64
mobile/lib/shared/theme/chat_colors.dart
Normal file
64
mobile/lib/shared/theme/chat_colors.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'accent_colors.dart';
|
||||
|
||||
/// Desktop message palette, in picker order. Wire values remain compatible
|
||||
/// with existing community preferences; fills match buzz-app bubble tokens.
|
||||
const chatColorChoices = [
|
||||
AccentColor(
|
||||
name: 'Neutral',
|
||||
light: Color(0xFFE9E9EA),
|
||||
dark: Color(0xFF2E2E2E),
|
||||
wireValue: 'neutral',
|
||||
),
|
||||
AccentColor(
|
||||
name: 'Blue',
|
||||
light: Color(0xFF0077DB),
|
||||
dark: Color(0xFF0077DB),
|
||||
wireValue: '#3b82f6',
|
||||
),
|
||||
AccentColor(
|
||||
name: 'Green',
|
||||
light: Color(0xFF008934),
|
||||
dark: Color(0xFF008934),
|
||||
wireValue: '#22c55e',
|
||||
),
|
||||
AccentColor(
|
||||
name: 'Orange',
|
||||
light: Color(0xFFC95100),
|
||||
dark: Color(0xFFC95100),
|
||||
wireValue: '#f97316',
|
||||
),
|
||||
AccentColor(
|
||||
name: 'Pink',
|
||||
light: Color(0xFFD33785),
|
||||
dark: Color(0xFFD33785),
|
||||
wireValue: '#ec4899',
|
||||
),
|
||||
AccentColor(
|
||||
name: 'Lilac',
|
||||
light: Color(0xFFA94AD8),
|
||||
dark: Color(0xFFA94AD8),
|
||||
wireValue: '#c0a2f1',
|
||||
),
|
||||
];
|
||||
|
||||
/// Resolves old community colors using desktop's retired-color aliases.
|
||||
int chatColorIndex(String wireValue) {
|
||||
final normalized = switch (wireValue) {
|
||||
'#06b6d4' || '#6366f1' => '#3b82f6',
|
||||
'#ef4444' => '#ec4899',
|
||||
'#a855f7' => '#c0a2f1',
|
||||
_ => wireValue,
|
||||
};
|
||||
final index = chatColorChoices.indexWhere(
|
||||
(color) => color.wireValue == normalized,
|
||||
);
|
||||
return index < 0 ? 0 : index;
|
||||
}
|
||||
|
||||
/// Resolves a picker swatch for the active appearance mode.
|
||||
Color chatColorForScheme(ColorScheme scheme, int index) {
|
||||
final color = chatColorChoices[index];
|
||||
return scheme.brightness == Brightness.dark ? color.dark : color.light;
|
||||
}
|
||||
@@ -2,70 +2,78 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'accent_colors.dart';
|
||||
|
||||
// Catppuccin Latte (mauve accent) — matches Buzz desktop light theme
|
||||
// Buzz desktop design-system tokens: neutral surfaces/text and Radix status roles.
|
||||
// Source: buzz-app/src/shared/design-system/styles/tokens.css.
|
||||
const lightColorScheme = ColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primary: Color(0xFF8839EF), // Latte Mauve
|
||||
onPrimary: Color(0xFFEFF1F5), // Latte Base
|
||||
primaryContainer: Color(0xFFE6E9EF), // Latte Mantle
|
||||
onPrimaryContainer: Color(0xFF4C4F69), // Latte Text
|
||||
secondary: Color(0xFF6C6F85), // Latte Subtext0
|
||||
primary: Color(0xFF262626),
|
||||
onPrimary: Color(0xFFFFFFFF),
|
||||
primaryContainer: Color(0xFFF0F0F0),
|
||||
onPrimaryContainer: Color(0xFF0A0A0A),
|
||||
secondary: Color(0xFF646464),
|
||||
onSecondary: Color(0xFFFFFFFF),
|
||||
secondaryContainer: Color(0xFFCCD0DA), // Latte Surface1
|
||||
onSecondaryContainer: Color(0xFF4C4F69), // Latte Text
|
||||
tertiary: Color(0xFF1E66F5), // Latte Blue
|
||||
secondaryContainer: Color(0xFFF0F0F0),
|
||||
onSecondaryContainer: Color(0xFF0A0A0A),
|
||||
tertiary: Color(0xFF0D74CE),
|
||||
onTertiary: Color(0xFFFFFFFF),
|
||||
tertiaryContainer: Color(0xFFBCC0CC), // Latte Surface2
|
||||
onTertiaryContainer: Color(0xFF1E66F5), // Latte Blue
|
||||
error: Color(0xFFD20F39), // Latte Red
|
||||
tertiaryContainer: Color(0xFFE6F4FE),
|
||||
onTertiaryContainer: Color(0xFF113264),
|
||||
error: Color(0xFFCE2C31),
|
||||
onError: Color(0xFFFFFFFF),
|
||||
errorContainer: Color(0xFFFDD8E0),
|
||||
onErrorContainer: Color(0xFFD20F39),
|
||||
errorContainer: Color(0xFFFEEBEC),
|
||||
onErrorContainer: Color(0xFF641723),
|
||||
surface: Color(0xFFFFFFFF),
|
||||
onSurface: Color(0xFF4C4F69), // Latte Text
|
||||
onSurfaceVariant: Color(0xFF6C6F85), // Latte Subtext0
|
||||
outline: Color(0xFFBCC0CC), // Latte Surface2
|
||||
outlineVariant: Color(0xFFCCD0DA), // Latte Surface1
|
||||
inverseSurface: Color(0xFF4C4F69), // Latte Text
|
||||
onInverseSurface: Color(0xFFEFF1F5), // Latte Base
|
||||
inversePrimary: Color(0xFFA875F5), // Macchiato Mauve (saturated)
|
||||
onSurface: Color(0xFF0A0A0A),
|
||||
onSurfaceVariant: Color(0xFF646464),
|
||||
outline: Color(0xFFE8E8E8),
|
||||
outlineVariant: Color(0xFFE8E8E8),
|
||||
inverseSurface: Color(0xFF262626),
|
||||
onInverseSurface: Color(0xFFFFFFFF),
|
||||
inversePrimary: Color(0xFFE5E5E5),
|
||||
shadow: Color(0xFF000000),
|
||||
scrim: Color(0xFF000000),
|
||||
surfaceTint: Color(0xFF8839EF), // Latte Mauve
|
||||
surfaceContainerHighest: Color(0xFFFFFFFF),
|
||||
surfaceContainerLowest: Color(0xFFFFFFFF),
|
||||
surfaceContainerLow: Color(0xFFF5F5F6),
|
||||
surfaceContainer: Color(0xFFF0F0F0),
|
||||
surfaceContainerHigh: Color(0xFFE8E8E8),
|
||||
surfaceContainerHighest: Color(0xFFF5F5F6),
|
||||
surfaceTint: Colors.transparent,
|
||||
);
|
||||
|
||||
// Catppuccin Macchiato (mauve accent) — matches Buzz desktop dark theme
|
||||
const darkColorScheme = ColorScheme(
|
||||
brightness: Brightness.dark,
|
||||
primary: Color(0xFFA875F5), // Macchiato Mauve (saturated)
|
||||
onPrimary: Color(0xFF24273A), // Macchiato Base
|
||||
primaryContainer: Color(0xFF363A4F), // Macchiato Surface0
|
||||
onPrimaryContainer: Color(0xFFCAD3F5), // Macchiato Text
|
||||
secondary: Color(0xFFA5ADCB), // Macchiato Subtext0
|
||||
onSecondary: Color(0xFF24273A), // Macchiato Base
|
||||
secondaryContainer: Color(0xFF494D64), // Macchiato Surface1
|
||||
onSecondaryContainer: Color(0xFFCAD3F5), // Macchiato Text
|
||||
tertiary: Color(0xFF8AADF4), // Macchiato Blue
|
||||
onTertiary: Color(0xFF24273A), // Macchiato Base
|
||||
tertiaryContainer: Color(0xFF363A4F), // Macchiato Surface0
|
||||
onTertiaryContainer: Color(0xFF8AADF4), // Macchiato Blue
|
||||
error: Color(0xFFED8796), // Macchiato Red
|
||||
onError: Color(0xFF24273A), // Macchiato Base
|
||||
errorContainer: Color(0xFF3D2030),
|
||||
onErrorContainer: Color(0xFFED8796),
|
||||
surface: Color(0xFF24273A), // Macchiato Base
|
||||
onSurface: Color(0xFFCAD3F5), // Macchiato Text
|
||||
onSurfaceVariant: Color(0xFFA5ADCB), // Macchiato Subtext0
|
||||
outline: Color(0xFF494D64), // Macchiato Surface1
|
||||
outlineVariant: Color(0xFF363A4F), // Macchiato Surface0
|
||||
inverseSurface: Color(0xFFCAD3F5), // Macchiato Text
|
||||
onInverseSurface: Color(0xFF24273A), // Macchiato Base
|
||||
inversePrimary: Color(0xFF8839EF), // Latte Mauve
|
||||
primary: Color(0xFFE5E5E5),
|
||||
onPrimary: Color(0xFF0F0F0F),
|
||||
primaryContainer: Color(0xFF2A2A2A),
|
||||
onPrimaryContainer: Color(0xFFF5F5F5),
|
||||
secondary: Color(0xFFC1C1C1),
|
||||
onSecondary: Color(0xFF0F0F0F),
|
||||
secondaryContainer: Color(0xFF2A2A2A),
|
||||
onSecondaryContainer: Color(0xFFF5F5F5),
|
||||
tertiary: Color(0xFF70B8FF),
|
||||
onTertiary: Color(0xFF0F0F0F),
|
||||
tertiaryContainer: Color(0xFF0D2847),
|
||||
onTertiaryContainer: Color(0xFFC2E6FF),
|
||||
error: Color(0xFFFF9592),
|
||||
onError: Color(0xFF191111),
|
||||
errorContainer: Color(0xFF3B1219),
|
||||
onErrorContainer: Color(0xFFFFD1D9),
|
||||
surface: Color(0xFF1C1C1C),
|
||||
onSurface: Color(0xFFF5F5F5),
|
||||
onSurfaceVariant: Color(0xFFC1C1C1),
|
||||
outline: Color(0xFF333333),
|
||||
outlineVariant: Color(0xFF333333),
|
||||
inverseSurface: Color(0xFFE5E5E5),
|
||||
onInverseSurface: Color(0xFF0F0F0F),
|
||||
inversePrimary: Color(0xFF262626),
|
||||
shadow: Color(0xFF000000),
|
||||
scrim: Color(0xFF000000),
|
||||
surfaceTint: Color(0xFFA875F5), // Macchiato Mauve (saturated)
|
||||
surfaceContainerHighest: Color(0xFF1E2030), // Macchiato Mantle
|
||||
surfaceContainerLowest: Color(0xFF0F0F0F),
|
||||
surfaceContainerLow: Color(0xFF161616),
|
||||
surfaceContainer: Color(0xFF232323),
|
||||
surfaceContainerHigh: Color(0xFF2A2A2A),
|
||||
surfaceContainerHighest: Color(0xFF2A2A2A),
|
||||
surfaceTint: Colors.transparent,
|
||||
);
|
||||
|
||||
/// Compute a contrast-safe foreground color for a given background.
|
||||
|
||||
@@ -70,6 +70,7 @@ class CommunityThemeStorage {
|
||||
static const _prefix = 'buzz-community-theme.v1';
|
||||
static const _outboxPrefix = 'buzz-community-theme-outbox.v1';
|
||||
static const _migrationPrefix = 'buzz-community-theme-migrated.v1';
|
||||
static const _localPreferenceKey = 'buzz-local-theme.v1';
|
||||
static const _legacyModeKey = 'buzz_theme_mode';
|
||||
static const _legacyAccentKey = 'buzz_accent_color';
|
||||
static const _legacySchemeKey = 'buzz_color_scheme';
|
||||
@@ -134,15 +135,14 @@ class CommunityThemeStorage {
|
||||
prefs.setBool('$_migrationPrefix:$pubkey', true);
|
||||
|
||||
Future<void> writeLegacy(CommunityThemePreference preference) async {
|
||||
await prefs.setString(_legacyModeKey, preference.mode.name);
|
||||
await prefs.setString(_legacySchemeKey, preference.theme);
|
||||
await prefs.setInt(
|
||||
_legacyAccentKey,
|
||||
accentIndexForWireValue(preference.accent) ?? defaultAccentIndex,
|
||||
);
|
||||
// New writes use one versioned snapshot. Historical integer indexes remain
|
||||
// read-only migration input and must never encode the current palette.
|
||||
await prefs.setString(_localPreferenceKey, jsonEncode(preference.toJson()));
|
||||
}
|
||||
|
||||
CommunityThemePreference legacyPreference() {
|
||||
final local = _readKey(_localPreferenceKey);
|
||||
if (local != null) return local;
|
||||
final modeName = prefs.getString(_legacyModeKey);
|
||||
final mode =
|
||||
ThemeMode.values.where((value) => value.name == modeName).firstOrNull ??
|
||||
|
||||
58
mobile/lib/shared/theme/mobile_appearance.dart
Normal file
58
mobile/lib/shared/theme/mobile_appearance.dart
Normal file
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'chat_colors.dart';
|
||||
import 'color_scheme.dart';
|
||||
import 'app_theme.dart';
|
||||
|
||||
/// Chat-only colors, independent of controls and navigation.
|
||||
@immutable
|
||||
class ChatColors extends ThemeExtension<ChatColors> {
|
||||
/// Creates the outgoing message palette.
|
||||
const ChatColors({required this.outgoing, required this.foreground});
|
||||
|
||||
/// Fill for messages authored by the active signing identity.
|
||||
final Color outgoing;
|
||||
|
||||
/// Desktop text color paired with the outgoing fill.
|
||||
final Color foreground;
|
||||
|
||||
/// Reads chat colors while supporting isolated widget previews.
|
||||
static ChatColors of(BuildContext context) =>
|
||||
Theme.of(context).extension<ChatColors>() ??
|
||||
ChatColors(
|
||||
outgoing: Theme.of(context).colorScheme.primary,
|
||||
foreground: contrastForeground(Theme.of(context).colorScheme.primary),
|
||||
);
|
||||
|
||||
@override
|
||||
ChatColors copyWith({Color? outgoing, Color? foreground}) => ChatColors(
|
||||
outgoing: outgoing ?? this.outgoing,
|
||||
foreground: foreground ?? this.foreground,
|
||||
);
|
||||
|
||||
@override
|
||||
ChatColors lerp(covariant ChatColors? other, double t) => other == null
|
||||
? this
|
||||
: ChatColors(
|
||||
outgoing: Color.lerp(outgoing, other.outgoing, t)!,
|
||||
foreground: Color.lerp(foreground, other.foreground, t)!,
|
||||
);
|
||||
}
|
||||
|
||||
/// Builds the single mobile palette. Legacy theme names remain readable for
|
||||
/// community preference compatibility, but never select an application palette.
|
||||
ThemeData mobileAppearanceTheme(Brightness brightness, String chatColor) {
|
||||
final theme = brightness == Brightness.dark
|
||||
? AppTheme.dark()
|
||||
: AppTheme.light();
|
||||
final index = chatColorIndex(chatColor);
|
||||
return theme.copyWith(
|
||||
extensions: [
|
||||
...theme.extensions.values,
|
||||
ChatColors(
|
||||
outgoing: chatColorForScheme(theme.colorScheme, index),
|
||||
foreground: index == 0 ? theme.colorScheme.onSurface : Colors.white,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -17,3 +17,5 @@ export 'theme_pairs.dart';
|
||||
export 'theme_provider.dart';
|
||||
export 'text_theme.dart' show bodyExtraSmallTextStyle;
|
||||
export 'utility_surface_theme.dart';
|
||||
export 'mobile_appearance.dart';
|
||||
export 'chat_colors.dart';
|
||||
|
||||
Reference in New Issue
Block a user