mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 10:58:40 +00:00
multiple bug fixes
This commit is contained in:
parent
c6c625df59
commit
c008174070
15 changed files with 202 additions and 867 deletions
|
|
@ -1,258 +0,0 @@
|
|||
import 'package:cv/cv.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/app.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class Contact {
|
||||
Contact(
|
||||
{required this.userId,
|
||||
required this.displayName,
|
||||
required this.accepted,
|
||||
required this.blocked,
|
||||
required this.verified,
|
||||
required this.totalMediaCounter,
|
||||
required this.requested});
|
||||
final Int64 userId;
|
||||
final String displayName;
|
||||
final bool accepted;
|
||||
final bool requested;
|
||||
final bool blocked;
|
||||
final bool verified;
|
||||
final int totalMediaCounter;
|
||||
}
|
||||
|
||||
class DbContacts extends CvModelBase {
|
||||
static const tableName = "contacts";
|
||||
|
||||
static const columnUserId = "contact_user_id";
|
||||
final userId = CvField<int>(columnUserId);
|
||||
|
||||
static const columnDisplayName = "display_name";
|
||||
final displayName = CvField<String>(columnDisplayName);
|
||||
|
||||
static const columnAccepted = "accepted";
|
||||
final accepted = CvField<int>(columnAccepted);
|
||||
|
||||
static const columnRequested = "requested";
|
||||
final requested = CvField<int>(columnRequested);
|
||||
|
||||
static const columnBlocked = "blocked";
|
||||
final blocked = CvField<int>(columnBlocked);
|
||||
|
||||
static const columnVerified = "verified";
|
||||
final verified = CvField<int>(columnVerified);
|
||||
|
||||
static const columnTotalMediaCounter = "total_media_counter";
|
||||
final totalMediaCounter = CvField<int>(columnTotalMediaCounter);
|
||||
|
||||
static const columnCreatedAt = "created_at";
|
||||
final createdAt = CvField<DateTime>(columnCreatedAt);
|
||||
|
||||
static Future setupDatabaseTable(Database db) async {
|
||||
String createTableString = """
|
||||
CREATE TABLE IF NOT EXISTS $tableName (
|
||||
$columnUserId INTEGER NOT NULL PRIMARY KEY,
|
||||
$columnDisplayName TEXT,
|
||||
$columnAccepted INT NOT NULL DEFAULT 0,
|
||||
$columnRequested INT NOT NULL DEFAULT 0,
|
||||
$columnBlocked INT NOT NULL DEFAULT 0,
|
||||
$columnVerified INTEGER NOT NULL DEFAULT 0,
|
||||
$columnTotalMediaCounter INT NOT NULL DEFAULT 0,
|
||||
$columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""";
|
||||
await db.execute(createTableString);
|
||||
|
||||
if (!await columnExists(db, tableName, columnVerified)) {
|
||||
String alterTableString = """
|
||||
ALTER TABLE $tableName
|
||||
ADD COLUMN $columnVerified INTEGER NOT NULL DEFAULT 0
|
||||
""";
|
||||
await db.execute(alterTableString);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
List<CvField> get fields =>
|
||||
[userId, displayName, accepted, requested, blocked, createdAt];
|
||||
|
||||
static Future<List<Contact>> getActiveUsers() async {
|
||||
return (await _getAllUsers())
|
||||
.where((u) => u.accepted && !u.blocked)
|
||||
.toList();
|
||||
}
|
||||
|
||||
static Future<List<Contact>> getBlockedUsers() async {
|
||||
return (await _getAllUsers()).where((u) => u.blocked).toList();
|
||||
}
|
||||
|
||||
static Future<List<Contact>> getUsers() async {
|
||||
return (await _getAllUsers()).where((u) => !u.blocked).toList();
|
||||
}
|
||||
|
||||
static Future<List<Contact>> getAllUsers() async {
|
||||
return await _getAllUsers();
|
||||
}
|
||||
|
||||
static Future updateTotalMediaCounter(
|
||||
int userId,
|
||||
) async {
|
||||
List<Map<String, dynamic>> result = await dbProvider.db!.query(
|
||||
tableName,
|
||||
columns: [columnTotalMediaCounter],
|
||||
where: '$columnUserId = ?',
|
||||
whereArgs: [userId],
|
||||
);
|
||||
|
||||
if (result.isNotEmpty) {
|
||||
int totalMediaCounter = result.first.cast()[columnTotalMediaCounter];
|
||||
_updateTotalMediaCounter(userId, totalMediaCounter + 1);
|
||||
globalCallBackOnContactChange();
|
||||
}
|
||||
}
|
||||
|
||||
static List<Contact> _parseContacts(List<dynamic> users) {
|
||||
List<Contact> parsedUsers = [];
|
||||
for (int i = 0; i < users.length; i++) {
|
||||
try {
|
||||
int userId = users.cast()[i][columnUserId];
|
||||
parsedUsers.add(
|
||||
Contact(
|
||||
userId: Int64(userId),
|
||||
totalMediaCounter: users.cast()[i][columnTotalMediaCounter],
|
||||
displayName: users.cast()[i][columnDisplayName],
|
||||
accepted: users[i][columnAccepted] == 1,
|
||||
blocked: users[i][columnBlocked] == 1,
|
||||
verified: users[i][columnVerified] == 1,
|
||||
requested: users[i][columnRequested] == 1,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
Logger("contacts_model/parse_single_user").shout("$e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return parsedUsers;
|
||||
}
|
||||
|
||||
static Future<Contact?> getUserById(int userId) async {
|
||||
try {
|
||||
var user = await dbProvider.db!.query(tableName,
|
||||
columns: [
|
||||
columnUserId,
|
||||
columnDisplayName,
|
||||
columnAccepted,
|
||||
columnRequested,
|
||||
columnBlocked,
|
||||
columnVerified,
|
||||
columnTotalMediaCounter,
|
||||
columnCreatedAt
|
||||
],
|
||||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId]);
|
||||
if (user.isEmpty) return null;
|
||||
return _parseContacts(user)[0];
|
||||
} catch (e) {
|
||||
Logger("contacts_model/getUserById").shout("$e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<Contact>> _getAllUsers() async {
|
||||
try {
|
||||
var users = await dbProvider.db!.query(tableName, columns: [
|
||||
columnUserId,
|
||||
columnDisplayName,
|
||||
columnAccepted,
|
||||
columnRequested,
|
||||
columnBlocked,
|
||||
columnVerified,
|
||||
columnTotalMediaCounter,
|
||||
columnCreatedAt
|
||||
]);
|
||||
if (users.isEmpty) return [];
|
||||
return _parseContacts(users);
|
||||
} catch (e) {
|
||||
Logger("contacts_model/getUsers").shout("$e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future _update(int userId, Map<String, dynamic> updates,
|
||||
{bool notifyFlutter = true}) async {
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
updates,
|
||||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId],
|
||||
);
|
||||
if (notifyFlutter) {
|
||||
globalCallBackOnContactChange();
|
||||
}
|
||||
}
|
||||
|
||||
static Future changeDisplayName(int userId, String newDisplayName) async {
|
||||
if (newDisplayName == "") return;
|
||||
Map<String, dynamic> updates = {
|
||||
columnDisplayName: newDisplayName,
|
||||
};
|
||||
await _update(userId, updates);
|
||||
}
|
||||
|
||||
static Future _updateTotalMediaCounter(
|
||||
int userId, int totalMediaCounter) async {
|
||||
Map<String, dynamic> updates = {columnTotalMediaCounter: totalMediaCounter};
|
||||
await _update(userId, updates, notifyFlutter: false);
|
||||
}
|
||||
|
||||
static Future blockUser(int userId, {bool unblock = false}) async {
|
||||
Map<String, dynamic> updates = {
|
||||
columnBlocked: unblock ? 0 : 1,
|
||||
};
|
||||
await _update(userId, updates);
|
||||
}
|
||||
|
||||
static Future acceptUser(int userId) async {
|
||||
Map<String, dynamic> updates = {
|
||||
columnAccepted: 1,
|
||||
columnRequested: 0,
|
||||
};
|
||||
await _update(userId, updates);
|
||||
}
|
||||
|
||||
static Future updateVerificationStatus(int userId, bool status) async {
|
||||
Map<String, dynamic> updates = {
|
||||
columnVerified: status ? 1 : 0,
|
||||
};
|
||||
await _update(userId, updates);
|
||||
}
|
||||
|
||||
static Future deleteUser(int userId) async {
|
||||
await dbProvider.db!.delete(
|
||||
tableName,
|
||||
where: "$columnUserId = ?",
|
||||
whereArgs: [userId],
|
||||
);
|
||||
globalCallBackOnContactChange();
|
||||
}
|
||||
|
||||
static Future<bool> insertNewContact(
|
||||
String username, int userId, bool requested) async {
|
||||
try {
|
||||
int a = requested ? 1 : 0;
|
||||
await dbProvider.db!.insert(DbContacts.tableName, {
|
||||
DbContacts.columnDisplayName: username,
|
||||
DbContacts.columnUserId: userId,
|
||||
DbContacts.columnRequested: a
|
||||
});
|
||||
globalCallBackOnContactChange();
|
||||
return true;
|
||||
} catch (e) {
|
||||
Logger("contacts_model/getUsers").shout("$e");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,444 +0,0 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:cv/cv.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:sqflite_sqlcipher/sqflite.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/app.dart';
|
||||
import 'package:twonly/src/components/message_send_state_icon.dart';
|
||||
import 'package:twonly/src/model/json/message.dart';
|
||||
import 'package:twonly/src/providers/api/api.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
||||
class DbMessage {
|
||||
DbMessage({
|
||||
required this.messageId,
|
||||
required this.messageOtherId,
|
||||
required this.otherUserId,
|
||||
required this.messageContent,
|
||||
required this.messageOpenedAt,
|
||||
required this.messageAcknowledgeByUser,
|
||||
required this.isDownloaded,
|
||||
required this.messageAcknowledgeByServer,
|
||||
required this.sendAt,
|
||||
});
|
||||
|
||||
int messageId;
|
||||
// is this null then the message was sent from the user itself
|
||||
int? messageOtherId;
|
||||
int otherUserId;
|
||||
MessageContent messageContent;
|
||||
DateTime? messageOpenedAt;
|
||||
bool messageAcknowledgeByUser;
|
||||
bool isDownloaded;
|
||||
bool messageAcknowledgeByServer;
|
||||
DateTime sendAt;
|
||||
|
||||
bool containsOtherMedia() {
|
||||
if (messageOtherId == null) return false;
|
||||
return isMedia();
|
||||
}
|
||||
|
||||
bool get messageReceived => messageOtherId != null;
|
||||
|
||||
bool isRealTwonly() {
|
||||
final content = messageContent;
|
||||
if (content is MediaMessageContent) {
|
||||
if (content.isRealTwonly) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isMedia() {
|
||||
return messageContent is MediaMessageContent;
|
||||
}
|
||||
|
||||
MessageSendState getSendState() {
|
||||
MessageSendState state;
|
||||
if (!messageAcknowledgeByServer) {
|
||||
state = MessageSendState.sending;
|
||||
} else {
|
||||
if (messageOtherId == null) {
|
||||
// message send
|
||||
if (messageOpenedAt == null) {
|
||||
state = MessageSendState.send;
|
||||
} else {
|
||||
state = MessageSendState.sendOpened;
|
||||
}
|
||||
} else {
|
||||
// message received
|
||||
if (messageOpenedAt == null) {
|
||||
state = MessageSendState.received;
|
||||
} else {
|
||||
state = MessageSendState.receivedOpened;
|
||||
}
|
||||
}
|
||||
}
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
class DbMessages extends CvModelBase {
|
||||
static const tableName = "messages";
|
||||
|
||||
static const columnMessageId = "id";
|
||||
final messageId = CvField<int>(columnMessageId);
|
||||
|
||||
static const columnMessageOtherId = "message_other_id";
|
||||
final messageOtherId = CvField<int?>(columnMessageOtherId);
|
||||
|
||||
static const columnOtherUserId = "other_user_id";
|
||||
final otherUserId = CvField<int>(columnOtherUserId);
|
||||
|
||||
static const columnMessageKind = "message_kind";
|
||||
final messageKind = CvField<int>(columnMessageKind);
|
||||
|
||||
static const columnMessageContentJson = "message_json";
|
||||
final messageContentJson = CvField<String>(columnMessageContentJson);
|
||||
|
||||
static const columnMessageOpenedAt = "message_opened_at";
|
||||
final messageOpenedAt = CvField<DateTime?>(columnMessageOpenedAt);
|
||||
|
||||
static const columnMessageAcknowledgeByUser = "message_acknowledged_by_user";
|
||||
final messageAcknowledgeByUser = CvField<int>(columnMessageAcknowledgeByUser);
|
||||
|
||||
static const columnMessageAcknowledgeByServer =
|
||||
"message_acknowledged_by_server";
|
||||
final messageAcknowledgeByServer =
|
||||
CvField<int>(columnMessageAcknowledgeByServer);
|
||||
|
||||
static const columnSendAt = "message_send_or_received_at";
|
||||
final sendAt = CvField<DateTime>(columnSendAt);
|
||||
|
||||
static const columnUpdatedAt = "updated_at";
|
||||
final updatedAt = CvField<DateTime>(columnUpdatedAt);
|
||||
|
||||
static Future setupDatabaseTable(Database db) async {
|
||||
String createTableString = """
|
||||
CREATE TABLE IF NOT EXISTS $tableName (
|
||||
$columnMessageId INTEGER NOT NULL PRIMARY KEY,
|
||||
$columnMessageOtherId INTEGER DEFAULT NULL,
|
||||
$columnOtherUserId INTEGER NOT NULL,
|
||||
$columnMessageKind INTEGER NOT NULL,
|
||||
$columnMessageAcknowledgeByUser INTEGER NOT NULL DEFAULT 0,
|
||||
$columnMessageAcknowledgeByServer INTEGER NOT NULL DEFAULT 0,
|
||||
$columnMessageContentJson TEXT NOT NULL,
|
||||
$columnMessageOpenedAt DATETIME DEFAULT NULL,
|
||||
$columnSendAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
$columnUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
""";
|
||||
await db.execute(createTableString);
|
||||
}
|
||||
|
||||
static Future<List<(DateTime, int?)>> getMessageDates(int otherUserId) async {
|
||||
final List<Map<String, dynamic>> maps = await dbProvider.db!.rawQuery('''
|
||||
SELECT $columnSendAt, $columnMessageOtherId
|
||||
FROM $tableName
|
||||
WHERE $columnOtherUserId = ? AND ($columnMessageKind = ? OR $columnMessageKind = ?)
|
||||
ORDER BY $columnSendAt DESC;
|
||||
''', [otherUserId, MessageKind.image.index, MessageKind.video.index]);
|
||||
|
||||
try {
|
||||
return List.generate(maps.length, (i) {
|
||||
return (
|
||||
DateTime.tryParse(maps[i][columnSendAt])!,
|
||||
maps[i][columnMessageOtherId]
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
Logger("error parsing datetime: $e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int?> deleteMessageById(int messageId) async {
|
||||
await dbProvider.db!.delete(
|
||||
tableName,
|
||||
where: '$columnMessageId = ?',
|
||||
whereArgs: [messageId],
|
||||
);
|
||||
int? fromUserId = await getFromUserIdByMessageId(messageId);
|
||||
if (fromUserId != null) {
|
||||
globalCallBackOnMessageChange(fromUserId, messageId);
|
||||
}
|
||||
return fromUserId;
|
||||
}
|
||||
|
||||
static Future<int?> getFromUserIdByMessageId(int messageId) async {
|
||||
List<Map<String, dynamic>> result = await dbProvider.db!.query(
|
||||
tableName,
|
||||
columns: [columnOtherUserId],
|
||||
where: '$columnMessageId = ?',
|
||||
whereArgs: [messageId],
|
||||
);
|
||||
if (result.isNotEmpty) {
|
||||
return result.first[columnOtherUserId] as int?;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<int?> insertMyMessage(int userIdFrom, MessageKind kind,
|
||||
MessageContent content, DateTime messageSendAt) async {
|
||||
try {
|
||||
int messageId = await dbProvider.db!.insert(tableName, {
|
||||
columnMessageKind: kind.index,
|
||||
columnMessageContentJson: jsonEncode(content.toJson()),
|
||||
columnOtherUserId: userIdFrom,
|
||||
columnSendAt: messageSendAt.toIso8601String()
|
||||
});
|
||||
globalCallBackOnMessageChange(userIdFrom, messageId);
|
||||
return messageId;
|
||||
} catch (e) {
|
||||
Logger("messsage_model/insertMyMessage").shout("$e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<int?> insertOtherMessage(int userIdFrom, MessageKind kind,
|
||||
int messageOtherId, String jsonContent, DateTime messageSendAt) async {
|
||||
try {
|
||||
int messageId = await dbProvider.db!.insert(tableName, {
|
||||
columnMessageOtherId: messageOtherId,
|
||||
columnMessageKind: kind.index,
|
||||
columnMessageContentJson: jsonContent,
|
||||
columnMessageAcknowledgeByServer: 1,
|
||||
columnMessageAcknowledgeByUser:
|
||||
0, // ack in case of sending corresponds to the opened flag
|
||||
columnOtherUserId: userIdFrom,
|
||||
columnSendAt: messageSendAt.toIso8601String()
|
||||
});
|
||||
globalCallBackOnMessageChange(userIdFrom, messageId);
|
||||
return messageId;
|
||||
} catch (e) {
|
||||
Logger("messsage_model/insertOtherMessage").shout("$e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<List<DbMessage>> getAllMessagesForUserWithHigherMessageId(
|
||||
int otherUserId, int lastMessageId) async {
|
||||
var rows = await dbProvider.db!.query(
|
||||
tableName,
|
||||
where: "$columnOtherUserId = ? AND $columnMessageId > ?",
|
||||
whereArgs: [otherUserId, lastMessageId],
|
||||
orderBy: "$columnUpdatedAt DESC",
|
||||
);
|
||||
|
||||
List<DbMessage> messages = await convertToDbMessage(rows);
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
static Future<List<DbMessage>> getAllMessagesForUser(int otherUserId) async {
|
||||
var rows = await dbProvider.db!.query(
|
||||
tableName,
|
||||
where: "$columnOtherUserId = ?",
|
||||
whereArgs: [otherUserId],
|
||||
orderBy: "$columnSendAt DESC",
|
||||
);
|
||||
|
||||
List<DbMessage> messages = await convertToDbMessage(rows);
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
static Future<DbMessage?> getMessageById(int messageId) async {
|
||||
var rows = await dbProvider.db!.query(tableName,
|
||||
where: "$columnMessageId = ?", whereArgs: [messageId]);
|
||||
List<DbMessage> messages = await convertToDbMessage(rows);
|
||||
return messages.firstOrNull;
|
||||
}
|
||||
|
||||
static Future<List<DbMessage>> getAllMessagesForRetransmitting() async {
|
||||
var rows = await dbProvider.db!.query(
|
||||
tableName,
|
||||
where: "$columnMessageAcknowledgeByServer = 0",
|
||||
);
|
||||
List<DbMessage> messages = await convertToDbMessage(rows);
|
||||
return messages;
|
||||
}
|
||||
|
||||
static Future<DbMessage?> getLastMessagesForPreviewForUser(
|
||||
int otherUserId) async {
|
||||
var rows = await dbProvider.db!.query(
|
||||
tableName,
|
||||
where: "$columnOtherUserId = ?",
|
||||
whereArgs: [otherUserId],
|
||||
orderBy: "$columnUpdatedAt DESC",
|
||||
limit: 10,
|
||||
);
|
||||
|
||||
List<DbMessage> messages = await convertToDbMessage(rows);
|
||||
|
||||
// check if you received a message which the user has not already opened
|
||||
List<DbMessage> receivedByOther = messages
|
||||
.where((c) => c.messageOtherId != null && c.messageOpenedAt == null)
|
||||
.toList();
|
||||
if (receivedByOther.isNotEmpty) {
|
||||
return receivedByOther[receivedByOther.length - 1];
|
||||
}
|
||||
|
||||
// check if there is a message which was not ack by the server
|
||||
List<DbMessage> notAckByServer =
|
||||
messages.where((c) => !c.messageAcknowledgeByServer).toList();
|
||||
if (notAckByServer.isNotEmpty) return notAckByServer[0];
|
||||
|
||||
// check if there is a message which was not ack by the user
|
||||
List<DbMessage> notAckByUser =
|
||||
messages.where((c) => !c.messageAcknowledgeByUser).toList();
|
||||
if (notAckByUser.isNotEmpty) return notAckByUser[0];
|
||||
|
||||
if (messages.isEmpty) return null;
|
||||
return messages[0];
|
||||
}
|
||||
|
||||
static Future _updateByMessageId(int messageId, Map<String, dynamic> data,
|
||||
{bool notifyFlutterState = true}) async {
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
data,
|
||||
where: "$columnMessageId = ?",
|
||||
whereArgs: [messageId],
|
||||
);
|
||||
if (notifyFlutterState) {
|
||||
int? fromUserId = await getFromUserIdByMessageId(messageId);
|
||||
if (fromUserId != null) {
|
||||
globalCallBackOnMessageChange(fromUserId, messageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future _updateByOtherMessageId(
|
||||
int fromUserId, int messageId, Map<String, dynamic> data) async {
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
data,
|
||||
where: "$columnMessageOtherId = ?",
|
||||
whereArgs: [messageId],
|
||||
);
|
||||
globalCallBackOnMessageChange(fromUserId, messageId);
|
||||
}
|
||||
|
||||
// this ensures that the message id can be spoofed by another person
|
||||
static Future _updateByMessageIdOther(
|
||||
int fromUserId, int messageId, Map<String, dynamic> data) async {
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
data,
|
||||
where: "$columnMessageId = ? AND $columnOtherUserId = ?",
|
||||
whereArgs: [messageId, fromUserId],
|
||||
);
|
||||
globalCallBackOnMessageChange(fromUserId, messageId);
|
||||
}
|
||||
|
||||
static Future userOpenedOtherMessage(
|
||||
int fromUserId, int otherMessageId) async {
|
||||
Map<String, dynamic> data = {
|
||||
columnMessageOpenedAt: DateTime.now().toIso8601String(),
|
||||
};
|
||||
await _updateByOtherMessageId(fromUserId, otherMessageId, data);
|
||||
}
|
||||
|
||||
static Future otherUserOpenedMyMessage(
|
||||
int fromUserId, int messageId, DateTime openedAt) async {
|
||||
Map<String, dynamic> data = {
|
||||
columnMessageOpenedAt: openedAt.toIso8601String(),
|
||||
};
|
||||
await _updateByMessageIdOther(fromUserId, messageId, data);
|
||||
}
|
||||
|
||||
static Future acknowledgeMessageByServer(int messageId) async {
|
||||
Map<String, dynamic> data = {
|
||||
columnMessageAcknowledgeByServer: 1,
|
||||
};
|
||||
await _updateByMessageId(messageId, data);
|
||||
}
|
||||
|
||||
// check fromUserId to prevent spoofing
|
||||
static Future acknowledgeMessageByUser(int fromUserId, int messageId) async {
|
||||
Map<String, dynamic> valuesToUpdate = {
|
||||
columnMessageAcknowledgeByUser: 1,
|
||||
};
|
||||
await dbProvider.db!.update(
|
||||
tableName,
|
||||
valuesToUpdate,
|
||||
where: "$messageId = ? AND $columnOtherUserId = ?",
|
||||
whereArgs: [messageId, fromUserId],
|
||||
);
|
||||
globalCallBackOnMessageChange(fromUserId, messageId);
|
||||
}
|
||||
|
||||
@override
|
||||
List<CvField> get fields =>
|
||||
[messageId, messageKind, messageContentJson, messageOpenedAt, sendAt];
|
||||
|
||||
// TODO: The message meta is needed to maintain the flame. Delete if not.
|
||||
// This function should calculate if this message is needed for the flame calculation and delete the message complete and not only
|
||||
// the message content.
|
||||
static Future deleteTextContent(
|
||||
int messageId, TextMessageContent oldMessage) async {
|
||||
oldMessage.text = "";
|
||||
Map<String, dynamic> data = {
|
||||
columnMessageContentJson: jsonEncode(oldMessage.toJson()),
|
||||
};
|
||||
await _updateByMessageId(messageId, data, notifyFlutterState: false);
|
||||
}
|
||||
|
||||
static Future<List<DbMessage>> convertToDbMessage(
|
||||
List<dynamic> fromDb) async {
|
||||
try {
|
||||
List<DbMessage> parsedUsers = [];
|
||||
final box = await getMediaStorage();
|
||||
for (int i = 0; i < fromDb.length; i++) {
|
||||
dynamic messageOpenedAt = fromDb[i][columnMessageOpenedAt];
|
||||
|
||||
MessageContent content = MessageContent.fromJson(
|
||||
jsonDecode(fromDb[i][columnMessageContentJson]));
|
||||
|
||||
var tmp = content;
|
||||
if (messageOpenedAt != null) {
|
||||
messageOpenedAt = DateTime.tryParse(fromDb[i][columnMessageOpenedAt]);
|
||||
if (tmp is TextMessageContent && messageOpenedAt != null) {
|
||||
if (calculateTimeDifference(DateTime.now(), messageOpenedAt)
|
||||
.inHours >=
|
||||
24) {
|
||||
deleteTextContent(fromDb[i][columnMessageId], tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
int? messageOtherId = fromDb[i][columnMessageOtherId];
|
||||
|
||||
bool isDownloaded = true;
|
||||
if (messageOtherId != null) {
|
||||
if (content is MediaMessageContent) {
|
||||
// when the media was send from the user itself the content is null
|
||||
isDownloaded =
|
||||
box.containsKey("${content.downloadToken}_downloaded");
|
||||
}
|
||||
}
|
||||
parsedUsers.add(
|
||||
DbMessage(
|
||||
sendAt: DateTime.tryParse(fromDb[i][columnSendAt])!,
|
||||
messageId: fromDb[i][columnMessageId],
|
||||
messageOtherId: messageOtherId,
|
||||
otherUserId: fromDb[i][columnOtherUserId],
|
||||
messageContent: content,
|
||||
isDownloaded: isDownloaded,
|
||||
messageOpenedAt: messageOpenedAt,
|
||||
messageAcknowledgeByUser:
|
||||
fromDb[i][columnMessageAcknowledgeByUser] == 1,
|
||||
messageAcknowledgeByServer:
|
||||
fromDb[i][columnMessageAcknowledgeByServer] == 1,
|
||||
),
|
||||
);
|
||||
}
|
||||
return parsedUsers;
|
||||
} catch (e) {
|
||||
Logger("messages_model/convertToDbMessage").shout("$e");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,11 @@ MessageSendState messageSendStateFromMessage(Message msg) {
|
|||
MessageSendState state;
|
||||
|
||||
if (!msg.acknowledgeByServer) {
|
||||
state = MessageSendState.sending;
|
||||
if (msg.messageOtherId == null) {
|
||||
state = MessageSendState.sending;
|
||||
} else {
|
||||
state = MessageSendState.receiving;
|
||||
}
|
||||
} else {
|
||||
if (msg.messageOtherId == null) {
|
||||
// message send
|
||||
|
|
@ -66,15 +70,12 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
textMsg = msg;
|
||||
}
|
||||
if (msg.kind == MessageKind.media) {
|
||||
MessageJson message =
|
||||
MessageJson.fromJson(jsonDecode(msg.contentJson!));
|
||||
final content = message.content;
|
||||
if (content is MediaMessageContent) {
|
||||
if (content.isVideo) {
|
||||
videoMsg = msg;
|
||||
} else {
|
||||
imageMsg = msg;
|
||||
}
|
||||
MediaMessageContent content =
|
||||
MediaMessageContent.fromJson(jsonDecode(msg.contentJson!));
|
||||
if (content.isVideo) {
|
||||
videoMsg = msg;
|
||||
} else {
|
||||
imageMsg = msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -110,9 +111,10 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
Widget icon = Placeholder();
|
||||
|
||||
MessageSendState state = messageSendStateFromMessage(message);
|
||||
MessageJson msg = MessageJson.fromJson(jsonDecode(message.contentJson!));
|
||||
if (msg.content == null) continue;
|
||||
Color color = getMessageColorFromType(msg.content!, twonlyColor);
|
||||
MessageContent? content = MessageContent.fromJson(
|
||||
message.kind, jsonDecode(message.contentJson!));
|
||||
if (content == null) continue;
|
||||
Color color = getMessageColorFromType(content, twonlyColor);
|
||||
|
||||
switch (state) {
|
||||
case MessageSendState.receivedOpened:
|
||||
|
|
@ -139,16 +141,20 @@ class _MessageSendStateIconState extends State<MessageSendStateIcon> {
|
|||
break;
|
||||
}
|
||||
|
||||
if (message.downloadState == DownloadState.pending) {
|
||||
text = context.lang.messageSendState_TapToLoad;
|
||||
}
|
||||
if (message.downloadState == DownloadState.downloaded) {
|
||||
text = context.lang.messageSendState_Loading;
|
||||
icon = getLoaderIcon(color);
|
||||
if (message.kind == MessageKind.media) {
|
||||
if (message.downloadState == DownloadState.pending) {
|
||||
text = context.lang.messageSendState_TapToLoad;
|
||||
}
|
||||
if (message.downloadState == DownloadState.downloaded) {
|
||||
text = context.lang.messageSendState_Loading;
|
||||
icon = getLoaderIcon(color);
|
||||
}
|
||||
}
|
||||
icons.add(icon);
|
||||
}
|
||||
|
||||
if (icons.isEmpty) return Container();
|
||||
|
||||
Widget icon = icons[0];
|
||||
|
||||
if (icons.length == 2) {
|
||||
|
|
|
|||
|
|
@ -29,23 +29,34 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
|||
|
||||
Stream<List<Message>> watchMessageNotOpened(int contactId) {
|
||||
return (select(messages)
|
||||
..where((t) => t.openedAt.isNull() & t.contactId.equals(contactId)))
|
||||
..where((t) => t.openedAt.isNull() & t.contactId.equals(contactId))
|
||||
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
|
||||
.watch();
|
||||
}
|
||||
|
||||
Stream<Message?> watchLastMessage(int contactId) {
|
||||
Stream<List<Message>> watchLastMessage(int contactId) {
|
||||
return (select(messages)
|
||||
..where((t) => t.contactId.equals(contactId))
|
||||
..orderBy([(t) => OrderingTerm.desc(t.sendAt)])
|
||||
..limit(1))
|
||||
.watchSingleOrNull();
|
||||
.watch();
|
||||
}
|
||||
|
||||
Stream<List<Message>> watchAllMessagesFrom(int contactId) {
|
||||
return (select(messages)..where((t) => t.contactId.equals(contactId)))
|
||||
return (select(messages)
|
||||
..where((t) => t.contactId.equals(contactId))
|
||||
..orderBy([(t) => OrderingTerm.desc(t.sendAt)]))
|
||||
.watch();
|
||||
}
|
||||
|
||||
Future<List<Message>> getAllMessagesPendingDownloading() {
|
||||
return (select(messages)
|
||||
..where((t) =>
|
||||
t.downloadState.equals(DownloadState.downloaded.index).not() &
|
||||
t.kind.equals(MessageKind.media.name)))
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<List<Message>> getAllMessagesForRetransmitting() {
|
||||
return (select(messages)..where((t) => t.acknowledgeByServer.equals(false)))
|
||||
.get();
|
||||
|
|
@ -168,7 +179,9 @@ class TwonlyDatabase extends _$TwonlyDatabase {
|
|||
|
||||
Stream<int?> watchContactsRequested() {
|
||||
final count = contacts.requested.count(distinct: true);
|
||||
final query = selectOnly(contacts)..where(contacts.requested.equals(true));
|
||||
final query = selectOnly(contacts)
|
||||
..where(contacts.requested.equals(true) &
|
||||
contacts.accepted.equals(true).not());
|
||||
query.addColumns([count]);
|
||||
return query.map((row) => row.read(count)).watchSingle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -756,7 +756,7 @@ class $MessagesTable extends Messages with TableInfo<$MessagesTable, Message> {
|
|||
downloadState = GeneratedColumn<int>('download_state', aliasedName, false,
|
||||
type: DriftSqlType.int,
|
||||
requiredDuringInsert: false,
|
||||
defaultValue: Constant(DownloadState.pending.index))
|
||||
defaultValue: Constant(DownloadState.downloaded.index))
|
||||
.withConverter<DownloadState>($MessagesTable.$converterdownloadState);
|
||||
static const VerificationMeta _acknowledgeByServerMeta =
|
||||
const VerificationMeta('acknowledgeByServer');
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class Messages extends Table {
|
|||
|
||||
BoolColumn get acknowledgeByUser => boolean().withDefault(Constant(false))();
|
||||
IntColumn get downloadState => intEnum<DownloadState>()
|
||||
.withDefault(Constant(DownloadState.pending.index))();
|
||||
.withDefault(Constant(DownloadState.downloaded.index))();
|
||||
|
||||
BoolColumn get acknowledgeByServer =>
|
||||
boolean().withDefault(Constant(false))();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,25 @@ import 'package:twonly/src/utils/misc.dart';
|
|||
// ignore: library_prefixes
|
||||
import 'package:twonly/src/utils/signal.dart' as SignalHelper;
|
||||
|
||||
|
||||
Future tryDownloadAllMediaFiles() async {
|
||||
|
||||
if (!await isAllowedToDownload()) {
|
||||
return;
|
||||
}
|
||||
List<Message> messages =
|
||||
await twonlyDatabase.getAllMessagesPendingDownloading();
|
||||
|
||||
for (Message message in messages) {
|
||||
MessageContent? content = MessageContent.fromJson(message.kind, jsonDecode(message.contentJson!));
|
||||
|
||||
if (content is MediaMessageContent) {
|
||||
tryDownloadMedia(message.messageId, message.contactId, content.downloadToken);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Future tryTransmitMessages() async {
|
||||
List<Message> retransmit =
|
||||
await twonlyDatabase.getAllMessagesForRetransmitting();
|
||||
|
|
@ -36,10 +55,10 @@ Future tryTransmitMessages() async {
|
|||
);
|
||||
|
||||
if (resp.isSuccess) {
|
||||
await twonlyDatabase.updateMessageByMessageId(
|
||||
msgId,
|
||||
MessagesCompanion(acknowledgeByServer: Value(true))
|
||||
);
|
||||
await twonlyDatabase.updateMessageByMessageId(
|
||||
msgId,
|
||||
MessagesCompanion(acknowledgeByServer: Value(true))
|
||||
);
|
||||
|
||||
box.delete("retransmit-$msgId-textmessage");
|
||||
} else {
|
||||
|
|
@ -49,11 +68,9 @@ Future tryTransmitMessages() async {
|
|||
|
||||
Uint8List? encryptedMedia = await box.get("retransmit-$msgId-media");
|
||||
if (encryptedMedia != null) {
|
||||
final content = MessageJson.fromJson(jsonDecode(retransmit[i].contentJson!)).content;
|
||||
if (content is MediaMessageContent) {
|
||||
MediaMessageContent content = MediaMessageContent.fromJson(jsonDecode(retransmit[i].contentJson!));
|
||||
uploadMediaFile(msgId, retransmit[i].contactId, encryptedMedia,
|
||||
content.isRealTwonly, content.maxShowTime, retransmit[i].sendAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ Future<client.Response> handleNewMessage(int fromUserId, Uint8List body) async {
|
|||
kind: Value(message.kind),
|
||||
messageOtherId: Value(message.messageId),
|
||||
contentJson: Value(content),
|
||||
downloadState: Value(DownloadState.downloaded),
|
||||
sendAt: Value(message.timestamp),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -70,10 +70,10 @@ class ApiProvider {
|
|||
Future onConnected() async {
|
||||
await authenticate();
|
||||
globalCallbackConnectionState(true);
|
||||
// _reconnectionDelay = 5;
|
||||
|
||||
if (!globalIsAppInBackground) {
|
||||
tryTransmitMessages();
|
||||
tryDownloadAllMediaFiles();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||
|
|
@ -235,3 +236,13 @@ Future<bool> authenticateUser(String localizedReason,
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> isAllowedToDownload() async {
|
||||
final List<ConnectivityResult> connectivityResult =
|
||||
await (Connectivity().checkConnectivity());
|
||||
if (connectivityResult.contains(ConnectivityResult.mobile)) {
|
||||
Logger("tryDownloadMedia").info("abort download over mobile connection");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/model/json/user_data.dart';
|
||||
import 'package:twonly/src/utils/misc.dart';
|
||||
|
|
@ -32,6 +33,12 @@ Future<bool> deleteLocalUserData() async {
|
|||
final storage = getSecureStorage();
|
||||
var password = await storage.read(key: "sqflite_database_password");
|
||||
await dbProvider.remove();
|
||||
|
||||
final appDir = await getApplicationSupportDirectory();
|
||||
if (appDir.existsSync()) {
|
||||
appDir.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
await storage.write(key: "sqflite_database_password", value: password);
|
||||
await storage.deleteAll();
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@ class ChatListEntry extends StatelessWidget {
|
|||
bool isDownloading = false;
|
||||
List<int> token = [];
|
||||
|
||||
final messageJson = MessageJson.fromJson(jsonDecode(message.contentJson!));
|
||||
final content = messageJson.content;
|
||||
MessageContent? content =
|
||||
MessageContent.fromJson(message.kind, jsonDecode(message.contentJson!));
|
||||
|
||||
if (message.messageOtherId != null && content is MediaMessageContent) {
|
||||
token = content.downloadToken;
|
||||
isDownloading = message.downloadState == DownloadState.downloading;
|
||||
|
|
|
|||
|
|
@ -36,9 +36,45 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
Stream<List<Contact>> contacts = twonlyDatabase.watchContactsForChatList();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: GestureDetector(
|
||||
onTap: () {
|
||||
appBar: AppBar(
|
||||
title: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ProfileView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text("twonly"),
|
||||
),
|
||||
// title:
|
||||
actions: [
|
||||
StreamBuilder(
|
||||
stream: twonlyDatabase.watchContactsRequested(),
|
||||
builder: (context, snapshot) {
|
||||
var count = 0;
|
||||
if (snapshot.hasData && snapshot.data != null) {
|
||||
count = snapshot.data!;
|
||||
}
|
||||
return NotificationBadge(
|
||||
count: count.toString(),
|
||||
child: IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.userPlus, size: 18),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SearchUsernameView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
|
|
@ -46,21 +82,24 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
),
|
||||
);
|
||||
},
|
||||
child: Text("twonly"),
|
||||
),
|
||||
// title:
|
||||
actions: [
|
||||
StreamBuilder(
|
||||
stream: twonlyDatabase.watchContactsRequested(),
|
||||
builder: (context, snapshot) {
|
||||
var count = 0;
|
||||
if (snapshot.hasData && snapshot.data != null) {
|
||||
count = snapshot.data!;
|
||||
}
|
||||
return NotificationBadge(
|
||||
count: count.toString(),
|
||||
child: IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.userPlus, size: 18),
|
||||
icon: FaIcon(FontAwesomeIcons.gear, size: 19),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: StreamBuilder(
|
||||
stream: contacts,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final contacts = snapshot.data!;
|
||||
if (contacts.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: OutlinedButton.icon(
|
||||
icon: Icon(Icons.person_add),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
|
|
@ -69,68 +108,32 @@ class _ChatListViewState extends State<ChatListView> {
|
|||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ProfileView(),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: FaIcon(FontAwesomeIcons.gear, size: 19),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: StreamBuilder(
|
||||
stream: contacts,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData || snapshot.data == null) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final contacts = snapshot.data!;
|
||||
if (contacts.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: OutlinedButton.icon(
|
||||
icon: Icon(Icons.person_add),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => SearchUsernameView()));
|
||||
},
|
||||
label: Text(context.lang.chatListViewSearchUserNameBtn)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
int maxTotalMediaCounter = 0;
|
||||
if (contacts.isNotEmpty) {
|
||||
maxTotalMediaCounter = contacts
|
||||
.map((x) => x.totalMediaCounter)
|
||||
.reduce((a, b) => a > b ? a : b);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
restorationId: 'chat_list_view',
|
||||
itemCount: contacts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final user = contacts[index];
|
||||
return UserListItem(
|
||||
user: user,
|
||||
maxTotalMediaCounter: maxTotalMediaCounter,
|
||||
);
|
||||
},
|
||||
label: Text(context.lang.chatListViewSearchUserNameBtn)),
|
||||
),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
int maxTotalMediaCounter = 0;
|
||||
if (contacts.isNotEmpty) {
|
||||
maxTotalMediaCounter = contacts
|
||||
.map((x) => x.totalMediaCounter)
|
||||
.reduce((a, b) => a > b ? a : b);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
restorationId: 'chat_list_view',
|
||||
itemCount: contacts.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final user = contacts[index];
|
||||
return UserListItem(
|
||||
user: user,
|
||||
maxTotalMediaCounter: maxTotalMediaCounter,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,22 +159,9 @@ class _UserListItem extends State<UserListItem> {
|
|||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// initAsync();
|
||||
lastUpdateTime();
|
||||
}
|
||||
|
||||
// Future initAsync() async {
|
||||
// if (currentMessage != null) {
|
||||
// if (currentMessage!.downloadState != DownloadState.downloading) {
|
||||
// final content = widget.lastMessage!.messageContent;
|
||||
// if (content is MediaMessageContent) {
|
||||
// tryDownloadMedia(widget.lastMessage!.messageId,
|
||||
// widget.lastMessage!.otherUserId, content.downloadToken);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
void lastUpdateTime() {
|
||||
// Change the color every 200 milliseconds
|
||||
updateTime = Timer.periodic(Duration(milliseconds: 200), (timer) {
|
||||
|
|
@ -193,25 +183,6 @@ class _UserListItem extends State<UserListItem> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final notOpenedMessages =
|
||||
twonlyDatabase.watchMessageNotOpened(widget.user.userId);
|
||||
final lastMessage = twonlyDatabase.watchLastMessage(widget.user.userId);
|
||||
|
||||
// if (widget.lastMessage != null) {
|
||||
// state = widget.lastMessage!.getSendState();
|
||||
|
||||
// final content = widget.lastMessage!.messageContent;
|
||||
|
||||
// if (widget.lastMessage!.messageReceived &&
|
||||
// content is MediaMessageContent) {
|
||||
// token = content.downloadToken;
|
||||
// isDownloading = context
|
||||
// .watch<DownloadChangeProvider>()
|
||||
// .currentlyDownloading
|
||||
// .contains(token.toString());
|
||||
// }
|
||||
// }
|
||||
|
||||
int flameCounter = getFlameCounterFromContact(widget.user);
|
||||
|
||||
return UserContextMenu(
|
||||
|
|
@ -219,25 +190,35 @@ class _UserListItem extends State<UserListItem> {
|
|||
child: ListTile(
|
||||
title: Text(getContactDisplayName(widget.user)),
|
||||
subtitle: StreamBuilder(
|
||||
stream: lastMessage,
|
||||
stream: twonlyDatabase.watchLastMessage(widget.user.userId),
|
||||
builder: (context, lastMessageSnapshot) {
|
||||
if (!lastMessageSnapshot.hasData) {
|
||||
return Container();
|
||||
}
|
||||
if (lastMessageSnapshot.data == null) {
|
||||
if (lastMessageSnapshot.data!.isEmpty) {
|
||||
return Text(context.lang.chatsTapToSend);
|
||||
}
|
||||
final lastMessage = lastMessageSnapshot.data!;
|
||||
final lastMessage = lastMessageSnapshot.data!.first;
|
||||
return StreamBuilder(
|
||||
stream: notOpenedMessages,
|
||||
stream: twonlyDatabase.watchMessageNotOpened(widget.user.userId),
|
||||
builder: (context, notOpenedMessagesSnapshot) {
|
||||
if (!lastMessageSnapshot.hasData) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
var lastMessages = [lastMessage];
|
||||
if (notOpenedMessagesSnapshot.data != null) {
|
||||
if (notOpenedMessagesSnapshot.data != null &&
|
||||
notOpenedMessagesSnapshot.data!.isNotEmpty) {
|
||||
lastMessages = notOpenedMessagesSnapshot.data!;
|
||||
var media =
|
||||
lastMessages.where((x) => x.kind == MessageKind.media);
|
||||
if (media.isNotEmpty) {
|
||||
currentMessage = media.first;
|
||||
} else {
|
||||
currentMessage = lastMessages.first;
|
||||
}
|
||||
} else {
|
||||
currentMessage = lastMessage;
|
||||
}
|
||||
|
||||
return Row(
|
||||
|
|
|
|||
|
|
@ -86,10 +86,9 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
await _noScreenshot.screenshotOff();
|
||||
if (!context.mounted || allMediaFiles.isEmpty) return;
|
||||
|
||||
final Message current = allMediaFiles.first;
|
||||
final MessageJson messageJson =
|
||||
MessageJson.fromJson(jsonDecode(current.contentJson!));
|
||||
final MessageContent? content = messageJson.content;
|
||||
final current = allMediaFiles.first;
|
||||
final MediaMessageContent content =
|
||||
MediaMessageContent.fromJson(jsonDecode(current.contentJson!));
|
||||
|
||||
setState(() {
|
||||
// reset current image values
|
||||
|
|
@ -101,17 +100,16 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
|||
isRealTwonly = false;
|
||||
});
|
||||
|
||||
if (content is MediaMessageContent) {
|
||||
if (content.isRealTwonly) {
|
||||
setState(() {
|
||||
isRealTwonly = true;
|
||||
});
|
||||
if (!showTwonly) {
|
||||
return;
|
||||
}
|
||||
if (content.isRealTwonly) {
|
||||
setState(() {
|
||||
isRealTwonly = true;
|
||||
});
|
||||
if (!showTwonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRealTwonly) {
|
||||
if (!context.mounted) return;
|
||||
bool isAuth = await authenticateUser(context.lang.mediaViewerAuthReason,
|
||||
force: false);
|
||||
if (!isAuth) {
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ class _ContactViewState extends State<ContactView> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (getContactDisplayName(contact) != contact.username)
|
||||
Center(child: Text("(${contact.username})")),
|
||||
SizedBox(height: 50),
|
||||
BetterListTile(
|
||||
icon: FontAwesomeIcons.pencil,
|
||||
|
|
@ -127,7 +129,7 @@ class _ContactViewState extends State<ContactView> {
|
|||
Future<String?> showNicknameChangeDialog(
|
||||
BuildContext context, Contact contact) {
|
||||
final TextEditingController controller =
|
||||
TextEditingController(text: contact.displayName);
|
||||
TextEditingController(text: getContactDisplayName(contact));
|
||||
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
|
|
|
|||
Loading…
Reference in a new issue