Mudança nos icones de ios.
This commit is contained in:
@@ -4,6 +4,7 @@ import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:async';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'database_helper.dart';
|
||||
|
||||
class ChatScreen extends StatefulWidget {
|
||||
const ChatScreen({super.key});
|
||||
@@ -12,17 +13,15 @@ class ChatScreen extends StatefulWidget {
|
||||
State<ChatScreen> createState() => _ChatScreenState();
|
||||
}
|
||||
|
||||
class ChatMessage {
|
||||
final String text;
|
||||
final bool isAssistant;
|
||||
|
||||
ChatMessage({required this.text, this.isAssistant = false});
|
||||
}
|
||||
|
||||
class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
||||
final List<ChatMessage> _messages = [];
|
||||
final List<ChatSession> _sessions = [];
|
||||
ChatSession? _currentSession;
|
||||
|
||||
final TextEditingController _textController = TextEditingController();
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
bool _isTyping = false;
|
||||
late AnimationController _typingController;
|
||||
|
||||
@@ -34,26 +33,62 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
||||
duration: const Duration(milliseconds: 1200),
|
||||
)..repeat();
|
||||
|
||||
Timer(const Duration(seconds: 2), () => _checkAvailableModels());
|
||||
_loadInitialData();
|
||||
}
|
||||
|
||||
Future<void> _checkAvailableModels() async {
|
||||
try {
|
||||
final response = await http.get(
|
||||
Uri.parse('http://89.114.196.110:11434/api/tags'),
|
||||
).timeout(const Duration(seconds: 15));
|
||||
Future<void> _loadInitialData() async {
|
||||
await _loadSessions();
|
||||
if (_sessions.isNotEmpty) {
|
||||
await _selectSession(_sessions.first);
|
||||
} else {
|
||||
await _createNewSession();
|
||||
}
|
||||
}
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
print("--- MODELOS DISPONÍVEIS ---");
|
||||
if (data['models'] != null) {
|
||||
for (var m in data['models']) {
|
||||
print("- ${m['name']}");
|
||||
}
|
||||
}
|
||||
Future<void> _loadSessions() async {
|
||||
final sessions = await DatabaseHelper.instance.getSessions();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_sessions.clear();
|
||||
_sessions.addAll(sessions);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _selectSession(ChatSession session) async {
|
||||
final messages = await DatabaseHelper.instance.getMessages(session.id!);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_currentSession = session;
|
||||
_messages.clear();
|
||||
_messages.addAll(messages.reversed);
|
||||
_isTyping = false;
|
||||
});
|
||||
}
|
||||
if (_scaffoldKey.currentState?.isDrawerOpen ?? false) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _createNewSession() async {
|
||||
final title = "Chat ${DateTime.now().hour}:${DateTime.now().minute}";
|
||||
final id = await DatabaseHelper.instance.createSession(title);
|
||||
await _loadSessions();
|
||||
final newSession = _sessions.firstWhere((s) => s.id == id);
|
||||
await _selectSession(newSession);
|
||||
}
|
||||
|
||||
Future<void> _deleteSession(int id) async {
|
||||
await DatabaseHelper.instance.deleteSession(id);
|
||||
await _loadSessions();
|
||||
if (_currentSession?.id == id) {
|
||||
if (_sessions.isNotEmpty) {
|
||||
await _selectSession(_sessions.first);
|
||||
} else {
|
||||
await _createNewSession();
|
||||
}
|
||||
} catch (e) {
|
||||
print("Erro ao listar modelos: $e");
|
||||
} else {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,61 +100,76 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _clearChat() {
|
||||
setState(() {
|
||||
_messages.clear();
|
||||
_isTyping = false;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _handleSubmitted(String text) async {
|
||||
if (text.trim().isEmpty || _isTyping) return;
|
||||
|
||||
// Se por algum motivo não houver sessão, cria uma antes de enviar
|
||||
if (_currentSession == null) {
|
||||
await _createNewSession();
|
||||
}
|
||||
|
||||
final userMsgText = text.trim();
|
||||
_textController.clear();
|
||||
|
||||
final userMsg = ChatMessage(
|
||||
sessionId: _currentSession!.id!,
|
||||
text: userMsgText,
|
||||
isAssistant: false,
|
||||
timestamp: DateTime.now().toIso8601String(),
|
||||
);
|
||||
|
||||
await DatabaseHelper.instance.insertMessage(userMsg);
|
||||
|
||||
setState(() {
|
||||
_messages.insert(0, ChatMessage(text: text));
|
||||
_messages.insert(0, userMsg);
|
||||
_isTyping = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final url = Uri.parse('http://89.114.196.110:11434/api/chat');
|
||||
|
||||
final response = await http
|
||||
.post(
|
||||
final response = await http.post(
|
||||
url,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: jsonEncode({
|
||||
'model': 'qwen3:4b',
|
||||
'messages': [{'role': 'user', 'content': text}],
|
||||
'stream': false,
|
||||
'messages': [{'role': 'user', 'content': userMsgText}],
|
||||
'stream': false,
|
||||
}),
|
||||
)
|
||||
.timeout(const Duration(seconds: 60));
|
||||
).timeout(const Duration(seconds: 60));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = jsonDecode(response.body);
|
||||
final reply = data['message']?['content'] ?? 'Sem resposta.';
|
||||
final replyText = data['message']?['content'] ?? 'Sem resposta.';
|
||||
|
||||
final assistantMsg = ChatMessage(
|
||||
sessionId: _currentSession!.id!,
|
||||
text: replyText,
|
||||
isAssistant: true,
|
||||
timestamp: DateTime.now().toIso8601String(),
|
||||
);
|
||||
|
||||
await DatabaseHelper.instance.insertMessage(assistantMsg);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isTyping = false;
|
||||
_messages.insert(0, ChatMessage(text: reply, isAssistant: true));
|
||||
_messages.insert(0, assistantMsg);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
throw Exception('Erro HTTP ${response.statusCode}: ${response.body}');
|
||||
throw Exception('Erro HTTP ${response.statusCode}');
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isTyping = false;
|
||||
_messages.insert(
|
||||
0,
|
||||
ChatMessage(text: "Erro: $e", isAssistant: true),
|
||||
);
|
||||
_messages.insert(0, ChatMessage(
|
||||
sessionId: _currentSession!.id!,
|
||||
text: "Erro de ligação. Verifique se o servidor está online.",
|
||||
isAssistant: true,
|
||||
timestamp: DateTime.now().toIso8601String(),
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -147,20 +197,19 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
||||
bottomLeft: Radius.circular(isAssistant ? 4 : 20),
|
||||
bottomRight: Radius.circular(isAssistant ? 20 : 4),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 5, offset: const Offset(0, 2)),
|
||||
],
|
||||
),
|
||||
child: isAssistant
|
||||
child: isAssistant
|
||||
? MarkdownBody(
|
||||
data: message.text,
|
||||
styleSheet: MarkdownStyleSheet(
|
||||
p: const TextStyle(color: Colors.black87, fontSize: 15, height: 1.4),
|
||||
strong: const TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
|
||||
listBullet: const TextStyle(color: Colors.black87),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
message.text,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 15, height: 1.4),
|
||||
),
|
||||
: Text(message.text, style: const TextStyle(color: Colors.white, fontSize: 15)),
|
||||
),
|
||||
),
|
||||
if (!isAssistant) _buildAvatar(Icons.person),
|
||||
@@ -218,61 +267,47 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Widget _buildTextComposer() {
|
||||
return ClipRRect(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
border: Border(top: BorderSide(color: Colors.black.withOpacity(0.05), width: 0.5)),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete_sweep_rounded, color: Colors.redAccent),
|
||||
onPressed: _isTyping ? null : _clearChat,
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border(top: BorderSide(color: Colors.black.withOpacity(0.05))),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F5F9),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF1F5F9),
|
||||
borderRadius: BorderRadius.circular(30.0),
|
||||
border: Border.all(color: Colors.black.withOpacity(0.05)),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _textController,
|
||||
enabled: !_isTyping,
|
||||
onSubmitted: _handleSubmitted,
|
||||
decoration: InputDecoration(
|
||||
hintText: _isTyping ? "Aguarde a resposta..." : "Mensagem EPVChat...",
|
||||
border: InputBorder.none,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 14.0),
|
||||
),
|
||||
),
|
||||
child: TextField(
|
||||
controller: _textController,
|
||||
enabled: !_isTyping,
|
||||
onSubmitted: _handleSubmitted,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "Mensagem EPVChat...",
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 14.0),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: _isTyping
|
||||
? [Colors.grey, Colors.grey]
|
||||
: [const Color(0xFF8ad5c9), const Color(0xFF57a7ed)],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.send_rounded, color: Colors.white),
|
||||
onPressed: _isTyping ? null : () => _handleSubmitted(_textController.text),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: _isTyping ? [Colors.grey, Colors.grey] : [const Color(0xFF8ad5c9), const Color(0xFF57a7ed)],
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.send_rounded, color: Colors.white),
|
||||
onPressed: _isTyping ? null : () => _handleSubmitted(_textController.text),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -283,45 +318,123 @@ class _ChatScreenState extends State<ChatScreen> with TickerProviderStateMixin {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
drawer: _buildSidebar(),
|
||||
backgroundColor: Colors.white,
|
||||
body: Column(
|
||||
body: Stack(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
reverse: true,
|
||||
children: [
|
||||
...(_isTyping ? [_buildTypingIndicator()] : []),
|
||||
..._messages.map((msg) => _buildMessage(msg)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 50, top: 20),
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
// Sombreado Mint
|
||||
Positioned(
|
||||
top: -screenWidth * 0.45,
|
||||
left: -screenWidth * 0.2,
|
||||
right: -screenWidth * 0.2,
|
||||
child: Container(
|
||||
height: screenWidth * 1.1,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
center: Alignment.center,
|
||||
radius: 0.5,
|
||||
colors: [const Color(0xFF8ad5c9).withOpacity(0.6), const Color(0xFF8ad5c9).withOpacity(0.0)],
|
||||
stops: const [0.2, 1.0],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Column(
|
||||
children: [
|
||||
// Área do Topo (Menu e Logo)
|
||||
SafeArea(
|
||||
child: Container(
|
||||
height: 100,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
height: screenWidth * 0.8,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: [
|
||||
const Color(0xFF8ad5c9).withOpacity(0.4),
|
||||
const Color(0xFF8ad5c9).withOpacity(0.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/logo.png',
|
||||
height: 170,
|
||||
fit: BoxFit.contain,
|
||||
IconButton(
|
||||
icon: const Icon(Icons.menu_rounded, color: Color(0xFF57a7ed), size: 32),
|
||||
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
|
||||
),
|
||||
Image.asset('assets/logo.png', height: 100, errorBuilder: (c,e,s) => const SizedBox(width: 100)),
|
||||
const SizedBox(width: 48), // Equilíbrio para o ícone do menu
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
reverse: true,
|
||||
itemCount: _messages.length + (_isTyping ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (_isTyping && index == 0) return _buildTypingIndicator();
|
||||
int msgIndex = _isTyping ? index - 1 : index;
|
||||
return _buildMessage(_messages[msgIndex]);
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildTextComposer(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSidebar() {
|
||||
return Drawer(
|
||||
width: MediaQuery.of(context).size.width * 0.75,
|
||||
child: Column(
|
||||
children: [
|
||||
DrawerHeader(
|
||||
decoration: const BoxDecoration(
|
||||
gradient: LinearGradient(colors: [Color(0xFF8ad5c9), Color(0xFF57a7ed)]),
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.auto_awesome, color: Colors.white, size: 40),
|
||||
const SizedBox(height: 10),
|
||||
const Text('Histórico de Chats', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_comment_rounded, color: Color(0xFF57a7ed)),
|
||||
title: const Text('Nova Conversa', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
onTap: _createNewSession,
|
||||
),
|
||||
const Divider(),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: _sessions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final session = _sessions[index];
|
||||
final isSelected = _currentSession?.id == session.id;
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? const Color(0xFF8ad5c9).withOpacity(0.1) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text(session.title, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete_outline_rounded, color: Colors.redAccent),
|
||||
onPressed: () => _deleteSession(session.id!),
|
||||
),
|
||||
onTap: () => _selectSession(session),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
_buildTextComposer(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
144
lib/database_helper.dart
Normal file
144
lib/database_helper.dart
Normal file
@@ -0,0 +1,144 @@
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
class ChatSession {
|
||||
final int? id;
|
||||
final String title;
|
||||
final String timestamp;
|
||||
|
||||
ChatSession({this.id, required this.title, required this.timestamp});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'timestamp': timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
factory ChatSession.fromMap(Map<String, dynamic> map) {
|
||||
return ChatSession(
|
||||
id: map['id'],
|
||||
title: map['title'],
|
||||
timestamp: map['timestamp'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChatMessage {
|
||||
final int? id;
|
||||
final int sessionId;
|
||||
final String text;
|
||||
final bool isAssistant;
|
||||
final String timestamp;
|
||||
|
||||
ChatMessage({
|
||||
this.id,
|
||||
required this.sessionId,
|
||||
required this.text,
|
||||
required this.isAssistant,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'sessionId': sessionId,
|
||||
'text': text,
|
||||
'isAssistant': isAssistant ? 1 : 0,
|
||||
'timestamp': timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
factory ChatMessage.fromMap(Map<String, dynamic> map) {
|
||||
return ChatMessage(
|
||||
id: map['id'],
|
||||
sessionId: map['sessionId'],
|
||||
text: map['text'],
|
||||
isAssistant: map['isAssistant'] == 1,
|
||||
timestamp: map['timestamp'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseHelper {
|
||||
static final DatabaseHelper instance = DatabaseHelper._init();
|
||||
static Database? _database;
|
||||
|
||||
DatabaseHelper._init();
|
||||
|
||||
Future<Database> get database async {
|
||||
if (_database != null) return _database!;
|
||||
_database = await _initDB('chat.db');
|
||||
return _database!;
|
||||
}
|
||||
|
||||
Future<Database> _initDB(String filePath) async {
|
||||
final dbPath = await getDatabasesPath();
|
||||
final path = join(dbPath, filePath);
|
||||
|
||||
return await openDatabase(
|
||||
path,
|
||||
version: 1,
|
||||
onCreate: _createDB,
|
||||
);
|
||||
}
|
||||
|
||||
Future _createDB(Database db, int version) async {
|
||||
await db.execute('''
|
||||
CREATE TABLE sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
timestamp TEXT NOT NULL
|
||||
)
|
||||
''');
|
||||
|
||||
await db.execute('''
|
||||
CREATE TABLE messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
sessionId INTEGER NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
isAssistant INTEGER NOT NULL,
|
||||
timestamp TEXT NOT NULL,
|
||||
FOREIGN KEY (sessionId) REFERENCES sessions (id) ON DELETE CASCADE
|
||||
)
|
||||
''');
|
||||
}
|
||||
|
||||
// Session operations
|
||||
Future<int> createSession(String title) async {
|
||||
final db = await instance.database;
|
||||
return await db.insert('sessions', {
|
||||
'title': title,
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<ChatSession>> getSessions() async {
|
||||
final db = await instance.database;
|
||||
final result = await db.query('sessions', orderBy: 'timestamp DESC');
|
||||
return result.map((json) => ChatSession.fromMap(json)).toList();
|
||||
}
|
||||
|
||||
Future<void> deleteSession(int id) async {
|
||||
final db = await instance.database;
|
||||
await db.delete('sessions', where: 'id = ?', whereArgs: [id]);
|
||||
}
|
||||
|
||||
// Message operations
|
||||
Future<int> insertMessage(ChatMessage message) async {
|
||||
final db = await instance.database;
|
||||
return await db.insert('messages', message.toMap());
|
||||
}
|
||||
|
||||
Future<List<ChatMessage>> getMessages(int sessionId) async {
|
||||
final db = await instance.database;
|
||||
final result = await db.query(
|
||||
'messages',
|
||||
where: 'sessionId = ?',
|
||||
whereArgs: [sessionId],
|
||||
orderBy: 'timestamp ASC',
|
||||
);
|
||||
return result.map((json) => ChatMessage.fromMap(json)).toList();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'EPVChat! Clone',
|
||||
title: 'EPVChat!',
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light,
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
|
||||
Reference in New Issue
Block a user