261 lines
7.3 KiB
Dart
261 lines
7.3 KiB
Dart
import 'package:fluent_ui/fluent_ui.dart';
|
||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||
import 'package:provider/provider.dart';
|
||
|
||
import '../providers/chat_provider.dart';
|
||
import '../widgets/message_bubble.dart';
|
||
import '../widgets/typing_indicator.dart';
|
||
import '../widgets/chat_input.dart';
|
||
import 'map_screen.dart'; // 导入地图界面
|
||
|
||
|
||
// 定义一个 GlobalKey 来访问 _ChatScreenState
|
||
final GlobalKey<_ChatScreenState> chatScreenStateKey = GlobalKey<_ChatScreenState>();
|
||
|
||
|
||
class ChatScreen extends StatefulWidget {
|
||
const ChatScreen({super.key});
|
||
|
||
@override
|
||
State<ChatScreen> createState() => _ChatScreenState();
|
||
}
|
||
|
||
class _ChatScreenState extends State<ChatScreen> {
|
||
final ScrollController _scrollController = ScrollController();
|
||
bool _mapViewOpen = false; // 控制地图界面是否展开
|
||
|
||
void _toggleMapView() {
|
||
setState(() {
|
||
_mapViewOpen = !_mapViewOpen;
|
||
});
|
||
}
|
||
|
||
void _scrollToBottom() {
|
||
if (_scrollController.hasClients) {
|
||
Future.delayed(const Duration(milliseconds: 100), () {
|
||
_scrollController.animateTo(
|
||
_scrollController.position.maxScrollExtent,
|
||
duration: const Duration(milliseconds: 300),
|
||
curve: Curves.easeOut,
|
||
);
|
||
});
|
||
}
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return ScaffoldPage(
|
||
header: PageHeader(
|
||
title: Row(
|
||
children: [
|
||
Container(
|
||
width: 32,
|
||
height: 32,
|
||
decoration: BoxDecoration(
|
||
color: FluentTheme.of(context).accentColor,
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Icon(
|
||
FluentIcons.robot,
|
||
color: Colors.white,
|
||
size: 18,
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
const Text('AI Chat'),
|
||
],
|
||
),
|
||
commandBar: CommandBar(
|
||
mainAxisAlignment: MainAxisAlignment.end,
|
||
primaryItems: [
|
||
CommandBarButton(
|
||
icon: const Icon(FluentIcons.add),
|
||
label: const Text('New Chat'),
|
||
onPressed: () {
|
||
context.read<ChatProvider>().clearMessages();
|
||
},
|
||
),
|
||
// 添加地图按钮
|
||
CommandBarButton(
|
||
icon: const Icon(FluentIcons.map_layers),
|
||
label: const Text('Map'),
|
||
onPressed: _toggleMapView, // 切换地图视图
|
||
),
|
||
],
|
||
),
|
||
),
|
||
content: Row( // 修改为Row布局,实现左右分屏
|
||
children: [
|
||
// 聊天界面部分,使用Expanded来正确分配空间
|
||
Expanded(
|
||
flex: 1,
|
||
child: _buildChatContent(),
|
||
),
|
||
|
||
// 地图界面部分,根据状态决定是否显示
|
||
if (_mapViewOpen)
|
||
Expanded(
|
||
child: const MapUiPage(), // 使用现有的地图页面
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
// 提取聊天内容部分到单独的方法
|
||
Widget _buildChatContent() {
|
||
return Column(
|
||
children: [
|
||
Expanded(child: _buildMessageList()),
|
||
_buildInputArea(),
|
||
],
|
||
);
|
||
}
|
||
|
||
Widget _buildMessageList() {
|
||
return Consumer<ChatProvider>(
|
||
builder: (context, chatProvider, child) {
|
||
final messages = chatProvider.messages;
|
||
final isTyping = chatProvider.isTyping;
|
||
final currentResponse = chatProvider.currentAiResponse;
|
||
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
_scrollToBottom();
|
||
});
|
||
|
||
if (messages.isEmpty && !isTyping) {
|
||
return _buildEmptyState();
|
||
}
|
||
|
||
return ListView.builder(
|
||
controller: _scrollController,
|
||
padding: const EdgeInsets.all(16),
|
||
itemCount: messages.length + (isTyping ? 1 : 0),
|
||
itemBuilder: (context, index) {
|
||
if (index == messages.length && isTyping) {
|
||
if (currentResponse.isNotEmpty) {
|
||
return _buildTypingResponse(currentResponse);
|
||
}
|
||
return const TypingIndicator();
|
||
}
|
||
return MessageBubble(message: messages[index], showAvatar: true);
|
||
},
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildTypingResponse(String content) {
|
||
final theme = FluentTheme.of(context);
|
||
return Padding(
|
||
padding: const EdgeInsets.only(bottom: 16),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Container(
|
||
width: 36,
|
||
height: 36,
|
||
decoration: BoxDecoration(
|
||
color: theme.accentColor,
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Icon(FluentIcons.robot, color: Colors.white, size: 18),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Flexible(
|
||
child: Card(
|
||
padding: const EdgeInsets.all(12),
|
||
child: MarkdownBody(
|
||
data: content,
|
||
styleSheet: MarkdownStyleSheet(
|
||
p: theme.typography.body,
|
||
h1: theme.typography.title,
|
||
h2: theme.typography.subtitle,
|
||
h3: theme.typography.bodyLarge,
|
||
code: TextStyle(
|
||
fontFamily: 'Consolas',
|
||
backgroundColor: theme.cardColor,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 48),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildEmptyState() {
|
||
final theme = FluentTheme.of(context);
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: [
|
||
Container(
|
||
width: 80,
|
||
height: 80,
|
||
decoration: BoxDecoration(
|
||
color: theme.accentColor,
|
||
borderRadius: BorderRadius.circular(20),
|
||
),
|
||
child: Icon(FluentIcons.robot, color: Colors.white, size: 40),
|
||
),
|
||
const SizedBox(height: 24),
|
||
Text('Hello! I\'m your AI Assistant', style: theme.typography.title),
|
||
const SizedBox(height: 12),
|
||
Text(
|
||
'How can I help you today?',
|
||
style: theme.typography.body?.copyWith(
|
||
color: theme.typography.body?.color?.withAlpha(180),
|
||
),
|
||
),
|
||
const SizedBox(height: 32),
|
||
_buildSuggestionChips(),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildSuggestionChips() {
|
||
final suggestions = [
|
||
'Write a poem',
|
||
'Explain quantum computing',
|
||
'Help me code',
|
||
'Translate to English',
|
||
];
|
||
|
||
return Wrap(
|
||
spacing: 8,
|
||
runSpacing: 8,
|
||
alignment: WrapAlignment.center,
|
||
children: suggestions.map((suggestion) {
|
||
return Button(
|
||
onPressed: () {
|
||
context.read<ChatProvider>().sendMessage(suggestion);
|
||
},
|
||
child: Text(suggestion),
|
||
);
|
||
}).toList(),
|
||
);
|
||
}
|
||
|
||
Widget _buildInputArea() {
|
||
return Consumer<ChatProvider>(
|
||
builder: (context, chatProvider, child) {
|
||
return ChatInput(
|
||
onSend: (message) {
|
||
chatProvider.sendMessage(message);
|
||
},
|
||
enabled: !chatProvider.isTyping,
|
||
);
|
||
},
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_scrollController.dispose();
|
||
super.dispose();
|
||
}
|
||
} |