From e790954af48da9e0c610c3fac5e92c4717ebf3c1 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 26 Jul 2023 18:47:49 +0800 Subject: [PATCH] fix time precision issue --- lib/components/chat.dart | 9 +++- lib/components/setting.dart | 67 ++++++++++++++++++++------- lib/controller/message.dart | 9 ++-- lib/controller/settings.dart | 2 +- lib/repository/chat_room.dart | 86 ++++++++++++++++++----------------- 5 files changed, 109 insertions(+), 64 deletions(-) diff --git a/lib/components/chat.dart b/lib/components/chat.dart index 2f18cff..8e934c9 100644 --- a/lib/components/chat.dart +++ b/lib/components/chat.dart @@ -80,7 +80,11 @@ class _ChatWindowState extends State { child: TextFormField( style: const TextStyle(fontSize: 16), controller: _controller, - keyboardType: TextInputType.multiline, + keyboardType: TextInputType.text, + textInputAction: TextInputAction.send, + onFieldSubmitted: (value) { + _sendMessage(); + }, decoration: InputDecoration( hintText: "@ai talk to AI", floatingLabelBehavior: FloatingLabelBehavior.auto, @@ -257,8 +261,9 @@ class _ChatWindowState extends State { } } + var polling = false; + void _startPollingRemote() { - var polling = false; _timer = Timer.periodic( const Duration(seconds: 3), (Timer timer) { diff --git a/lib/components/setting.dart b/lib/components/setting.dart index 410fde3..f5a2600 100644 --- a/lib/components/setting.dart +++ b/lib/components/setting.dart @@ -14,12 +14,12 @@ class SettingPage extends StatefulWidget { } class _SettingPageState extends State { - _popDone(String content) { + _popFinish(String title, String content) { if (context.mounted) { showDialog( context: context, builder: (BuildContext context) => AlertDialog( - title: const Text('Done!'), + title: Text(title), content: Text(content), actions: [ TextButton( @@ -32,19 +32,49 @@ class _SettingPageState extends State { } } - _onClearLocalMessage() async { - await ChatRoomRepository().removeDatabase(); + Future _doRemoveMessage(bool isLocal) async { + if (isLocal) { + await ChatRoomRepository().removeDatabase(); + return true; + } else { + return await ChatRoomRepository().removeDatabaseRemote(); + } + } + + _onClearMessage(bool isLocal) async { ChatRoomController controller = Get.find(); controller.reset(); - _popDone("Cleared all local messages."); - } + String location = isLocal ? "local" : "remote"; + if (context.mounted) { + showDialog( + context: context, + builder: (BuildContext context) => AlertDialog( + content: Text("Do really want to remove all $location messages?"), + actions: [ + TextButton( + onPressed: () async { + final res = _doRemoveMessage(isLocal); + res.then((value) { + if (value) { + _popFinish("Done", "Remove all $location messages done!"); + } else { + _popFinish("Failed", "Remove $location messages failed!"); + } + }); - _onClearRemoteMessage() async { - final res = await ChatRoomRepository().removeDatabaseRemote(); - if (res) { - _popDone("Cleared all remote messages."); - } else { - _popDone("Clear remote messages failed."); + if (context.mounted) { + Navigator.pop(context, 'OK'); + } + }, + child: const Text('Yes'), + ), + TextButton( + onPressed: () => Navigator.pop(context, 'OK'), + child: const Text('Cancel'), + ), + ], + ), + ); } } @@ -315,14 +345,19 @@ class _SettingPageState extends State { divider, sizedBoxSpace, Row( - mainAxisAlignment: MainAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.start, children: [ ElevatedButton( - onPressed: _onClearLocalMessage, + onPressed: () => _onClearMessage(true), child: const Text("Clear local messages")), - const SizedBox(width: 8), + ], + ), + sizedBoxSpace, + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ ElevatedButton( - onPressed: _onClearRemoteMessage, + onPressed: () => _onClearMessage(false), child: const Text("Clear remote messages")), ], ), diff --git a/lib/controller/message.dart b/lib/controller/message.dart index ab49ebb..edad148 100644 --- a/lib/controller/message.dart +++ b/lib/controller/message.dart @@ -17,7 +17,10 @@ class MessageController extends GetxController { final messageListRemote = await ChatRoomRepository() .getNewMessagesByChatRoomUuidRemote( room, msgList.lastOrNull?.createTime); + + await ChatRoomRepository().addMessageLocal(room, messageListRemote); messageList.value = [...msgList, ...messageListRemote]; + update(); } @@ -27,10 +30,8 @@ class MessageController extends GetxController { .getNewMessagesByChatRoomUuidRemote(room, lastMsgTime); bool needUpdate = false; for (var item in newMessages) { - if (messageList.where((m) => m.uuid == item.uuid).isEmpty) { - messageList.add(item); - needUpdate = true; - } + messageList.add(item); + needUpdate = true; } if (needUpdate) { update(); diff --git a/lib/controller/settings.dart b/lib/controller/settings.dart index 83e8ec3..22f0731 100644 --- a/lib/controller/settings.dart +++ b/lib/controller/settings.dart @@ -126,7 +126,7 @@ class SettingsController extends GetxController { switch (res) { case null: { - popMsg = "OK"; + popMsg = ""; break; } case "Empty": diff --git a/lib/repository/chat_room.dart b/lib/repository/chat_room.dart index 205208c..a055e64 100644 --- a/lib/repository/chat_room.dart +++ b/lib/repository/chat_room.dart @@ -1,5 +1,3 @@ -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:get_storage/get_storage.dart'; import 'package:mysql_client/exception.dart'; import 'package:sqflite/sqflite.dart'; import 'package:moyubie/utils/tidb.dart'; @@ -22,7 +20,11 @@ class TiDBConnection { close() async { if (connection != null) { if (connection!.connected) { - await connection?.close(); + try { + await connection?.close(); + } catch (e) { + // Ignore. + } } } connection = null; @@ -113,7 +115,7 @@ class ChatRoom { return { 'uuid': uuid, 'name': name, - 'create_time': createTime.millisecondsSinceEpoch, + 'create_time': createTime.toIso8601String(), 'connection': connectionToken, 'role': role.name, }; @@ -150,7 +152,7 @@ class Message { return { 'uuid': uuid, 'user_name': userName, - 'create_time': createTime.millisecondsSinceEpoch, + 'create_time': createTime.toIso8601String(), 'message': message, 'source': source.name, 'ask_ai': ask_ai ? '1' : '0', @@ -167,7 +169,7 @@ class ChatRoomRepository { static const String _tableChatRoom = 'chat_room'; static const String _columnChatRoomUuid = 'uuid'; static const String _columnChatRoomName = 'name'; - // UTC time zone. SQLite: Integer(i.e. Unix Time), TiDB: DateTime + // UTC time zone. SQLite: Text, TiDB: DateTime static const String _columnChatRoomCreateTime = 'create_time'; static const String _columnChatRoomConnectionToken = 'connection'; // The user role of this chat room, could be 'host' or 'guest' @@ -175,7 +177,7 @@ class ChatRoomRepository { static const String _columnMessageUuid = 'uuid'; static const String _columnMessageUserName = 'user_name'; - // UTC time zone. SQLite: Integer(i.e. Unix Time), TiDB: DateTime + // UTC time zone. SQLite: Text, TiDB: DateTime static const String _columnMessageCreateTime = 'create_time'; static const String _columnMessageMessage = 'message'; static const String _columnMessageSource = 'source'; @@ -209,7 +211,7 @@ class ChatRoomRepository { CREATE TABLE $_tableChatRoom ( $_columnChatRoomUuid VARCHAR(36) PRIMARY KEY, $_columnChatRoomName TEXT, - $_columnChatRoomCreateTime INTEGER, + $_columnChatRoomCreateTime TEXT, $_columnChatRoomConnectionToken TEXT, $_columnChatRoomRole TEXT ) @@ -281,18 +283,13 @@ class ChatRoomRepository { static Future getRemoteDb(TiDBConnection conn, bool isHost, {bool forceInit = false}) async { bool shouldInit = - conn.connection == null || !conn.connection!.connected || forceInit; - if (conn.host.isEmpty || - conn.port == 0 || - conn.userName.isEmpty || - conn.password.isEmpty) { - shouldInit = false; - } + (conn.connection == null || !conn.connection!.connected || forceInit) && + conn.hasSet(); try { if (shouldInit) { // Make sure the old connection has been close - conn.connection?.close(); + conn.close(); var dbConn = await MySQLConnection.createConnection( host: conn.host, @@ -346,7 +343,7 @@ class ChatRoomRepository { return ChatRoom( uuid: maps[i][_columnChatRoomUuid], name: maps[i][_columnChatRoomName], - createTime: DateTime.fromMicrosecondsSinceEpoch(ct), + createTime: DateTime.parse(ct), connectionToken: maps[i][_columnChatRoomConnectionToken], role: Role.values .firstWhere((e) => e.name == maps[i][_columnChatRoomRole])); @@ -362,7 +359,8 @@ class ChatRoomRepository { return ChatRoom( uuid: maps[_columnChatRoomUuid]!, name: maps[_columnChatRoomName]!, - createTime: DateTime.parse(maps[_columnChatRoomCreateTime]!), + + createTime: DateTime.parse("${maps[_columnChatRoomCreateTime]!}Z"), // connectionToken: maps[_columnChatRoomConnectionToken]!, role: Role.values.firstWhere((e) => e.name == maps[_columnChatRoomRole]), @@ -379,7 +377,7 @@ class ChatRoomRepository { CREATE TABLE IF NOT EXISTS `msg_${room.uuid}` ( $_columnMessageUuid VARCHAR(36) PRIMARY KEY, $_columnMessageUserName TEXT, - $_columnMessageCreateTime INTEGER, + $_columnMessageCreateTime TEXT, $_columnMessageMessage TEXT, $_columnMessageSource TEXT, $_columnAskAI INTEGER @@ -521,14 +519,15 @@ class ChatRoomRepository { Future> getMessagesByChatRoomUUid(ChatRoom room, {int limit = 500}) async { final db = await _getDb(); - final List> maps = await db.query('`msg_${room.uuid}`', - orderBy: "$_columnMessageCreateTime desc", limit: limit); + final List> maps = await db.query( + '`msg_${room.uuid}`', + orderBy: "julianday($_columnMessageCreateTime) desc", + limit: limit, + ); return List.from(maps.reversed.map((m) => Message( uuid: m[_columnMessageUuid], userName: m[_columnMessageUserName], - createTime: DateTime.fromMillisecondsSinceEpoch( - m[_columnMessageCreateTime], - isUtc: true), + createTime: DateTime.parse(m[_columnMessageCreateTime]), message: m[_columnMessageMessage], source: MessageSource.values .firstWhere((e) => e.name == m[_columnMessageSource]), @@ -545,17 +544,16 @@ class ChatRoomRepository { } String whereClause = ""; if (from != null) { - whereClause = - "WHERE UNIX_TIMESTAMP($_columnMessageCreateTime) > ${from.millisecondsSinceEpoch ~/ 1000}"; + whereClause = "WHERE $_columnMessageCreateTime > '${from.toString()}'"; } var res; + var sql; try { - res = await db.execute(''' - SELECT * FROM `msg_${room.uuid}` $whereClause ORDER BY $_columnMessageCreateTime ASC; - '''); + sql = + "SELECT * FROM moyubie.`msg_${room.uuid}` $whereClause ORDER BY $_columnMessageCreateTime ASC;"; + res = await db.execute(sql); } catch (e) { - print("catch error"); - print(e.toString()); + print("catch error: $sql, error: ${e.toString()}"); return Future(() => []); } return List.from(res.rows.map((e) { @@ -563,7 +561,8 @@ class ChatRoomRepository { return Message( uuid: maps[_columnMessageUuid]!, userName: maps[_columnMessageUserName]!, - createTime: DateTime.parse(maps[_columnMessageCreateTime]!), + createTime: DateTime.parse( + "${maps[_columnMessageCreateTime]!}Z"), // Add a Z at the end to tell the parser that it is a utc DateTime message: maps[_columnMessageMessage]!, source: MessageSource.values .firstWhere((e) => e.name == maps[_columnMessageSource]!), @@ -573,12 +572,7 @@ class ChatRoomRepository { } Future addMessage(ChatRoom room, Message message) async { - final db = await _getDb(); - await db.insert( - '`msg_${room.uuid}`', - message.toSQLMap(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); + addMessageLocal(room, [message]); // Don't wait for remote message finish adding to TiDB. var addRemote = addMessageRemote(room, message); @@ -591,9 +585,19 @@ class ChatRoomRepository { }); } + Future addMessageLocal(ChatRoom room, List messages) async { + final db = await _getDb(); + final batch = db.batch(); + for (final m in messages) { + batch.insert('`msg_${room.uuid}`', m.toSQLMap(), + conflictAlgorithm: ConflictAlgorithm.replace); + } + batch.commit(); + } + Future addMessageRemote(ChatRoom room, Message message) async { try { - await insertMessageRemote(room, message); + await _insertMessageRemote(room, message); } catch (e) { if (e is MySQLServerException) { if (e.errorCode == 1146) { @@ -604,7 +608,7 @@ class ChatRoomRepository { // Add chat room again. final newRoom = rooms.first; await addChatRoom(newRoom); - await insertMessageRemote(newRoom, message); + await _insertMessageRemote(newRoom, message); } else { // If it is not, then too weird. I give up! } @@ -620,7 +624,7 @@ class ChatRoomRepository { return null; } - Future insertMessageRemote(ChatRoom room, Message message) async { + Future _insertMessageRemote(ChatRoom room, Message message) async { final conn = ensureConnection(room.connectionToken); final remoteDB = await getRemoteDb(conn, false); if (remoteDB != null) {