Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
YuJuncen committed Jul 26, 2023
2 parents 113f518 + caf14be commit d4b022d
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 64 deletions.
9 changes: 7 additions & 2 deletions lib/components/chat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ class _ChatWindowState extends State<ChatWindow> {
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,
Expand Down Expand Up @@ -257,8 +261,9 @@ class _ChatWindowState extends State<ChatWindow> {
}
}

var polling = false;

void _startPollingRemote() {
var polling = false;
_timer = Timer.periodic(
const Duration(seconds: 3),
(Timer timer) {
Expand Down
67 changes: 51 additions & 16 deletions lib/components/setting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ class SettingPage extends StatefulWidget {
}

class _SettingPageState extends State<SettingPage> {
_popDone(String content) {
_popFinish(String title, String content) {
if (context.mounted) {
showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Done!'),
title: Text(title),
content: Text(content),
actions: <Widget>[
TextButton(
Expand All @@ -32,19 +32,49 @@ class _SettingPageState extends State<SettingPage> {
}
}

_onClearLocalMessage() async {
await ChatRoomRepository().removeDatabase();
Future<bool> _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<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
content: Text("Do really want to remove all $location messages?"),
actions: <Widget>[
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'),
),
],
),
);
}
}

Expand Down Expand Up @@ -315,14 +345,19 @@ class _SettingPageState extends State<SettingPage> {
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")),
],
),
Expand Down
9 changes: 5 additions & 4 deletions lib/controller/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion lib/controller/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ class SettingsController extends GetxController {
switch (res) {
case null:
{
popMsg = "OK";
popMsg = "";
break;
}
case "Empty":
Expand Down
86 changes: 45 additions & 41 deletions lib/repository/chat_room.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -113,7 +115,7 @@ class ChatRoom {
return {
'uuid': uuid,
'name': name,
'create_time': createTime.millisecondsSinceEpoch,
'create_time': createTime.toIso8601String(),
'connection': connectionToken,
'role': role.name,
};
Expand Down Expand Up @@ -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',
Expand All @@ -167,15 +169,15 @@ 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'
static const String _columnChatRoomRole = 'role';

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';
Expand Down Expand Up @@ -209,7 +211,7 @@ class ChatRoomRepository {
CREATE TABLE $_tableChatRoom (
$_columnChatRoomUuid VARCHAR(36) PRIMARY KEY,
$_columnChatRoomName TEXT,
$_columnChatRoomCreateTime INTEGER,
$_columnChatRoomCreateTime TEXT,
$_columnChatRoomConnectionToken TEXT,
$_columnChatRoomRole TEXT
)
Expand Down Expand Up @@ -281,18 +283,13 @@ class ChatRoomRepository {
static Future<MySQLConnection?> 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,
Expand Down Expand Up @@ -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]));
Expand All @@ -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]),
Expand All @@ -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
Expand Down Expand Up @@ -521,14 +519,15 @@ class ChatRoomRepository {
Future<List<Message>> getMessagesByChatRoomUUid(ChatRoom room,
{int limit = 500}) async {
final db = await _getDb();
final List<Map<String, dynamic>> maps = await db.query('`msg_${room.uuid}`',
orderBy: "$_columnMessageCreateTime desc", limit: limit);
final List<Map<String, dynamic>> maps = await db.query(
'`msg_${room.uuid}`',
orderBy: "julianday($_columnMessageCreateTime) desc",
limit: limit,
);
return List<Message>.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]),
Expand All @@ -545,25 +544,25 @@ 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<Message>.from(res.rows.map((e) {
var maps = e.assoc();
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]!),
Expand All @@ -573,12 +572,7 @@ class ChatRoomRepository {
}

Future<void> 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);
Expand All @@ -591,9 +585,19 @@ class ChatRoomRepository {
});
}

Future<void> addMessageLocal(ChatRoom room, List<Message> 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<String?> 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) {
Expand All @@ -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!
}
Expand All @@ -620,7 +624,7 @@ class ChatRoomRepository {
return null;
}

Future<void> insertMessageRemote(ChatRoom room, Message message) async {
Future<void> _insertMessageRemote(ChatRoom room, Message message) async {
final conn = ensureConnection(room.connectionToken);
final remoteDB = await getRemoteDb(conn, false);
if (remoteDB != null) {
Expand Down

0 comments on commit d4b022d

Please sign in to comment.