refactor(apps): 主题系统迁移至 ColorScheme + 扩展架构并支持 Dark Mode

This commit is contained in:
qzl
2026-03-27 19:07:39 +08:00
parent ecc1ec6ce4
commit ae29a8209b
146 changed files with 4301 additions and 3200 deletions
@@ -1,50 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import '../../../../app/router/app_routes.dart';
enum HomeReturnAction { pop, goHome, goHomeForDock }
HomeReturnAction resolveHomeReturnAction({
required bool canPop,
required bool isAuthEntry,
bool forceGoHome = false,
}) {
if (forceGoHome) {
return HomeReturnAction.goHome;
}
if (isAuthEntry) {
return HomeReturnAction.goHome;
}
if (canPop) {
return HomeReturnAction.goHomeForDock;
}
return HomeReturnAction.goHome;
}
void returnToHomePreserveState(
BuildContext context, {
bool isAuthEntry = false,
bool forceGoHome = false,
}) {
final action = resolveHomeReturnAction(
canPop: context.canPop(),
isAuthEntry: isAuthEntry,
forceGoHome: forceGoHome,
);
switch (action) {
case HomeReturnAction.pop:
context.pop();
return;
case HomeReturnAction.goHome:
context.go(AppRoutes.homeMain);
return;
case HomeReturnAction.goHomeForDock:
if (context.canPop()) {
context.pop();
return;
}
context.go(AppRoutes.homeMain);
return;
}
}
@@ -5,15 +5,15 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:social_app/core/chat/agent_stage.dart';
import '../../../../core/network/api_exception.dart';
import '../../../../app/di/injection.dart';
import '../../../../app/router/app_route_observer.dart';
import '../../../../app/router/app_routes.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../chat/presentation/bloc/agent_stage.dart';
import '../../../../data/repositories/inbox_repository.dart';
import '../../../chat/presentation/bloc/chat_bloc.dart';
import '../../../messages/data/inbox_api.dart';
import '../../data/voice_recorder.dart';
import '../controllers/home_keyboard_inset_calculator.dart';
import '../controllers/home_message_viewport_controller.dart';
@@ -47,9 +47,6 @@ const homeConversationStageKey = ValueKey('home_conversation_stage');
const homeBottomInputStackKey = ValueKey('home_bottom_input_stack');
const homeEmptyStateAmbientKey = ValueKey('home_empty_state_ambient');
/// Color constants.
const _chatBgColor = AppColors.slate50;
class HomeScreen extends StatefulWidget {
final VoiceRecorder? voiceRecorder;
final Future<String> Function(String filePath)? onTranscribeAudio;
@@ -76,7 +73,7 @@ class _HomeScreenState extends State<HomeScreen>
final ScrollController _scrollController = ScrollController();
late final ChatBloc _chatBloc;
late final VoiceRecorder _voiceRecorder;
late final InboxApi _inboxApi;
late final InboxRepository _inboxRepository;
late final Future<String> Function(String filePath) _transcribeAudio;
late final AnimationController _listeningAnimationController;
bool _isRecording = false;
@@ -113,7 +110,7 @@ class _HomeScreenState extends State<HomeScreen>
_chatBloc = context.read<ChatBloc>();
}
_voiceRecorder = widget.voiceRecorder ?? RecordVoiceRecorder();
_inboxApi = sl<InboxApi>();
_inboxRepository = sl<InboxRepository>();
_transcribeAudio =
widget.onTranscribeAudio ?? _chatBloc.transcribeAudioFile;
_listeningAnimationController = AnimationController(
@@ -132,7 +129,7 @@ class _HomeScreenState extends State<HomeScreen>
Future<void> _loadUnreadCount() async {
try {
final messages = await _inboxApi.getMessages(isRead: false);
final messages = await _inboxRepository.getMessages(isRead: false);
if (mounted) {
setState(() => _unreadCount = messages.length);
}
@@ -220,8 +217,10 @@ class _HomeScreenState extends State<HomeScreen>
_previousIsLoadingHistory = state.isLoadingHistory;
},
builder: (context, state) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: _chatBgColor,
backgroundColor: colorScheme.surface,
resizeToAvoidBottomInset: false,
body: SafeArea(
maintainBottomViewPadding: true,
@@ -688,6 +687,8 @@ class _HomeEmptyStateAmbient extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Center(
child: IgnorePointer(
child: Container(
@@ -701,9 +702,9 @@ class _HomeEmptyStateAmbient extends StatelessWidget {
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: [
AppColors.homeBackgroundGlowSoft.withValues(alpha: 0.12),
AppColors.homeBackgroundGlow.withValues(alpha: 0.08),
AppColors.homeBackgroundGlowSoft.withValues(alpha: 0.12),
colorScheme.primaryContainer.withValues(alpha: 0.12),
colorScheme.primary.withValues(alpha: 0.08),
colorScheme.primaryContainer.withValues(alpha: 0.12),
],
),
),
@@ -212,7 +212,9 @@ extension _HomeScreenInteractions on _HomeScreenState {
void _showBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
backgroundColor: Theme.of(
context,
).colorScheme.surface.withValues(alpha: 0),
isScrollControlled: true,
builder: (context) => HomeSheet(
onImagesSelected: (images) {
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
class HomeSheet extends StatelessWidget {
final Function(List<XFile>) onImagesSelected;
@@ -11,10 +10,12 @@ class HomeSheet extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
color: const Color(0x4D0F172A),
color: colorScheme.scrim.withValues(alpha: 0.3),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@@ -23,9 +24,11 @@ class HomeSheet extends StatelessWidget {
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: AppColors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(28)),
decoration: BoxDecoration(
color: colorScheme.surface,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(28),
),
),
child: Column(
children: [
@@ -33,7 +36,7 @@ class HomeSheet extends StatelessWidget {
width: 36,
height: 4,
decoration: BoxDecoration(
color: AppColors.slate300,
color: colorScheme.outlineVariant,
borderRadius: BorderRadius.circular(2),
),
),
@@ -79,6 +82,8 @@ class HomeSheet extends StatelessWidget {
required String label,
required VoidCallback onTap,
}) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: onTap,
child: Column(
@@ -88,18 +93,18 @@ class HomeSheet extends StatelessWidget {
width: 72,
height: 72,
decoration: BoxDecoration(
color: AppColors.blue50,
color: colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(16),
),
child: Icon(icon, size: 32, color: AppColors.blue500),
child: Icon(icon, size: 32, color: colorScheme.primary),
),
const SizedBox(height: 12),
Text(
label,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: AppColors.slate700,
color: colorScheme.onSurfaceVariant,
),
),
],
@@ -24,13 +24,15 @@ class HomeAttachmentStrip extends StatelessWidget {
return const SizedBox.shrink();
}
final colorScheme = Theme.of(context).colorScheme;
return Container(
key: homeAttachmentStripKey,
padding: const EdgeInsets.all(AppSpacing.sm),
decoration: BoxDecoration(
color: AppColors.homeAttachmentSurface,
color: colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(AppRadius.xl),
border: Border.all(color: AppColors.homeComposerBorder),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Wrap(
spacing: AppSpacing.sm,
@@ -55,6 +57,7 @@ class _AttachmentPreviewTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
const previewExtent = AppSpacing.xxl * 3 + AppSpacing.sm;
final colorScheme = Theme.of(context).colorScheme;
return Stack(
children: [
@@ -69,12 +72,12 @@ class _AttachmentPreviewTile extends StatelessWidget {
return Container(
width: previewExtent,
height: previewExtent,
color: AppColors.white,
color: colorScheme.surface,
alignment: Alignment.center,
child: const Icon(
child: Icon(
LucideIcons.image,
size: AppSpacing.xl,
color: AppColors.slate400,
color: colorScheme.onSurfaceVariant,
),
);
},
@@ -88,14 +91,14 @@ class _AttachmentPreviewTile extends StatelessWidget {
child: Container(
width: AppSpacing.lg + AppSpacing.sm,
height: AppSpacing.lg + AppSpacing.sm,
decoration: const BoxDecoration(
color: AppColors.red500,
decoration: BoxDecoration(
color: colorScheme.error,
shape: BoxShape.circle,
),
child: const Icon(
child: Icon(
LucideIcons.x,
size: AppSpacing.md,
color: AppColors.white,
color: colorScheme.onError,
),
),
),
@@ -11,13 +11,15 @@ class HomeBackgroundField extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return DecoratedBox(
key: homeBackgroundFieldKey,
decoration: const BoxDecoration(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [AppColors.homeBackgroundTop, AppColors.homeBackgroundBottom],
colors: [colorScheme.surface, colorScheme.surfaceContainerLowest],
),
),
child: const Stack(children: [_HomeTopGlow(), _HomeBottomGlow()]),
@@ -30,6 +32,8 @@ class _HomeTopGlow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Align(
alignment: const Alignment(-0.25, -0.9),
child: IgnorePointer(
@@ -39,10 +43,10 @@ class _HomeTopGlow extends StatelessWidget {
height: AppSpacing.xxl * 7,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(AppSpacing.xxl * 3),
color: AppColors.homeBackgroundGlowSoft.withValues(alpha: 0.28),
color: colorScheme.primaryContainer.withValues(alpha: 0.28),
boxShadow: [
BoxShadow(
color: AppColors.homeBackgroundGlow.withValues(alpha: 0.28),
color: colorScheme.primary.withValues(alpha: 0.2),
blurRadius: AppSpacing.xxl * 3,
spreadRadius: AppSpacing.xl,
),
@@ -59,6 +63,8 @@ class _HomeBottomGlow extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return IgnorePointer(
child: Align(
alignment: Alignment.bottomCenter,
@@ -69,11 +75,11 @@ class _HomeBottomGlow extends StatelessWidget {
width: AppSpacing.xxl * 12,
height: AppSpacing.xxl * 3,
decoration: BoxDecoration(
color: AppColors.homeBackgroundGlowSoft.withValues(alpha: 0.18),
color: colorScheme.primaryContainer.withValues(alpha: 0.18),
borderRadius: BorderRadius.circular(AppSpacing.xxl * 2),
boxShadow: [
BoxShadow(
color: AppColors.homeBackgroundGlow.withValues(alpha: 0.1),
color: colorScheme.primary.withValues(alpha: 0.1),
blurRadius: AppSpacing.xxl,
spreadRadius: AppSpacing.sm,
),
@@ -2,12 +2,12 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import 'package:social_app/core/chat/chat_list_item.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
import '../../../../core/utils/tool_name_localizer.dart';
import '../../../../shared/widgets/app_loading_indicator.dart';
import '../../../chat/data/models/chat_list_item.dart';
import '../../../ui_schema/presentation/widgets/ui_schema_renderer.dart';
const _messagePaddingH = 13.0;
@@ -23,15 +23,16 @@ class HomeChatItemRenderer {
static Widget build(BuildContext context, ChatListItem item) {
switch (item.type) {
case ChatItemType.message:
return _buildMessageItem(item as TextMessageItem);
return _buildMessageItem(context, item as TextMessageItem);
case ChatItemType.toolCall:
return _buildToolCallItem(context, item as ToolCallItem);
case ChatItemType.toolResult:
return _buildToolResultItem(item as ToolResultItem);
return _buildToolResultItem(context, item as ToolResultItem);
}
}
static Widget _buildMessageItem(TextMessageItem item) {
static Widget _buildMessageItem(BuildContext context, TextMessageItem item) {
final colorScheme = Theme.of(context).colorScheme;
final isUser = item.sender == MessageSender.user;
final imageAttachments = _collectRenderableImageAttachments(
item.attachments,
@@ -55,20 +56,26 @@ class HomeChatItemRenderer {
vertical: _messagePaddingV,
),
decoration: BoxDecoration(
color: isUser ? AppColors.blue50 : AppColors.white,
color: isUser
? colorScheme.primaryContainer
: colorScheme.surface,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(_cornerRadius),
topRight: const Radius.circular(_cornerRadius),
bottomLeft: Radius.circular(isUser ? _cornerRadius : 0),
bottomRight: Radius.circular(isUser ? 0 : _cornerRadius),
),
border: isUser ? null : Border.all(color: AppColors.slate300),
border: isUser
? null
: Border.all(color: colorScheme.outlineVariant),
),
child: Text(
item.content,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.slate900,
color: isUser
? colorScheme.onPrimaryContainer
: colorScheme.onSurface,
),
),
),
@@ -79,6 +86,7 @@ class HomeChatItemRenderer {
Padding(
padding: const EdgeInsets.only(top: _attachmentPreviewGap),
child: _buildHistoryAttachmentPreviews(
context,
item.attachments,
imageAttachments: imageAttachments,
),
@@ -88,6 +96,7 @@ class HomeChatItemRenderer {
}
static Widget _buildHistoryAttachmentPreviews(
BuildContext context,
List<Map<String, dynamic>> attachments, {
List<Map<String, dynamic>>? imageAttachments,
}) {
@@ -100,7 +109,9 @@ class HomeChatItemRenderer {
spacing: _attachmentPreviewGap,
runSpacing: _attachmentPreviewGap,
crossAxisAlignment: WrapCrossAlignment.start,
children: renderableAttachments.map(_buildHistoryAttachmentTile).toList(),
children: renderableAttachments
.map((attachment) => _buildHistoryAttachmentTile(context, attachment))
.toList(),
);
}
@@ -122,7 +133,11 @@ class HomeChatItemRenderer {
mimeType.startsWith('image/');
}
static Widget _buildHistoryAttachmentTile(Map<String, dynamic> attachment) {
static Widget _buildHistoryAttachmentTile(
BuildContext context,
Map<String, dynamic> attachment,
) {
final colorScheme = Theme.of(context).colorScheme;
final path = attachment['path'];
final url = attachment['url'];
final isUploading = attachment['uploading'] == true;
@@ -143,11 +158,11 @@ class HomeChatItemRenderer {
);
},
errorBuilder: (context, error, stackTrace) {
return const Center(
return Center(
child: Icon(
LucideIcons.imageOff,
size: _iconSize,
color: AppColors.slate500,
color: colorScheme.onSurfaceVariant,
),
);
},
@@ -157,11 +172,11 @@ class HomeChatItemRenderer {
File(path),
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return const Center(
return Center(
child: Icon(
LucideIcons.imageOff,
size: _iconSize,
color: AppColors.slate500,
color: colorScheme.onSurfaceVariant,
),
);
},
@@ -175,21 +190,21 @@ class HomeChatItemRenderer {
child: Container(
width: _attachmentPreviewSize,
height: _attachmentPreviewSize,
color: AppColors.slate100,
color: colorScheme.surfaceContainerHighest,
child: Stack(
fit: StackFit.expand,
children: [
image,
if (isUploading)
ColoredBox(
color: AppColors.slate900.withValues(alpha: 0.2),
child: const Center(
color: colorScheme.scrim.withValues(alpha: 0.2),
child: Center(
child: AppLoadingIndicator(
variant: AppLoadingVariant.inline,
size: 18,
strokeWidth: 2,
color: AppColors.white,
trackColor: AppColors.slate200,
color: colorScheme.onPrimary,
trackColor: colorScheme.surfaceContainerHighest,
),
),
),
@@ -201,25 +216,26 @@ class HomeChatItemRenderer {
static Widget _buildToolCallItem(BuildContext context, ToolCallItem item) {
final l10n = context.l10n;
final colorScheme = Theme.of(context).colorScheme;
final (statusText, statusColor, statusIcon) = switch (item.status) {
ToolCallStatus.pending => (
l10n.homeToolPreparing,
AppColors.slate500,
colorScheme.onSurfaceVariant,
LucideIcons.clock,
),
ToolCallStatus.executing => (
l10n.homeToolExecuting,
AppColors.blue600,
colorScheme.primary,
LucideIcons.loader,
),
ToolCallStatus.error => (
item.errorMessage ?? l10n.homeToolExecutionFailed,
AppColors.red600,
colorScheme.error,
LucideIcons.alertCircle,
),
ToolCallStatus.completed => (
l10n.homeToolCompleted,
AppColors.emerald600,
colorScheme.tertiary,
LucideIcons.checkCircle,
),
};
@@ -228,9 +244,9 @@ class HomeChatItemRenderer {
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.surfaceInfoLight,
color: colorScheme.surfaceContainerLow,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: AppColors.borderTertiary),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
@@ -239,9 +255,9 @@ class HomeChatItemRenderer {
width: 28,
height: 28,
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.full),
border: Border.all(color: AppColors.borderTertiary),
border: Border.all(color: colorScheme.outlineVariant),
),
child: Icon(statusIcon, size: 14, color: statusColor),
),
@@ -252,10 +268,10 @@ class HomeChatItemRenderer {
children: [
Text(
localizeToolName(item.toolName),
style: const TextStyle(
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w700,
color: AppColors.slate800,
color: colorScheme.onSurface,
),
),
const SizedBox(height: AppSpacing.xs),
@@ -275,21 +291,27 @@ class HomeChatItemRenderer {
);
}
static Widget _buildToolResultItem(ToolResultItem item) {
static Widget _buildToolResultItem(
BuildContext context,
ToolResultItem item,
) {
final colorScheme = Theme.of(context).colorScheme;
final rootNode = item.uiSchema['root'];
final appearance = rootNode is Map<String, dynamic>
? rootNode['appearance'] as String?
: null;
final needsOuterCard = appearance == null || appearance == 'plain';
final schemaContent = UiSchemaRenderer.renderSchema(item.uiSchema);
final schemaContent = UiSchemaRenderer(
colorScheme,
).renderSchema(item.uiSchema);
final wrappedContent = needsOuterCard
? Container(
width: double.infinity,
padding: const EdgeInsets.all(AppSpacing.md),
decoration: BoxDecoration(
color: AppColors.white,
color: colorScheme.surface,
borderRadius: BorderRadius.circular(AppRadius.lg),
border: Border.all(color: AppColors.homeConversationBorder),
border: Border.all(color: colorScheme.outlineVariant),
),
child: schemaContent,
)
@@ -115,6 +115,8 @@ class HomeComposerStack extends StatelessWidget {
}
Widget _buildTextInputContent(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
if (isTranscribing) {
return _buildTranscribingIndicator(context);
}
@@ -126,10 +128,10 @@ class HomeComposerStack extends StatelessWidget {
focusNode: messageFocusNode,
minLines: 1,
maxLines: 1,
style: const TextStyle(
style: TextStyle(
fontSize: AppSpacing.lg,
height: 1,
color: AppColors.slate900,
color: colorScheme.onSurface,
),
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
@@ -137,7 +139,7 @@ class HomeComposerStack extends StatelessWidget {
hintStyle: TextStyle(
fontSize: AppSpacing.lg,
height: 1,
color: AppColors.slate400,
color: colorScheme.onSurfaceVariant,
),
border: InputBorder.none,
enabledBorder: InputBorder.none,
@@ -156,29 +158,31 @@ class HomeComposerStack extends StatelessWidget {
}
Widget _buildTranscribingIndicator(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 18,
height: 18,
child: const AppLoadingIndicator(
child: AppLoadingIndicator(
variant: AppLoadingVariant.inline,
size: 18,
strokeWidth: 2,
color: AppColors.blue600,
trackColor: AppColors.blue100,
color: colorScheme.primary,
trackColor: colorScheme.primaryContainer,
),
),
const SizedBox(width: AppSpacing.sm),
_buildWaveDots(),
_buildWaveDots(context),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: Text(
context.l10n.homeTranscribing,
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: AppColors.blue600,
color: colorScheme.primary,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
@@ -188,7 +192,9 @@ class HomeComposerStack extends StatelessWidget {
);
}
Widget _buildWaveDots() {
Widget _buildWaveDots(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: List.generate(3, (index) {
@@ -197,7 +203,7 @@ class HomeComposerStack extends StatelessWidget {
width: 3,
height: 6 + index * 2,
decoration: BoxDecoration(
color: AppColors.blue500,
color: colorScheme.primary,
borderRadius: BorderRadius.circular(2),
),
);
@@ -11,6 +11,8 @@ class HomeWaitingIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: Row(
@@ -19,18 +21,18 @@ class HomeWaitingIndicator extends StatelessWidget {
SizedBox(
width: 18,
height: 18,
child: const AppLoadingIndicator(
child: AppLoadingIndicator(
variant: AppLoadingVariant.inline,
size: 18,
strokeWidth: 2,
color: AppColors.blue600,
trackColor: AppColors.blue100,
color: colorScheme.primary,
trackColor: colorScheme.primaryContainer,
),
),
SizedBox(width: AppSpacing.sm),
Text(
label,
style: const TextStyle(fontSize: 14, color: AppColors.slate500),
style: TextStyle(fontSize: 14, color: colorScheme.onSurfaceVariant),
),
],
),
@@ -45,6 +47,7 @@ class HomeDateDivider extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final now = DateTime.now();
const weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
final weekday = weekdays[date.weekday - 1];
@@ -62,7 +65,7 @@ class HomeDateDivider extends StatelessWidget {
alignment: Alignment.center,
child: Text(
label,
style: const TextStyle(fontSize: 12, color: AppColors.slate400),
style: TextStyle(fontSize: 12, color: colorScheme.onSurfaceVariant),
),
);
}
@@ -80,22 +83,27 @@ class HomeLoadMoreButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onTap: isLoading ? null : onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
alignment: Alignment.center,
child: isLoading
? const AppLoadingIndicator(
? AppLoadingIndicator(
variant: AppLoadingVariant.inline,
size: 14,
strokeWidth: 1.5,
color: AppColors.slate400,
trackColor: AppColors.slate200,
color: colorScheme.onSurfaceVariant,
trackColor: colorScheme.surfaceContainerHighest,
)
: Text(
context.l10n.homeViewHistory,
style: const TextStyle(fontSize: 12, color: AppColors.slate400),
style: TextStyle(
fontSize: 12,
color: colorScheme.onSurfaceVariant,
),
),
),
);
@@ -22,6 +22,8 @@ class HomeFloatingHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Container(
key: homeFloatingHeaderKey,
padding: const EdgeInsets.fromLTRB(
@@ -30,9 +32,9 @@ class HomeFloatingHeader extends StatelessWidget {
AppSpacing.lg,
AppSpacing.xs,
),
decoration: const BoxDecoration(
color: AppColors.homeToolbarSurface,
border: Border(bottom: BorderSide(color: AppColors.homeToolbarBorder)),
decoration: BoxDecoration(
color: colorScheme.surface.withValues(alpha: 0.95),
border: Border(bottom: BorderSide(color: colorScheme.outlineVariant)),
),
child: Stack(
alignment: Alignment.center,
@@ -61,7 +63,7 @@ class HomeFloatingHeader extends StatelessWidget {
),
],
),
const IgnorePointer(
IgnorePointer(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: AppSpacing.xl * 3),
child: Text(
@@ -72,7 +74,7 @@ class HomeFloatingHeader extends StatelessWidget {
style: TextStyle(
fontSize: AppSpacing.lg + (AppSpacing.xs / 2),
fontWeight: FontWeight.w600,
color: AppColors.slate800,
color: colorScheme.onSurface,
),
),
),
@@ -91,6 +93,8 @@ class _HeaderIconButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return IconButton(
visualDensity: VisualDensity.compact,
padding: const EdgeInsets.all(AppSpacing.xs),
@@ -99,7 +103,7 @@ class _HeaderIconButton extends StatelessWidget {
minHeight: AppSpacing.xxl + AppSpacing.lg,
),
onPressed: onPressed,
icon: Icon(icon, size: AppSpacing.xxl, color: AppColors.slate900),
icon: Icon(icon, size: AppSpacing.xxl, color: colorScheme.onSurface),
);
}
}
@@ -112,6 +116,8 @@ class _MessagesButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return IconButton(
visualDensity: VisualDensity.compact,
padding: const EdgeInsets.all(AppSpacing.xs),
@@ -123,10 +129,10 @@ class _MessagesButton extends StatelessWidget {
icon: Stack(
clipBehavior: Clip.none,
children: [
const Icon(
Icon(
LucideIcons.messageSquare,
size: AppSpacing.xxl,
color: AppColors.slate900,
color: colorScheme.onSurface,
),
if (unreadCount > 0)
Positioned(
@@ -138,7 +144,7 @@ class _MessagesButton extends StatelessWidget {
vertical: AppSpacing.xs / 2,
),
decoration: BoxDecoration(
color: AppColors.red500,
color: colorScheme.error,
borderRadius: BorderRadius.circular(AppSpacing.sm),
),
constraints: const BoxConstraints(
@@ -148,10 +154,10 @@ class _MessagesButton extends StatelessWidget {
child: Text(
unreadCount > 99 ? '99+' : unreadCount.toString(),
textAlign: TextAlign.center,
style: const TextStyle(
style: TextStyle(
fontSize: AppSpacing.sm + (AppSpacing.xs / 2),
fontWeight: FontWeight.w600,
color: AppColors.white,
color: colorScheme.onError,
),
),
),
@@ -3,13 +3,6 @@ import 'package:flutter/material.dart';
import '../../../../core/l10n/l10n.dart';
import '../../../../core/theme/design_tokens.dart';
const _recordingCancelTopColor = AppColors.warningBackground;
const _recordingCancelBottomColor = AppColors.red400;
const _recordingCancelLabelColor = AppColors.red600;
const _recordingActiveTopColor = AppColors.blue50;
const _recordingActiveBottomColor = AppColors.blue400;
const _recordingActiveLabelColor = AppColors.white;
class HomeRecordingOverlay extends StatelessWidget {
const HomeRecordingOverlay({
super.key,
@@ -22,15 +15,15 @@ class HomeRecordingOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final topColor = isCancel
? _recordingCancelTopColor
: _recordingActiveTopColor;
final bottomColor = isCancel
? _recordingCancelBottomColor
: _recordingActiveBottomColor;
? colorScheme.errorContainer
: colorScheme.primaryContainer;
final bottomColor = isCancel ? colorScheme.error : colorScheme.primary;
final labelColor = isCancel
? _recordingCancelLabelColor
: _recordingActiveLabelColor;
? colorScheme.onErrorContainer
: colorScheme.onPrimaryContainer;
final barColor = isCancel ? colorScheme.error : colorScheme.primary;
final label = isCancel
? context.l10n.homeRecordingReleaseCancel
: context.l10n.homeRecordingHintReleaseSend;
@@ -73,7 +66,7 @@ class HomeRecordingOverlay extends StatelessWidget {
const SizedBox(height: AppSpacing.md),
_WaveDots(
listeningAnimation: listeningAnimation,
barColor: isCancel ? AppColors.red500 : AppColors.blue500,
barColor: barColor,
),
],
),
@@ -12,6 +12,8 @@ class HomeUnreadBadge extends StatelessWidget {
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return AppPressable(
onTap: onTap,
child: Container(
@@ -20,11 +22,11 @@ class HomeUnreadBadge extends StatelessWidget {
vertical: AppSpacing.sm,
),
decoration: BoxDecoration(
color: AppColors.blue600,
color: colorScheme.primary,
borderRadius: BorderRadius.circular(AppRadius.full),
boxShadow: [
BoxShadow(
color: AppColors.slate900.withValues(alpha: 0.18),
color: colorScheme.shadow.withValues(alpha: 0.18),
blurRadius: AppRadius.md,
offset: const Offset(0, AppSpacing.xs),
),
@@ -32,9 +34,9 @@ class HomeUnreadBadge extends StatelessWidget {
),
child: Text(
context.l10n.homeUnreadMessages(count),
style: const TextStyle(
color: AppColors.white,
fontSize: 12,
style: TextStyle(
color: colorScheme.onPrimary,
fontSize: AppSpacing.md,
fontWeight: FontWeight.w600,
),
),