mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 11:18:41 +00:00
sending text messages kinda works
This commit is contained in:
parent
f866e4315e
commit
e0d420b78d
14 changed files with 225 additions and 77 deletions
|
|
@ -24,12 +24,7 @@ class MessageSendStateIcon extends StatelessWidget {
|
||||||
Widget icon = Placeholder();
|
Widget icon = Placeholder();
|
||||||
String text = "";
|
String text = "";
|
||||||
|
|
||||||
Color color = Theme.of(context).colorScheme.primary;
|
Color color = kind.getColor(Theme.of(context).colorScheme.primary);
|
||||||
if (kind == MessageKind.textMessage) {
|
|
||||||
color = Colors.lightBlue;
|
|
||||||
} else if (kind == MessageKind.video) {
|
|
||||||
color = Colors.deepPurple;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case MessageSendState.receivedOpened:
|
case MessageSendState.receivedOpened:
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,10 @@
|
||||||
"searchUsernameNotFound": "Username not found",
|
"searchUsernameNotFound": "Username not found",
|
||||||
"searchUsernameNewFollowerTitle": "Follow requests",
|
"searchUsernameNewFollowerTitle": "Follow requests",
|
||||||
"searchUsernameQrCodeBtn": "Scan QR code",
|
"searchUsernameQrCodeBtn": "Scan QR code",
|
||||||
"chatListViewSearchUserNameBtn": "Add user",
|
"chatListViewSearchUserNameBtn": "Add your first twonly contact!",
|
||||||
"chatListViewSendFirstTwonly": "Send your first twonly!",
|
"chatListViewSendFirstTwonly": "Send your first twonly!",
|
||||||
|
"chatListDetailInput": "Type a message",
|
||||||
|
"chatListDetailTitle": "Your chat with {username}",
|
||||||
"searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.",
|
"searchUsernameNotFoundLong": "\"{username}\" is not a twonly user. Please check the username and try again.",
|
||||||
"errorUnknown": "An unexpected error has occurred. Please try again later.",
|
"errorUnknown": "An unexpected error has occurred. Please try again later.",
|
||||||
"errorBadRequest": "The request could not be understood by the server due to malformed syntax. Please check your input and try again.",
|
"errorBadRequest": "The request could not be understood by the server due to malformed syntax. Please check your input and try again.",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:twonly/src/utils/json.dart';
|
import 'package:twonly/src/utils/json.dart';
|
||||||
part 'message.g.dart';
|
part 'message.g.dart';
|
||||||
|
|
@ -9,6 +10,7 @@ enum MessageKind {
|
||||||
contactRequest,
|
contactRequest,
|
||||||
rejectRequest,
|
rejectRequest,
|
||||||
acceptRequest,
|
acceptRequest,
|
||||||
|
opened,
|
||||||
ack
|
ack
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,6 +26,16 @@ extension MessageKindExtension on MessageKind {
|
||||||
static MessageKind fromIndex(int index) {
|
static MessageKind fromIndex(int index) {
|
||||||
return MessageKind.values[index];
|
return MessageKind.values[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Color getColor(Color primary) {
|
||||||
|
Color color = primary;
|
||||||
|
if (this == MessageKind.textMessage) {
|
||||||
|
color = Colors.lightBlue;
|
||||||
|
} else if (this == MessageKind.video) {
|
||||||
|
color = Colors.deepPurple;
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// so _$MessageKindEnumMap gets generated
|
// so _$MessageKindEnumMap gets generated
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const _$MessageKindEnumMap = {
|
||||||
MessageKind.contactRequest: 'contactRequest',
|
MessageKind.contactRequest: 'contactRequest',
|
||||||
MessageKind.rejectRequest: 'rejectRequest',
|
MessageKind.rejectRequest: 'rejectRequest',
|
||||||
MessageKind.acceptRequest: 'acceptRequest',
|
MessageKind.acceptRequest: 'acceptRequest',
|
||||||
|
MessageKind.opened: 'opened',
|
||||||
MessageKind.ack: 'ack',
|
MessageKind.ack: 'ack',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import 'package:cv/cv.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/main.dart';
|
import 'package:twonly/main.dart';
|
||||||
import 'package:twonly/src/app.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/model/json/message.dart';
|
||||||
import 'package:twonly/src/providers/api/api.dart';
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
|
|
||||||
|
|
@ -37,6 +38,30 @@ class DbMessage {
|
||||||
if (messageOtherId == null) return false;
|
if (messageOtherId == null) return false;
|
||||||
return messageKind == MessageKind.image || messageKind == MessageKind.video;
|
return messageKind == MessageKind.image || messageKind == MessageKind.video;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
class DbMessages extends CvModelBase {
|
||||||
|
|
@ -179,6 +204,12 @@ class DbMessages extends CvModelBase {
|
||||||
|
|
||||||
List<DbMessage> messages = await convertToDbMessage(rows);
|
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[0];
|
||||||
|
|
||||||
// check if there is a message which was not ack by the server
|
// check if there is a message which was not ack by the server
|
||||||
List<DbMessage> notAckByServer =
|
List<DbMessage> notAckByServer =
|
||||||
messages.where((c) => !c.messageAcknowledgeByServer).toList();
|
messages.where((c) => !c.messageAcknowledgeByServer).toList();
|
||||||
|
|
@ -198,7 +229,7 @@ class DbMessages extends CvModelBase {
|
||||||
await dbProvider.db!.update(
|
await dbProvider.db!.update(
|
||||||
tableName,
|
tableName,
|
||||||
data,
|
data,
|
||||||
where: "$messageId = ?",
|
where: "$columnMessageId = ?",
|
||||||
whereArgs: [messageId],
|
whereArgs: [messageId],
|
||||||
);
|
);
|
||||||
int? fromUserId = await getFromUserIdByMessageId(messageId);
|
int? fromUserId = await getFromUserIdByMessageId(messageId);
|
||||||
|
|
@ -207,6 +238,18 @@ class DbMessages extends CvModelBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
static Future userOpenedMessage(int messageId) async {
|
static Future userOpenedMessage(int messageId) async {
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
columnMessageOpenedAt: DateTime.now().toIso8601String(),
|
columnMessageOpenedAt: DateTime.now().toIso8601String(),
|
||||||
|
|
@ -214,6 +257,14 @@ class DbMessages extends CvModelBase {
|
||||||
await _updateByMessageId(messageId, data);
|
await _updateByMessageId(messageId, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future userOpenedMessageOtherUser(
|
||||||
|
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 {
|
static Future acknowledgeMessageByServer(int messageId) async {
|
||||||
Map<String, dynamic> data = {
|
Map<String, dynamic> data = {
|
||||||
columnMessageAcknowledgeByServer: 1,
|
columnMessageAcknowledgeByServer: 1,
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,24 @@ Future<Result> encryptAndSendMessage(Int64 userId, Message msg) async {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future sendTextMessage(Int64 target, String message) async {
|
||||||
|
MessageContent content = MessageContent(text: message, downloadToken: null);
|
||||||
|
|
||||||
|
int? messageId = await DbMessages.insertMyMessage(
|
||||||
|
target.toInt(), MessageKind.textMessage,
|
||||||
|
jsonContent: jsonEncode(content.toJson()));
|
||||||
|
if (messageId == null) return;
|
||||||
|
|
||||||
|
Message msg = Message(
|
||||||
|
kind: MessageKind.textMessage,
|
||||||
|
messageId: messageId,
|
||||||
|
content: content,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
await encryptAndSendMessage(target, msg);
|
||||||
|
}
|
||||||
|
|
||||||
Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async {
|
Future sendImageToSingleTarget(Int64 target, Uint8List imageBytes) async {
|
||||||
int? messageId =
|
int? messageId =
|
||||||
await DbMessages.insertMyMessage(target.toInt(), MessageKind.image);
|
await DbMessages.insertMyMessage(target.toInt(), MessageKind.image);
|
||||||
|
|
@ -118,15 +136,28 @@ Future tryDownloadMedia(List<int> imageToken, {bool force = false}) async {
|
||||||
apiProvider.triggerDownload(imageToken);
|
apiProvider.triggerDownload(imageToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future userOpenedMessage(int fromUserId, int messageId) async {
|
||||||
|
await DbMessages.userOpenedMessage(messageId);
|
||||||
|
|
||||||
|
encryptAndSendMessage(
|
||||||
|
Int64(fromUserId),
|
||||||
|
Message(
|
||||||
|
kind: MessageKind.opened,
|
||||||
|
messageId: messageId,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<Uint8List?> getDownloadedMedia(
|
Future<Uint8List?> getDownloadedMedia(
|
||||||
List<int> mediaToken, int messageId) async {
|
List<int> mediaToken, int messageId) async {
|
||||||
final box = await getMediaStorage();
|
final box = await getMediaStorage();
|
||||||
Uint8List? media = box.get("${mediaToken}_downloaded");
|
Uint8List? media = box.get("${mediaToken}_downloaded");
|
||||||
// box.delete(mediaToken.toString());
|
int fromUserId = box.get("${mediaToken}_fromUserId");
|
||||||
// box.delete("${mediaToken}_downloaded");
|
await userOpenedMessage(fromUserId, messageId);
|
||||||
// box.delete("${mediaToken}_fromUserId");
|
box.delete(mediaToken.toString());
|
||||||
// await DbMessages.userOpenedMessage(messageId);
|
box.put("${mediaToken}_downloaded", "deleted");
|
||||||
|
box.delete("${mediaToken}_fromUserId");
|
||||||
return media;
|
return media;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,13 @@ Future<client.Response> handleNewMessage(
|
||||||
utf8.decode(name), fromUserId.toInt(), true);
|
utf8.decode(name), fromUserId.toInt(), true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case MessageKind.opened:
|
||||||
|
await DbMessages.userOpenedMessageOtherUser(
|
||||||
|
fromUserId.toInt(),
|
||||||
|
message.messageId!,
|
||||||
|
message.timestamp,
|
||||||
|
);
|
||||||
|
break;
|
||||||
case MessageKind.rejectRequest:
|
case MessageKind.rejectRequest:
|
||||||
DbContacts.deleteUser(fromUserId.toInt());
|
DbContacts.deleteUser(fromUserId.toInt());
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ class ApiProvider {
|
||||||
|
|
||||||
final result = await _sendRequestV0(req);
|
final result = await _sendRequestV0(req);
|
||||||
if (result.isError) {
|
if (result.isError) {
|
||||||
log.shout(result);
|
log.shout("Error auth", result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -203,7 +203,7 @@ class ApiProvider {
|
||||||
|
|
||||||
final result2 = await _sendRequestV0(req2);
|
final result2 = await _sendRequestV0(req2);
|
||||||
if (result2.isError) {
|
if (result2.isError) {
|
||||||
log.shout(result2);
|
log.shout("send request failed: ${result2.error}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import 'package:twonly/src/model/messages_model.dart';
|
||||||
/// for every contact.
|
/// for every contact.
|
||||||
class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class MessagesChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
final Map<int, DbMessage> _lastMessage = <int, DbMessage>{};
|
final Map<int, DbMessage> _lastMessage = <int, DbMessage>{};
|
||||||
|
|
||||||
Map<int, DbMessage> get lastMessage => _lastMessage;
|
Map<int, DbMessage> get lastMessage => _lastMessage;
|
||||||
|
|
||||||
void updateLastMessageFor(int targetUserId) async {
|
void updateLastMessageFor(int targetUserId) async {
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,10 @@ class _CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onDoubleTap: () async {
|
||||||
|
cameraState.switchCameraSensor(
|
||||||
|
aspectRatio: CameraAspectRatios.ratio_16_9);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,28 @@
|
||||||
|
import 'package:cv/cv.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:twonly/src/components/message_send_state_icon.dart';
|
||||||
import 'package:twonly/src/model/contacts_model.dart';
|
import 'package:twonly/src/model/contacts_model.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/messages_model.dart';
|
import 'package:twonly/src/model/messages_model.dart';
|
||||||
|
import 'package:twonly/src/providers/api/api.dart';
|
||||||
|
import 'package:twonly/src/providers/messages_change_provider.dart';
|
||||||
|
import 'package:twonly/src/views/media_viewer_view.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
class ChatListEntry extends StatelessWidget {
|
class ChatListEntry extends StatelessWidget {
|
||||||
const ChatListEntry(this.message, {super.key});
|
const ChatListEntry(this.message, this.user, this.lastMessageFromSameUser,
|
||||||
|
{super.key});
|
||||||
final DbMessage message;
|
final DbMessage message;
|
||||||
|
final Contact user;
|
||||||
|
final bool lastMessageFromSameUser;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool right = message.messageOtherId == null;
|
bool right = message.messageOtherId == null;
|
||||||
|
|
||||||
|
MessageSendState state = message.getSendState();
|
||||||
|
|
||||||
Widget child = Container();
|
Widget child = Container();
|
||||||
|
|
||||||
switch (message.messageKind) {
|
switch (message.messageKind) {
|
||||||
|
|
@ -38,13 +50,52 @@ class ChatListEntry extends StatelessWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case MessageKind.image:
|
||||||
|
Color color =
|
||||||
|
message.messageKind.getColor(Theme.of(context).colorScheme.primary);
|
||||||
|
child = GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
if (state == MessageSendState.received) {
|
||||||
|
if (message.isDownloaded) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) {
|
||||||
|
return MediaViewerView(user, message);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
List<int> token = message.messageContent!.downloadToken!;
|
||||||
|
tryDownloadMedia(token, force: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
width: 200,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: color, // Set the background color
|
||||||
|
width: 1.0, // Set the border width here
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12.0), // Set border radius
|
||||||
|
),
|
||||||
|
child: MessageSendStateIcon(
|
||||||
|
state,
|
||||||
|
message.isDownloaded,
|
||||||
|
message.messageKind,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Align(
|
return Align(
|
||||||
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
alignment: right ? Alignment.centerRight : Alignment.centerLeft,
|
||||||
child: Padding(padding: EdgeInsets.all(10), child: child),
|
child: Padding(
|
||||||
|
padding: lastMessageFromSameUser
|
||||||
|
? EdgeInsets.only(top: 5, bottom: 0, right: 10, left: 10)
|
||||||
|
: EdgeInsets.all(10),
|
||||||
|
child: child),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +112,7 @@ class ChatItemDetailsView extends StatefulWidget {
|
||||||
|
|
||||||
class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
List<DbMessage> _messages = [];
|
List<DbMessage> _messages = [];
|
||||||
|
final TextEditingController newMessageController = TextEditingController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
@ -73,8 +125,23 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
await DbMessages.getAllMessagesForUser(widget.user.userId.toInt());
|
await DbMessages.getAllMessagesForUser(widget.user.userId.toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future _sendMessage() async {
|
||||||
|
String text = newMessageController.text;
|
||||||
|
if (text == "") return;
|
||||||
|
sendTextMessage(widget.user.userId, newMessageController.text);
|
||||||
|
newMessageController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final messages = context.watch<MessagesChangeProvider>().lastMessage;
|
||||||
|
if (messages.containsKey(widget.user.userId.toInt()) &&
|
||||||
|
_messages.isNotEmpty) {
|
||||||
|
final lastMessage = messages[widget.user.userId.toInt()];
|
||||||
|
if (lastMessage!.messageId != _messages[0].messageId) {
|
||||||
|
_loadAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
// messages = messages.reversed.toList();
|
// messages = messages.reversed.toList();
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
|
@ -87,7 +154,16 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
itemCount: _messages.length, // Number of items in the list
|
itemCount: _messages.length, // Number of items in the list
|
||||||
reverse: true,
|
reverse: true,
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
return ChatListEntry(_messages[i]);
|
bool lastMessageFromSameUser = false;
|
||||||
|
if (i > 0) {
|
||||||
|
lastMessageFromSameUser =
|
||||||
|
(_messages[i - 1].messageOtherId == null &&
|
||||||
|
_messages[i].messageOtherId == null) ||
|
||||||
|
(_messages[i - 1].messageOtherId != null &&
|
||||||
|
_messages[i].messageOtherId != null);
|
||||||
|
}
|
||||||
|
return ChatListEntry(
|
||||||
|
_messages[i], widget.user, lastMessageFromSameUser);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -98,9 +174,13 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
// controller: _controller,
|
controller: newMessageController,
|
||||||
|
onSubmitted: (_) {
|
||||||
|
_sendMessage();
|
||||||
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Type a message',
|
hintText:
|
||||||
|
AppLocalizations.of(context)!.chatListDetailInput,
|
||||||
contentPadding: EdgeInsets.symmetric(horizontal: 10)
|
contentPadding: EdgeInsets.symmetric(horizontal: 10)
|
||||||
// border: OutlineInputBorder(),
|
// border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
|
|
@ -110,31 +190,12 @@ class _ChatItemDetailsViewState extends State<ChatItemDetailsView> {
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// Handle send action
|
_sendMessage();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// Container(
|
|
||||||
// child: Row(children: [
|
|
||||||
// Padding(
|
|
||||||
// padding: const EdgeInsets.only(bottom: 40, left: 10, right: 10),
|
|
||||||
// child: TextField(
|
|
||||||
// decoration: InputDecoration(
|
|
||||||
// // border: OutlineInputBorder(),
|
|
||||||
// hintText: 'Enter your message',
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// IconButton(
|
|
||||||
// icon: FaIcon(FontAwesomeIcons.solidPaperPlane),
|
|
||||||
// onPressed: () {
|
|
||||||
// // Handle send action
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ]),
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,11 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
Map<int, DbMessage> lastMessages =
|
Map<int, DbMessage> lastMessages =
|
||||||
context.watch<MessagesChangeProvider>().lastMessage;
|
context.watch<MessagesChangeProvider>().lastMessage;
|
||||||
|
|
||||||
List<Contact> allUsers = context.read<ContactChangeProvider>().allContacts;
|
List<Contact> allUsers = context
|
||||||
|
.read<ContactChangeProvider>()
|
||||||
|
.allContacts
|
||||||
|
.where((c) => c.accepted)
|
||||||
|
.toList();
|
||||||
|
|
||||||
List<Contact> activeUsers = allUsers
|
List<Contact> activeUsers = allUsers
|
||||||
.where((x) => lastMessages.containsKey(x.userId.toInt()))
|
.where((x) => lastMessages.containsKey(x.userId.toInt()))
|
||||||
|
|
@ -80,11 +84,11 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(10),
|
||||||
child: OutlinedButton.icon(
|
child: OutlinedButton.icon(
|
||||||
icon: Icon((allUsers.isEmpty)
|
icon: Icon((activeUsers.isEmpty)
|
||||||
? Icons.person_add
|
? Icons.person_add
|
||||||
: Icons.camera_alt),
|
: Icons.camera_alt),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
(allUsers.isEmpty)
|
(activeUsers.isEmpty)
|
||||||
? Navigator.push(
|
? Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
|
@ -93,7 +97,7 @@ class _ChatListViewState extends State<ChatListView> {
|
||||||
)
|
)
|
||||||
: globalUpdateOfHomeViewPageIndex(1);
|
: globalUpdateOfHomeViewPageIndex(1);
|
||||||
},
|
},
|
||||||
label: Text((allUsers.isEmpty)
|
label: Text((activeUsers.isEmpty)
|
||||||
? AppLocalizations.of(context)!
|
? AppLocalizations.of(context)!
|
||||||
.chatListViewSearchUserNameBtn
|
.chatListViewSearchUserNameBtn
|
||||||
: AppLocalizations.of(context)!
|
: AppLocalizations.of(context)!
|
||||||
|
|
@ -140,30 +144,11 @@ class _UserListItem extends State<UserListItem> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
MessageSendState state;
|
|
||||||
int lastMessageInSeconds = DateTime.now()
|
int lastMessageInSeconds = DateTime.now()
|
||||||
.difference(widget.lastMessage.sendOrReceivedAt)
|
.difference(widget.lastMessage.sendOrReceivedAt)
|
||||||
.inSeconds;
|
.inSeconds;
|
||||||
|
|
||||||
if (!widget.lastMessage.messageAcknowledgeByServer) {
|
MessageSendState state = widget.lastMessage.getSendState();
|
||||||
state = MessageSendState.sending;
|
|
||||||
} else {
|
|
||||||
if (widget.lastMessage.messageOtherId == null) {
|
|
||||||
// message send
|
|
||||||
if (widget.lastMessage.messageOpenedAt == null) {
|
|
||||||
state = MessageSendState.send;
|
|
||||||
} else {
|
|
||||||
state = MessageSendState.sendOpened;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// message received
|
|
||||||
if (widget.lastMessage.messageOpenedAt == null) {
|
|
||||||
state = MessageSendState.received;
|
|
||||||
} else {
|
|
||||||
state = MessageSendState.receivedOpened;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return UserContextMenu(
|
return UserContextMenu(
|
||||||
user: widget.user,
|
user: widget.user,
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
|
|
||||||
Future _initAsync() async {
|
Future _initAsync() async {
|
||||||
List<int> token = widget.message.messageContent!.downloadToken!;
|
List<int> token = widget.message.messageContent!.downloadToken!;
|
||||||
|
|
||||||
_imageByte = await getDownloadedMedia(token, widget.message.messageId);
|
_imageByte = await getDownloadedMedia(token, widget.message.messageId);
|
||||||
print(_imageByte);
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +51,7 @@ class _MediaViewerViewState extends State<MediaViewerView> {
|
||||||
_imageByte!,
|
_imageByte!,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
)
|
)
|
||||||
: CircularProgressIndicator()),
|
: Container()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_imageByte != null
|
_imageByte != null
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,9 @@ class _SearchUsernameView extends State<SearchUsernameView> {
|
||||||
},
|
},
|
||||||
controller: searchUserName,
|
controller: searchUserName,
|
||||||
decoration: getInputDecoration(
|
decoration: getInputDecoration(
|
||||||
AppLocalizations.of(context)!.searchUsernameInput))),
|
AppLocalizations.of(context)!.searchUsernameInput),
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
OutlinedButton.icon(
|
OutlinedButton.icon(
|
||||||
icon: Icon(Icons.qr_code),
|
icon: Icon(Icons.qr_code),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue