refactor(apps): 主题系统迁移至 ColorScheme + 扩展架构并支持 Dark Mode
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user