mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Summary - reorganize mobile settings around profile, appearance, and connection cards - add System/Light/Dark theme pairing, accent selection, and the Buzz gradient theme - align avatar badges, status editing, and supporting mobile chrome ## Test plan - `just mobile-check` - `just mobile-test`
119 lines
4.1 KiB
Dart
119 lines
4.1 KiB
Dart
import 'package:app_badge_plus/app_badge_plus.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
import 'features/channels/unread_badge/unread_badge_provider.dart';
|
|
import 'features/home/home_page.dart';
|
|
import 'features/pairing/pairing_page.dart';
|
|
import 'features/channels/agent_activity/observer_subscription.dart';
|
|
import 'features/channels/deep_link_dispatcher.dart';
|
|
import 'features/profile/user_status_cache_provider.dart';
|
|
import 'features/profile/settings_profile_header.dart';
|
|
import 'features/settings/settings_page.dart';
|
|
import 'shared/auth/auth.dart';
|
|
import 'shared/deeplink/pending_deep_link_provider.dart';
|
|
import 'shared/relay/relay.dart';
|
|
import 'shared/theme/theme.dart';
|
|
|
|
class App extends HookConsumerWidget {
|
|
const App({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final themeMode = ref.watch(themeProvider);
|
|
final accentIndex = ref.watch(accentProvider);
|
|
final schemeName = ref.watch(schemeProvider);
|
|
final authState = ref.watch(authProvider);
|
|
|
|
final resolved = resolveSchemes(schemeName, themeMode);
|
|
final lightScheme = applyAccent(resolved.light, accentIndex);
|
|
final darkScheme = applyAccent(resolved.dark, accentIndex);
|
|
// Light/Dark modes pin the brightness; System leaves it null so Flutter
|
|
// follows the OS across the selected theme and its pair.
|
|
final effectiveMode = resolved.forcedMode ?? themeMode;
|
|
|
|
// Derive the gradient from the themes that produced each color scheme.
|
|
// This keeps fallbacks and pinned brightness changes aligned with the
|
|
// rendered palette rather than the raw persisted selection.
|
|
final buzzLightGradient = buzzTopSectionGradient(
|
|
resolved.lightTheme?.name ?? '',
|
|
lightScheme.brightness,
|
|
);
|
|
final buzzDarkGradient = buzzTopSectionGradient(
|
|
resolved.darkTheme?.name ?? '',
|
|
darkScheme.brightness,
|
|
);
|
|
|
|
// Eagerly initialize websocket session and lifecycle observer when
|
|
// authenticated. These providers connect and manage the websocket.
|
|
if (authState.value?.status == AuthStatus.authenticated) {
|
|
ref.watch(relaySessionProvider);
|
|
ref.watch(observerRelayProvider);
|
|
ref.watch(appLifecycleProvider);
|
|
ref.watch(userStatusCacheProvider);
|
|
}
|
|
|
|
// Start listening for buzz:// links immediately (even pre-auth) so a
|
|
// cold-start link survives until the authenticated UI can dispatch it.
|
|
ref.watch(pendingDeepLinkProvider);
|
|
|
|
void applyBadge(UnreadBadgeState state) {
|
|
if (state.highPriorityCount > 0) {
|
|
AppBadgePlus.updateBadge(state.highPriorityCount);
|
|
} else if (state.generalUnreadCount > 0) {
|
|
AppBadgePlus.updateBadge(1);
|
|
} else {
|
|
AppBadgePlus.updateBadge(0);
|
|
}
|
|
}
|
|
|
|
useEffect(() {
|
|
applyBadge(ref.read(unreadBadgeProvider));
|
|
return null;
|
|
}, const []);
|
|
ref.listen<UnreadBadgeState>(unreadBadgeProvider, (_, next) {
|
|
applyBadge(next);
|
|
});
|
|
|
|
return MaterialApp(
|
|
title: 'Buzz',
|
|
theme: AppTheme.light(
|
|
colorScheme: lightScheme,
|
|
topSectionGradient: buzzLightGradient,
|
|
),
|
|
darkTheme: AppTheme.dark(
|
|
colorScheme: darkScheme,
|
|
topSectionGradient: buzzDarkGradient,
|
|
),
|
|
themeMode: effectiveMode,
|
|
home: authState.when(
|
|
loading: () => const _SplashScreen(),
|
|
error: (_, _) => const PairingPage(),
|
|
data: (state) => switch (state.status) {
|
|
AuthStatus.authenticated => const DeepLinkDispatcher(
|
|
child: HomePage(settingsPageBuilder: _buildSettingsPage),
|
|
),
|
|
_ => const DeepLinkDispatcher(
|
|
dispatchMessageLinks: false,
|
|
child: PairingPage(),
|
|
),
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget _buildSettingsPage(BuildContext context) =>
|
|
const SettingsPage(profileHeader: SettingsProfileHeader());
|
|
|
|
class _SplashScreen extends StatelessWidget {
|
|
const _SplashScreen();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return const Scaffold(body: Center(child: CircularProgressIndicator()));
|
|
}
|
|
}
|