xmpp: RECEIVE MESSAGES!
This commit is contained in:
parent
f22d042255
commit
1acc2630b4
@ -3,7 +3,7 @@ import "package:moxxyv2/isar.g.dart";
|
|||||||
|
|
||||||
@Collection()
|
@Collection()
|
||||||
@Name("Conversation")
|
@Name("Conversation")
|
||||||
class Conversation {
|
class DBConversation {
|
||||||
int? id;
|
int? id;
|
||||||
|
|
||||||
@Index(caseSensitive: false)
|
@Index(caseSensitive: false)
|
||||||
|
17
lib/db/message.dart
Normal file
17
lib/db/message.dart
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import "package:isar/isar.dart";
|
||||||
|
import "package:moxxyv2/isar.g.dart";
|
||||||
|
|
||||||
|
@Collection()
|
||||||
|
@Name("Message")
|
||||||
|
class DBMessage {
|
||||||
|
int? id;
|
||||||
|
|
||||||
|
@Index(caseSensitive: false)
|
||||||
|
late String from;
|
||||||
|
|
||||||
|
late int timestamp;
|
||||||
|
|
||||||
|
late String body;
|
||||||
|
|
||||||
|
late bool sent;
|
||||||
|
}
|
@ -21,10 +21,18 @@ String padInt(int i) {
|
|||||||
* returned true.
|
* returned true.
|
||||||
*/
|
*/
|
||||||
bool listContains<T>(List<T> list, bool Function(T element) test) {
|
bool listContains<T>(List<T> list, bool Function(T element) test) {
|
||||||
|
return firstWhereOrNull<T>(list, test) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A wrapper around List<T>.firstWhere that does not throw but instead just
|
||||||
|
* return null if test never returned true
|
||||||
|
*/
|
||||||
|
T? firstWhereOrNull<T>(List<T> list, bool Function(T element) test) {
|
||||||
try {
|
try {
|
||||||
return list.firstWhere(test) != null;
|
return list.firstWhere(test);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,15 +3,17 @@ class Message {
|
|||||||
final int timestamp; // NOTE: Milliseconds since Epoch
|
final int timestamp; // NOTE: Milliseconds since Epoch
|
||||||
final String from;
|
final String from;
|
||||||
final bool sent;
|
final bool sent;
|
||||||
|
final int id; // Database ID
|
||||||
|
|
||||||
const Message({ required this.from, required this.body, required this.timestamp, required this.sent });
|
const Message({ required this.from, required this.body, required this.timestamp, required this.sent, required this.id });
|
||||||
|
|
||||||
Message copyWith({ String? from, String? body, int? timestamp }) {
|
Message copyWith({ String? from, String? body, int? timestamp }) {
|
||||||
return Message(
|
return Message(
|
||||||
from: from ?? this.from,
|
from: from ?? this.from,
|
||||||
body: body ?? this.body,
|
body: body ?? this.body,
|
||||||
timestamp: timestamp ?? this.timestamp,
|
timestamp: timestamp ?? this.timestamp,
|
||||||
sent: this.sent
|
sent: this.sent,
|
||||||
|
id: this.id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import "package:moxxyv2/models/message.dart";
|
||||||
|
import "package:moxxyv2/xmpp/jid.dart";
|
||||||
|
|
||||||
class SetShowSendButtonAction {
|
class SetShowSendButtonAction {
|
||||||
final bool show;
|
final bool show;
|
||||||
|
|
||||||
@ -20,6 +23,21 @@ class SendMessageAction {
|
|||||||
SendMessageAction({ required this.from, required this.body, required this.timestamp, required this.jid, required this.cid });
|
SendMessageAction({ required this.from, required this.body, required this.timestamp, required this.jid, required this.cid });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ReceiveMessageAction {
|
||||||
|
final String body;
|
||||||
|
final int timestamp;
|
||||||
|
final FullJID from;
|
||||||
|
final String jid;
|
||||||
|
|
||||||
|
ReceiveMessageAction({ required this.from, required this.body, required this.timestamp, required this.jid });
|
||||||
|
}
|
||||||
|
|
||||||
|
class AddMessageAction {
|
||||||
|
final Message message;
|
||||||
|
|
||||||
|
AddMessageAction({ required this.message });
|
||||||
|
}
|
||||||
|
|
||||||
class CloseConversationAction {
|
class CloseConversationAction {
|
||||||
final String jid;
|
final String jid;
|
||||||
final int id;
|
final int id;
|
||||||
|
@ -5,24 +5,14 @@ import "package:moxxyv2/redux/conversation/state.dart";
|
|||||||
import "package:moxxyv2/redux/conversation/actions.dart";
|
import "package:moxxyv2/redux/conversation/actions.dart";
|
||||||
|
|
||||||
HashMap<String, List<Message>> messageReducer(HashMap<String, List<Message>> state, dynamic action) {
|
HashMap<String, List<Message>> messageReducer(HashMap<String, List<Message>> state, dynamic action) {
|
||||||
if (action is SendMessageAction) {
|
if (action is AddMessageAction) {
|
||||||
HashMap<String, List<Message>> map = HashMap<String, List<Message>>()..addAll(state);
|
if (!state.containsKey(action.message.from)) {
|
||||||
|
state[action.message.from] = List.from([ action.message ]);
|
||||||
Message msg = Message(
|
} else {
|
||||||
from: action.from,
|
state[action.message.from] = state[action.message.from]!..add(action.message);
|
||||||
body: action.body,
|
|
||||||
timestamp: action.timestamp,
|
|
||||||
sent: true
|
|
||||||
);
|
|
||||||
|
|
||||||
String jid = action.jid;
|
|
||||||
if (!map.containsKey(jid)) {
|
|
||||||
map[jid] = [ msg ];
|
|
||||||
return map;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
map[jid]!.add(msg);
|
return state;
|
||||||
return map;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import "dart:collection";
|
import "package:moxxyv2/models/conversation.dart";
|
||||||
|
|
||||||
class AddConversationAction {
|
class AddConversationAction {
|
||||||
final String title;
|
Conversation conversation;
|
||||||
final String lastMessageBody;
|
|
||||||
final String avatarUrl;
|
|
||||||
final String jid;
|
|
||||||
final int id;
|
|
||||||
final int unreadCounter;
|
|
||||||
final List<String> sharedMediaPaths;
|
|
||||||
final int lastChangeTimestamp;
|
|
||||||
final bool triggeredByDatabase;
|
|
||||||
final bool open;
|
|
||||||
|
|
||||||
AddConversationAction({ required this.title, required this.lastMessageBody, required this.avatarUrl, required this.jid, required this.sharedMediaPaths, required this.lastChangeTimestamp, required this.id, this.unreadCounter = 0, this.triggeredByDatabase = false, required this.open });
|
AddConversationAction({ required this.conversation });
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateConversationAction {
|
||||||
|
Conversation conversation;
|
||||||
|
|
||||||
|
UpdateConversationAction({ required this.conversation });
|
||||||
}
|
}
|
||||||
|
@ -2,29 +2,13 @@ import "package:moxxyv2/redux/state.dart";
|
|||||||
import "package:moxxyv2/redux/conversations/actions.dart";
|
import "package:moxxyv2/redux/conversations/actions.dart";
|
||||||
import "package:moxxyv2/redux/conversation/actions.dart";
|
import "package:moxxyv2/redux/conversation/actions.dart";
|
||||||
import "package:moxxyv2/repositories/conversation.dart";
|
import "package:moxxyv2/repositories/conversation.dart";
|
||||||
|
import "package:moxxyv2/models/conversation.dart";
|
||||||
|
|
||||||
import "package:redux/redux.dart";
|
import "package:redux/redux.dart";
|
||||||
import "package:flutter_redux_navigation/flutter_redux_navigation.dart";
|
import "package:flutter_redux_navigation/flutter_redux_navigation.dart";
|
||||||
import "package:get_it/get_it.dart";
|
import "package:get_it/get_it.dart";
|
||||||
|
|
||||||
void conversationsMiddleware(Store<MoxxyState> store, action, NextDispatcher next) {
|
void conversationsMiddleware(Store<MoxxyState> store, action, NextDispatcher next) async {
|
||||||
var repo = GetIt.I.get<DatabaseRepository>();
|
|
||||||
|
|
||||||
if (action is AddConversationAction && !action.triggeredByDatabase) {
|
|
||||||
if (repo.hasConversation(action.id)) {
|
|
||||||
// TODO
|
|
||||||
} else {
|
|
||||||
repo.addConversationFromAction(action);
|
|
||||||
}
|
|
||||||
} else if (action is SendMessageAction) {
|
|
||||||
if (repo.hasConversation(action.cid)) {
|
|
||||||
repo.updateConversation(id: action.cid, lastMessageBody: action.body, lastChangeTimestamp: action.timestamp);
|
|
||||||
} else {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
} else if (action is CloseConversationAction) {
|
|
||||||
store.dispatch(NavigateToAction.replace("/conversations"));
|
|
||||||
}
|
|
||||||
|
|
||||||
next(action);
|
next(action);
|
||||||
}
|
}
|
||||||
|
@ -5,29 +5,15 @@ import "package:moxxyv2/redux/conversation/actions.dart";
|
|||||||
|
|
||||||
List<Conversation> conversationReducer(List<Conversation> state, dynamic action) {
|
List<Conversation> conversationReducer(List<Conversation> state, dynamic action) {
|
||||||
if (action is AddConversationAction) {
|
if (action is AddConversationAction) {
|
||||||
state.add(Conversation(
|
return state..add(action.conversation);
|
||||||
title: action.title,
|
} else if (action is UpdateConversationAction) {
|
||||||
lastMessageBody: action.lastMessageBody,
|
return state.map((c) {
|
||||||
avatarUrl: action.avatarUrl,
|
if (c.id == action.conversation.id) {
|
||||||
jid: action.jid,
|
return action.conversation;
|
||||||
// TODO: Correct?
|
|
||||||
unreadCounter: 0,
|
|
||||||
sharedMediaPaths: action.sharedMediaPaths,
|
|
||||||
lastChangeTimestamp: action.lastChangeTimestamp,
|
|
||||||
open: action.open,
|
|
||||||
id: action.id
|
|
||||||
));
|
|
||||||
} else if (action is SendMessageAction) {
|
|
||||||
return state.map((element) {
|
|
||||||
if (element.jid == action.jid) {
|
|
||||||
return element.copyWith(lastMessageBody: action.body, lastChangeTimestamp: action.timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return element;
|
return c;
|
||||||
}).toList();
|
}).toList();
|
||||||
} else if (action is CloseConversationAction) {
|
|
||||||
// TODO: Yikes.
|
|
||||||
return state.map((element) => element.jid == action.jid ? element.copyWith(open: false) : element).toList().where((element) => element.open).toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
|
5
lib/redux/messages/actions.dart
Normal file
5
lib/redux/messages/actions.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class LoadMessagesAction {
|
||||||
|
final String jid;
|
||||||
|
|
||||||
|
LoadMessagesAction({ required this.jid });
|
||||||
|
}
|
65
lib/redux/messages/middleware.dart
Normal file
65
lib/redux/messages/middleware.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import "package:moxxyv2/redux/state.dart";
|
||||||
|
import "package:moxxyv2/redux/conversations/actions.dart";
|
||||||
|
import "package:moxxyv2/redux/conversation/actions.dart";
|
||||||
|
import "package:moxxyv2/repositories/conversation.dart";
|
||||||
|
import "package:moxxyv2/models/conversation.dart";
|
||||||
|
import "package:moxxyv2/models/message.dart";
|
||||||
|
import "package:moxxyv2/redux/messages/actions.dart";
|
||||||
|
import "package:moxxyv2/helpers.dart";
|
||||||
|
|
||||||
|
import "package:redux/redux.dart";
|
||||||
|
import "package:flutter_redux_navigation/flutter_redux_navigation.dart";
|
||||||
|
import "package:get_it/get_it.dart";
|
||||||
|
|
||||||
|
void messageMiddleware(Store<MoxxyState> store, action, NextDispatcher next) async {
|
||||||
|
if (action is ReceiveMessageAction) {
|
||||||
|
// TODO: Check if the conversation already exists
|
||||||
|
final repo = GetIt.I.get<DatabaseRepository>();
|
||||||
|
final now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
final bareJidString = action.from.toBare().toString();
|
||||||
|
|
||||||
|
final message = await repo.addMessageFromData(
|
||||||
|
action.body,
|
||||||
|
now,
|
||||||
|
bareJidString,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
final existantConversation = firstWhereOrNull(store.state.conversations, (Conversation c) => c.jid == bareJidString);
|
||||||
|
if (existantConversation == null) {
|
||||||
|
final conversation = await repo.addConversationFromData(
|
||||||
|
action.from.local,
|
||||||
|
action.body,
|
||||||
|
"",
|
||||||
|
bareJidString,
|
||||||
|
1,
|
||||||
|
now,
|
||||||
|
[],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
repo.loadedConversations.add(bareJidString);
|
||||||
|
store.dispatch(AddConversationAction(conversation: conversation));
|
||||||
|
} else {
|
||||||
|
await repo.updateConversation(
|
||||||
|
id: existantConversation.id,
|
||||||
|
lastMessageBody: action.body,
|
||||||
|
lastChangeTimestamp: now,
|
||||||
|
unreadCounter: existantConversation.unreadCounter + 1
|
||||||
|
);
|
||||||
|
store.dispatch(UpdateConversationAction(
|
||||||
|
conversation: existantConversation.copyWith(
|
||||||
|
lastMessageBody: action.body,
|
||||||
|
lastChangeTimestamp: now,
|
||||||
|
unreadCounter: existantConversation.unreadCounter + 1
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
store.dispatch(AddMessageAction(message: message));
|
||||||
|
} else if (action is LoadMessagesAction) {
|
||||||
|
GetIt.I.get<DatabaseRepository>().loadMessagesForJid(action.jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
next(action);
|
||||||
|
}
|
@ -1,9 +1,12 @@
|
|||||||
import "dart:collection";
|
import "dart:collection";
|
||||||
|
|
||||||
import "package:moxxyv2/db/conversation.dart" as db;
|
import "package:moxxyv2/db/conversation.dart";
|
||||||
import "package:moxxyv2/models/conversation.dart" as model;
|
import "package:moxxyv2/db/message.dart";
|
||||||
|
import "package:moxxyv2/models/conversation.dart";
|
||||||
|
import "package:moxxyv2/models/message.dart";
|
||||||
import "package:moxxyv2/redux/state.dart";
|
import "package:moxxyv2/redux/state.dart";
|
||||||
import "package:moxxyv2/redux/conversations/actions.dart";
|
import "package:moxxyv2/redux/conversations/actions.dart";
|
||||||
|
import "package:moxxyv2/redux/conversation/actions.dart";
|
||||||
|
|
||||||
import "package:isar/isar.dart";
|
import "package:isar/isar.dart";
|
||||||
import "package:redux/redux.dart";
|
import "package:redux/redux.dart";
|
||||||
@ -16,16 +19,18 @@ class DatabaseRepository {
|
|||||||
final Isar isar;
|
final Isar isar;
|
||||||
final Store<MoxxyState> store;
|
final Store<MoxxyState> store;
|
||||||
|
|
||||||
final HashMap<int, db.Conversation> _cache = HashMap();
|
final HashMap<int, DBConversation> _cache = HashMap();
|
||||||
|
final List<String> loadedConversations = List.empty(growable: true);
|
||||||
|
|
||||||
DatabaseRepository({ required this.isar, required this.store });
|
DatabaseRepository({ required this.isar, required this.store });
|
||||||
|
|
||||||
Future<void> loadConversations() async {
|
Future<void> loadConversations() async {
|
||||||
var conversations = await this.isar.conversations.where().findAll();
|
var conversations = await this.isar.dBConversations.where().findAll();
|
||||||
|
|
||||||
conversations.forEach((c) {
|
conversations.forEach((c) {
|
||||||
this._cache[c.id!] = c;
|
this._cache[c.id!] = c;
|
||||||
this.store.dispatch(AddConversationAction(
|
this.store.dispatch(AddConversationAction(
|
||||||
|
conversation: Conversation(
|
||||||
id: c.id!,
|
id: c.id!,
|
||||||
title: c.title,
|
title: c.title,
|
||||||
jid: c.jid,
|
jid: c.jid,
|
||||||
@ -34,19 +39,32 @@ class DatabaseRepository {
|
|||||||
unreadCounter: c.unreadCounter,
|
unreadCounter: c.unreadCounter,
|
||||||
lastChangeTimestamp: c.lastChangeTimestamp,
|
lastChangeTimestamp: c.lastChangeTimestamp,
|
||||||
sharedMediaPaths: [],
|
sharedMediaPaths: [],
|
||||||
open: c.open,
|
open: c.open
|
||||||
triggeredByDatabase: true
|
)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> loadMessagesForJid(String jid) async {
|
||||||
|
final messages = await this.isar.dBMessages.where().fromEqualTo(jid).findAll();
|
||||||
|
this.loadedConversations.add(jid);
|
||||||
|
|
||||||
|
messages.forEach((m) => this.store.dispatch(AddMessageAction(message: Message(
|
||||||
|
from: m.from,
|
||||||
|
body: m.body,
|
||||||
|
timestamp: m.timestamp,
|
||||||
|
sent: m.sent,
|
||||||
|
id: m.id!
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
bool hasConversation(int id) {
|
bool hasConversation(int id) {
|
||||||
return this._cache.containsKey(id);
|
return this._cache.containsKey(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateConversation({ required int id, String? lastMessageBody, int? lastChangeTimestamp, bool? open }) async {
|
Future<void> updateConversation({ required int id, String? lastMessageBody, int? lastChangeTimestamp, bool? open, int? unreadCounter }) async {
|
||||||
print("updateConversation");
|
print("updateConversation");
|
||||||
|
|
||||||
final c = this._cache[id]!;
|
final c = this._cache[id]!;
|
||||||
@ -59,40 +77,64 @@ class DatabaseRepository {
|
|||||||
if (open != null) {
|
if (open != null) {
|
||||||
c.open = open;
|
c.open = open;
|
||||||
}
|
}
|
||||||
|
if (unreadCounter != null) {
|
||||||
|
c.unreadCounter = unreadCounter;
|
||||||
|
}
|
||||||
|
|
||||||
await this.isar.writeTxn((isar) async {
|
await this.isar.writeTxn((isar) async {
|
||||||
await isar.conversations.put(c);
|
await isar.dBConversations.put(c);
|
||||||
print("DONE");
|
print("DONE");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addConversationFromAction(AddConversationAction action) async {
|
Future<Conversation> addConversationFromData(String title, String lastMessageBody, String avatarUrl, String jid, int unreadCounter, int lastChangeTimestamp, List<String> sharedMediaPaths, bool open) async {
|
||||||
print("addConversationFromACtion");
|
print("addConversationFromAction");
|
||||||
final c = db.Conversation()
|
final c = DBConversation()
|
||||||
..jid = action.jid
|
..jid = jid
|
||||||
..title = action.title
|
..title = title
|
||||||
..avatarUrl = action.avatarUrl
|
..avatarUrl = avatarUrl
|
||||||
..lastChangeTimestamp = action.lastChangeTimestamp
|
..lastChangeTimestamp = lastChangeTimestamp
|
||||||
..unreadCounter = action.unreadCounter
|
..unreadCounter = unreadCounter
|
||||||
..lastMessageBody = action.lastMessageBody
|
..lastMessageBody = lastMessageBody
|
||||||
..open = action.open;
|
..open = open;
|
||||||
await this.isar.writeTxn((isar) async {
|
await this.isar.writeTxn((isar) async {
|
||||||
await isar.conversations.put(c);
|
await isar.dBConversations.put(c);
|
||||||
print("DONE");
|
print("DONE");
|
||||||
});
|
});
|
||||||
|
this._cache[c.id!] = c;
|
||||||
|
|
||||||
|
return Conversation(
|
||||||
|
title: title,
|
||||||
|
lastMessageBody: lastMessageBody,
|
||||||
|
avatarUrl: avatarUrl,
|
||||||
|
jid: jid,
|
||||||
|
id: c.id!,
|
||||||
|
unreadCounter: unreadCounter,
|
||||||
|
lastChangeTimestamp: lastChangeTimestamp,
|
||||||
|
sharedMediaPaths: sharedMediaPaths,
|
||||||
|
open: open
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addConversation(model.Conversation conversation) async {
|
Future<Message> addMessageFromData(String body, int timestamp, String from, bool sent) async {
|
||||||
final c = db.Conversation()
|
print("addMessageFromData");
|
||||||
..jid = conversation.jid
|
final m = DBMessage()
|
||||||
..title = conversation.title
|
..from = from
|
||||||
..avatarUrl = conversation.avatarUrl
|
..timestamp = timestamp
|
||||||
..lastChangeTimestamp = conversation.lastChangeTimestamp
|
..body = body
|
||||||
..unreadCounter = conversation.unreadCounter
|
..sent = sent;
|
||||||
..lastMessageBody = conversation.lastMessageBody
|
|
||||||
..open = conversation.open;
|
|
||||||
await this.isar.writeTxn((isar) async {
|
await this.isar.writeTxn((isar) async {
|
||||||
await isar.conversations.put(c);
|
await isar.dBMessages.put(m);
|
||||||
|
print("DONE");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Message(
|
||||||
|
body: body,
|
||||||
|
from: from,
|
||||||
|
timestamp: timestamp,
|
||||||
|
sent: sent,
|
||||||
|
id: m.id!
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,13 @@ import "package:moxxyv2/ui/pages/profile/profile.dart";
|
|||||||
import "package:moxxyv2/ui/pages/conversation/arguments.dart";
|
import "package:moxxyv2/ui/pages/conversation/arguments.dart";
|
||||||
import "package:moxxyv2/ui/constants.dart";
|
import "package:moxxyv2/ui/constants.dart";
|
||||||
import "package:moxxyv2/ui/helpers.dart";
|
import "package:moxxyv2/ui/helpers.dart";
|
||||||
|
import "package:moxxyv2/repositories/conversation.dart";
|
||||||
|
import "package:moxxyv2/redux/messages/actions.dart";
|
||||||
|
|
||||||
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
import "package:flutter_redux/flutter_redux.dart";
|
import "package:flutter_redux/flutter_redux.dart";
|
||||||
import "package:redux/redux.dart";
|
import "package:redux/redux.dart";
|
||||||
|
import "package:get_it/get_it.dart";
|
||||||
|
|
||||||
typedef SendMessageFunction = void Function(String body);
|
typedef SendMessageFunction = void Function(String body);
|
||||||
|
|
||||||
@ -50,8 +53,9 @@ class _MessageListViewModel {
|
|||||||
final void Function(bool scrollToEndButton) setShowScrollToEndButton;
|
final void Function(bool scrollToEndButton) setShowScrollToEndButton;
|
||||||
final bool showScrollToEndButton;
|
final bool showScrollToEndButton;
|
||||||
final void Function() closeChat;
|
final void Function() closeChat;
|
||||||
|
final void Function(String) loadMessages;
|
||||||
|
|
||||||
_MessageListViewModel({ required this.conversation, required this.showSendButton, required this.sendMessage, required this.setShowSendButton, required this.showScrollToEndButton, required this.setShowScrollToEndButton, required this.closeChat });
|
_MessageListViewModel({ required this.conversation, required this.showSendButton, required this.sendMessage, required this.setShowSendButton, required this.showScrollToEndButton, required this.setShowScrollToEndButton, required this.closeChat, required this.loadMessages });
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ListViewWrapperViewModel {
|
class _ListViewWrapperViewModel {
|
||||||
@ -182,6 +186,7 @@ class ConversationPage extends StatelessWidget {
|
|||||||
jid: jid,
|
jid: jid,
|
||||||
id: conversation.id
|
id: conversation.id
|
||||||
)),
|
)),
|
||||||
|
loadMessages: (jid) => store.dispatch(LoadMessagesAction(jid: jid)),
|
||||||
sendMessage: (body) => store.dispatch(
|
sendMessage: (body) => store.dispatch(
|
||||||
// TODO
|
// TODO
|
||||||
SendMessageAction(
|
SendMessageAction(
|
||||||
@ -195,6 +200,11 @@ class ConversationPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
builder: (context, viewModel) {
|
builder: (context, viewModel) {
|
||||||
|
// TODO: Handle this in a middleware
|
||||||
|
if (GetIt.I.get<DatabaseRepository>().loadedConversations.indexOf(jid) == -1) {
|
||||||
|
viewModel.loadMessages(jid);
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: BorderlessTopbar.avatarAndName(
|
appBar: BorderlessTopbar.avatarAndName(
|
||||||
avatar: AvatarWrapper(
|
avatar: AvatarWrapper(
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
import "package:moxxyv2/xmpp/jid.dart";
|
||||||
|
|
||||||
abstract class XmppEvent {}
|
abstract class XmppEvent {}
|
||||||
|
|
||||||
class MessageEvent extends XmppEvent {
|
class MessageEvent extends XmppEvent {
|
||||||
final String body;
|
final String body;
|
||||||
final String fromJid;
|
final FullJID fromJid;
|
||||||
final String sid;
|
final String sid;
|
||||||
|
|
||||||
MessageEvent({ required this.body, required this.fromJid, required this.sid });
|
MessageEvent({ required this.body, required this.fromJid, required this.sid });
|
||||||
|
@ -31,6 +31,16 @@ class BareJID extends JID {
|
|||||||
class FullJID extends JID {
|
class FullJID extends JID {
|
||||||
FullJID({ required String local, required String domain, required String resource }) : super(local: local, domain: domain, resource: resource);
|
FullJID({ required String local, required String domain, required String resource }) : super(local: local, domain: domain, resource: resource);
|
||||||
|
|
||||||
|
BareJID toBare() {
|
||||||
|
return BareJID(local: this.local, domain: this.domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FullJID fromString(String fullJid) {
|
||||||
|
final jidParts = fullJid.split("@");
|
||||||
|
final other = jidParts[1].split("/");
|
||||||
|
return FullJID(local: jidParts[0], domain: other[0], resource: other[1]);
|
||||||
|
}
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
return "${this.local}@${this.domain}/${this.resource}";
|
return "${this.local}@${this.domain}/${this.resource}";
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import "package:moxxyv2/xmpp/stanzas/stanza.dart";
|
import "package:moxxyv2/xmpp/stanzas/stanza.dart";
|
||||||
import "package:moxxyv2/xmpp/connection.dart";
|
import "package:moxxyv2/xmpp/connection.dart";
|
||||||
import "package:moxxyv2/xmpp/events.dart";
|
import "package:moxxyv2/xmpp/events.dart";
|
||||||
|
import "package:moxxyv2/xmpp/jid.dart";
|
||||||
|
|
||||||
bool handleMessageStanza(XmppConnection conn, Stanza stanza) {
|
bool handleMessageStanza(XmppConnection conn, Stanza stanza) {
|
||||||
final body = stanza.firstTag("body");
|
final body = stanza.firstTag("body");
|
||||||
@ -8,7 +9,7 @@ bool handleMessageStanza(XmppConnection conn, Stanza stanza) {
|
|||||||
|
|
||||||
conn.sendEvent(MessageEvent(
|
conn.sendEvent(MessageEvent(
|
||||||
body: body.innerText(),
|
body: body.innerText(),
|
||||||
fromJid: stanza.attributes["from"]!,
|
fromJid: FullJID.fromString(stanza.attributes["from"]!),
|
||||||
sid: stanza.attributes["id"]!
|
sid: stanza.attributes["id"]!
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -18,12 +18,12 @@ void main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group("listContains", () {
|
group("firstWhereOrNull", () {
|
||||||
test("[] should not contain 1", () {
|
test("[] should not contain 1", () {
|
||||||
expect(listContains<int>([], (int element) => element == 1), false);
|
expect(firstWhereOrNull<int>([], (int element) => element == 1), null);
|
||||||
});
|
});
|
||||||
test("[1, 2, 3] should contain 2", () {
|
test("[1, 2, 3] should contain 2", () {
|
||||||
expect(listContains([ 1, 2, 3 ], (int element) => element == 2), true);
|
expect(firstWhereOrNull([ 1, 2, 3 ], (int element) => element == 2), 2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user