diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6ee3df0..a701cb5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ createState() => _ChatScreenState(); } -class ChatMessage { - final String text; - final bool isAssistant; - - ChatMessage({required this.text, this.isAssistant = false}); -} - class _ChatScreenState extends State with TickerProviderStateMixin { final List _messages = []; + final List _sessions = []; + ChatSession? _currentSession; + final TextEditingController _textController = TextEditingController(); final ScrollController _scrollController = ScrollController(); + final GlobalKey _scaffoldKey = GlobalKey(); + bool _isTyping = false; late AnimationController _typingController; @@ -34,26 +33,62 @@ class _ChatScreenState extends State with TickerProviderStateMixin { duration: const Duration(milliseconds: 1200), )..repeat(); - Timer(const Duration(seconds: 2), () => _checkAvailableModels()); + _loadInitialData(); } - Future _checkAvailableModels() async { - try { - final response = await http.get( - Uri.parse('http://89.114.196.110:11434/api/tags'), - ).timeout(const Duration(seconds: 15)); + Future _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 _loadSessions() async { + final sessions = await DatabaseHelper.instance.getSessions(); + if (mounted) { + setState(() { + _sessions.clear(); + _sessions.addAll(sessions); + }); + } + } + + Future _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 _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 _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 with TickerProviderStateMixin { super.dispose(); } - void _clearChat() { - setState(() { - _messages.clear(); - _isTyping = false; - }); - } - Future _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 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 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 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(), ], ), ); diff --git a/lib/database_helper.dart b/lib/database_helper.dart new file mode 100644 index 0000000..1bfa7f0 --- /dev/null +++ b/lib/database_helper.dart @@ -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 toMap() { + return { + 'id': id, + 'title': title, + 'timestamp': timestamp, + }; + } + + factory ChatSession.fromMap(Map 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 toMap() { + return { + 'id': id, + 'sessionId': sessionId, + 'text': text, + 'isAssistant': isAssistant ? 1 : 0, + 'timestamp': timestamp, + }; + } + + factory ChatMessage.fromMap(Map 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 get database async { + if (_database != null) return _database!; + _database = await _initDB('chat.db'); + return _database!; + } + + Future _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 createSession(String title) async { + final db = await instance.database; + return await db.insert('sessions', { + 'title': title, + 'timestamp': DateTime.now().toIso8601String(), + }); + } + + Future> 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 deleteSession(int id) async { + final db = await instance.database; + await db.delete('sessions', where: 'id = ?', whereArgs: [id]); + } + + // Message operations + Future insertMessage(ChatMessage message) async { + final db = await instance.database; + return await db.insert('messages', message.toMap()); + } + + Future> 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(); + } +} diff --git a/lib/main.dart b/lib/main.dart index a325320..18c40ca 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -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, diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..29d4e7f 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index c6994c1..26cdee4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" args: dependency: transitive description: @@ -33,6 +41,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -65,11 +89,27 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -107,6 +147,22 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image: + dependency: transitive + description: + name: image + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + url: "https://pub.dev" + source: hosted + version: "4.8.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: cb09e7dac6210041fad964ed7fbee004f14258b4eca4040f72d1234062ace4c8 + url: "https://pub.dev" + source: hosted + version: "4.11.0" leak_tracker: dependency: transitive description: @@ -172,13 +228,45 @@ packages: source: hosted version: "1.17.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted version: "1.9.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" sky_engine: dependency: transitive description: flutter @@ -192,6 +280,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "881e28efdcc9950fd8e9bb42713dcf1103e62a2e7168f23c9338d82db13dec40" + url: "https://pub.dev" + source: hosted + version: "2.4.2+3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -216,6 +344,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -264,6 +400,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" sdks: dart: ">=3.11.1 <4.0.0" - flutter: ">=3.22.0" + flutter: ">=3.38.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2f413e9..782cd36 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,13 +13,22 @@ dependencies: cupertino_icons: ^1.0.8 http: ^1.6.0 flutter_markdown: ^0.7.3 + sqflite: ^2.3.0 + path: ^1.9.0 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^6.0.0 + flutter_launcher_icons: ^0.13.1 + +flutter_icons: + android: true + ios: true + image_path: "assets/icon/icon_logos.png" flutter: uses-material-design: true assets: - assets/logo.png + diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png index b749bfe..75222d8 100644 Binary files a/web/icons/Icon-192.png and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png index 88cfd48..263a727 100644 Binary files a/web/icons/Icon-512.png and b/web/icons/Icon-512.png differ