2021-01-21 20:37:35 +00:00
|
|
|
import 'dart:convert';
|
2021-02-10 01:36:12 +00:00
|
|
|
|
2021-02-03 11:22:25 +00:00
|
|
|
import 'package:flutter/cupertino.dart';
|
2021-04-20 23:54:47 +00:00
|
|
|
import 'package:flutter_app/models/servers.dart';
|
2021-04-10 02:31:27 +00:00
|
|
|
import 'package:provider/provider.dart';
|
2021-01-06 23:09:58 +00:00
|
|
|
import 'dart:async';
|
2021-01-07 19:38:06 +00:00
|
|
|
import 'dart:collection';
|
2021-01-06 23:09:58 +00:00
|
|
|
|
2021-01-12 06:21:18 +00:00
|
|
|
import 'cwtch/cwtch.dart';
|
2021-04-10 02:31:27 +00:00
|
|
|
import 'main.dart';
|
2021-01-12 06:21:18 +00:00
|
|
|
|
2021-01-07 19:38:06 +00:00
|
|
|
////////////////////
|
|
|
|
/// UI State ///
|
|
|
|
////////////////////
|
2021-01-06 23:09:58 +00:00
|
|
|
|
2021-02-10 01:36:12 +00:00
|
|
|
//todo: delete
|
2021-01-07 19:38:06 +00:00
|
|
|
class ProfileModel {
|
|
|
|
String onion;
|
|
|
|
String nickname;
|
|
|
|
String creationDate;
|
2021-01-21 20:37:35 +00:00
|
|
|
String imagePath;
|
2021-01-07 19:38:06 +00:00
|
|
|
HashMap<String, ContactModel> contacts;
|
|
|
|
}
|
2021-01-06 23:09:58 +00:00
|
|
|
|
2021-02-10 01:36:12 +00:00
|
|
|
//todo: delete
|
2021-01-07 19:38:06 +00:00
|
|
|
class ContactModel {
|
|
|
|
String onion;
|
|
|
|
String nickname;
|
|
|
|
bool isGroup;
|
2021-01-21 20:37:35 +00:00
|
|
|
bool isInvitation;
|
2021-01-07 19:38:06 +00:00
|
|
|
bool isBlocked;
|
|
|
|
String status;
|
2021-01-21 20:37:35 +00:00
|
|
|
String imagePath;
|
2021-01-06 23:09:58 +00:00
|
|
|
|
2021-03-16 23:33:03 +00:00
|
|
|
ContactModel({this.onion, this.nickname, this.status, this.isInvitation, this.isBlocked, this.imagePath});
|
2021-01-07 19:38:06 +00:00
|
|
|
}
|
|
|
|
|
2021-01-21 20:37:35 +00:00
|
|
|
class ChatMessage {
|
|
|
|
final int o;
|
|
|
|
final String d;
|
|
|
|
|
|
|
|
ChatMessage({this.o, this.d});
|
|
|
|
|
|
|
|
ChatMessage.fromJson(Map<String, dynamic> json)
|
|
|
|
: o = json['o'],
|
|
|
|
d = json['d'];
|
|
|
|
|
2021-03-10 17:40:14 +00:00
|
|
|
Map<String, dynamic> toJson() => {
|
2021-01-21 20:37:35 +00:00
|
|
|
'o': o,
|
|
|
|
'd': d,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-02-03 11:22:25 +00:00
|
|
|
///////////////////
|
|
|
|
/// Providers ///
|
|
|
|
///////////////////
|
|
|
|
|
|
|
|
class ProfileListState extends ChangeNotifier {
|
2021-03-12 12:31:21 +00:00
|
|
|
List<ProfileInfoState> _profiles = [];
|
|
|
|
int get num => _profiles.length;
|
2021-02-03 11:22:25 +00:00
|
|
|
|
2021-03-12 12:31:21 +00:00
|
|
|
void addAll(Iterable<ProfileInfoState> newProfiles) {
|
|
|
|
_profiles.addAll(newProfiles);
|
2021-02-03 11:22:25 +00:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-03-12 12:31:21 +00:00
|
|
|
void add(ProfileInfoState newProfile) {
|
|
|
|
_profiles.add(newProfile);
|
2021-02-03 11:22:25 +00:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-03-16 23:33:03 +00:00
|
|
|
List<ProfileInfoState> get profiles => _profiles.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
2021-03-12 12:31:21 +00:00
|
|
|
|
|
|
|
ProfileInfoState getProfile(String onion) {
|
|
|
|
int idx = _profiles.indexWhere((element) => element.onion == onion);
|
|
|
|
return idx >= 0 ? _profiles[idx] : null;
|
|
|
|
}
|
2021-02-03 11:22:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ContactListState extends ChangeNotifier {
|
2021-03-12 12:31:21 +00:00
|
|
|
List<ContactInfoState> _contacts = [];
|
|
|
|
int get num => _contacts.length;
|
|
|
|
|
|
|
|
void addAll(Iterable<ContactInfoState> newContacts) {
|
|
|
|
_contacts.addAll(newContacts);
|
2021-02-03 11:22:25 +00:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-03-12 12:31:21 +00:00
|
|
|
void add(ContactInfoState newContact) {
|
|
|
|
_contacts.add(newContact);
|
2021-02-03 11:22:25 +00:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-04-13 00:04:51 +00:00
|
|
|
void updateLastMessageTime(String forOnion, DateTime newVal) {
|
|
|
|
getContact(forOnion).lastMessageTime = newVal;
|
2021-03-16 23:33:03 +00:00
|
|
|
_contacts.sort((ContactInfoState a, ContactInfoState b) {
|
2021-04-13 00:04:51 +00:00
|
|
|
if (a.lastMessageTime == null && b.lastMessageTime == null) return 0;
|
|
|
|
if (a.lastMessageTime == null) return 1;
|
|
|
|
if (b.lastMessageTime == null) return -1;
|
2021-04-13 20:59:25 +00:00
|
|
|
return b.lastMessageTime.compareTo(a.lastMessageTime);
|
2021-03-16 23:33:03 +00:00
|
|
|
});
|
2021-02-03 11:22:25 +00:00
|
|
|
//<todo> if(changed) {
|
|
|
|
notifyListeners();
|
|
|
|
//} </todo>
|
|
|
|
}
|
|
|
|
|
2021-03-16 23:33:03 +00:00
|
|
|
List<ContactInfoState> get contacts => _contacts.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
2021-03-12 12:31:21 +00:00
|
|
|
|
|
|
|
ContactInfoState getContact(String onion) {
|
|
|
|
int idx = _contacts.indexWhere((element) => element.onion == onion);
|
|
|
|
return idx >= 0 ? _contacts[idx] : null;
|
|
|
|
}
|
2021-02-03 11:22:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ProfileInfoState extends ChangeNotifier {
|
2021-03-12 12:31:21 +00:00
|
|
|
ContactListState _contacts = ContactListState();
|
2021-04-20 23:54:47 +00:00
|
|
|
ServerListState _servers = ServerListState();
|
2021-02-03 11:22:25 +00:00
|
|
|
final String onion;
|
|
|
|
String _nickname = "";
|
|
|
|
String _imagePath = "";
|
|
|
|
int _unreadMessages = 0;
|
2021-04-10 04:01:12 +00:00
|
|
|
bool _online = false;
|
2021-02-03 11:22:25 +00:00
|
|
|
|
2021-03-16 23:33:03 +00:00
|
|
|
ProfileInfoState({
|
|
|
|
this.onion,
|
|
|
|
nickname = "",
|
|
|
|
imagePath = "",
|
|
|
|
unreadMessages = 0,
|
|
|
|
contactsJson = "",
|
2021-04-20 23:54:47 +00:00
|
|
|
serversJson = "",
|
2021-04-10 04:01:12 +00:00
|
|
|
online = false,
|
2021-03-16 23:33:03 +00:00
|
|
|
}) {
|
2021-02-03 11:22:25 +00:00
|
|
|
this._nickname = nickname;
|
|
|
|
this._imagePath = imagePath;
|
|
|
|
this._unreadMessages = unreadMessages;
|
2021-04-10 04:01:12 +00:00
|
|
|
this._online = online;
|
2021-03-12 12:31:21 +00:00
|
|
|
|
|
|
|
if (contactsJson != null && contactsJson != "" && contactsJson != "null") {
|
|
|
|
List<dynamic> contacts = jsonDecode(contactsJson);
|
2021-03-16 23:33:03 +00:00
|
|
|
this._contacts.addAll(contacts.map((contact) {
|
|
|
|
return ContactInfoState(
|
2021-03-17 22:40:33 +00:00
|
|
|
profileOnion: this.onion,
|
|
|
|
onion: contact["onion"],
|
|
|
|
nickname: contact["name"],
|
|
|
|
status: contact["status"],
|
|
|
|
imagePath: contact["picture"],
|
|
|
|
isBlocked: contact["authorization"] == "blocked",
|
2021-04-08 05:07:01 +00:00
|
|
|
isInvitation: contact["authorization"] == "unknown",
|
2021-03-17 22:56:59 +00:00
|
|
|
savePeerHistory: contact["saveConversationHistory"],
|
|
|
|
numMessages: contact["numMessages"],
|
2021-04-13 00:04:51 +00:00
|
|
|
numUnread: contact["numUnread"],
|
2021-04-22 21:15:27 +00:00
|
|
|
isGroup: contact["isGroup"],
|
|
|
|
server: contact["groupServer"],
|
2021-04-13 20:59:25 +00:00
|
|
|
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])));
|
2021-03-16 23:33:03 +00:00
|
|
|
}));
|
2021-04-13 00:04:51 +00:00
|
|
|
|
|
|
|
// dummy set to invoke sort-on-load
|
|
|
|
if (this._contacts.num > 0) {
|
|
|
|
this._contacts.updateLastMessageTime(this._contacts._contacts.first.onion, this._contacts._contacts.first.lastMessageTime);
|
|
|
|
}
|
2021-03-12 12:31:21 +00:00
|
|
|
}
|
2021-04-20 23:54:47 +00:00
|
|
|
|
|
|
|
this.replaceServers(serversJson);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse out the server list json into our server info state struct...
|
|
|
|
void replaceServers(String serversJson) {
|
|
|
|
if (serversJson != null && serversJson != "" && serversJson != "null") {
|
|
|
|
print("got servers $serversJson");
|
|
|
|
List<dynamic> servers = jsonDecode(serversJson);
|
|
|
|
this._servers.replace(servers.map((server) {
|
|
|
|
// TODO Keys...
|
|
|
|
return ServerInfoState(onion: server["onion"], status: server["status"]);
|
|
|
|
}));
|
|
|
|
}
|
2021-02-03 11:22:25 +00:00
|
|
|
}
|
|
|
|
|
2021-04-10 04:01:12 +00:00
|
|
|
// Getters and Setters for Online Status
|
|
|
|
bool get isOnline => this._online;
|
|
|
|
set isOnline(bool newValue) {
|
|
|
|
this._online = newValue;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-02-03 11:22:25 +00:00
|
|
|
String get nickname => this._nickname;
|
|
|
|
set nickname(String newValue) {
|
2021-02-23 02:55:10 +00:00
|
|
|
this._nickname = newValue;
|
2021-02-03 11:22:25 +00:00
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
String get imagePath => this._imagePath;
|
|
|
|
set imagePath(String newVal) {
|
|
|
|
this._imagePath = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
int get unreadMessages => this._unreadMessages;
|
|
|
|
set unreadMessages(int newVal) {
|
|
|
|
this._unreadMessages = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
2021-02-11 01:03:02 +00:00
|
|
|
|
2021-03-12 12:31:21 +00:00
|
|
|
ContactListState get contactList => this._contacts;
|
2021-04-20 23:54:47 +00:00
|
|
|
ServerListState get serverList => this._servers;
|
2021-03-12 12:31:21 +00:00
|
|
|
|
2021-02-11 01:03:02 +00:00
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
super.dispose();
|
|
|
|
print("profileinfostate.dispose()");
|
|
|
|
}
|
2021-02-03 11:22:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ContactInfoState extends ChangeNotifier {
|
|
|
|
final String profileOnion;
|
|
|
|
final String onion;
|
|
|
|
String _nickname;
|
2021-04-22 21:15:27 +00:00
|
|
|
|
2021-02-03 11:22:25 +00:00
|
|
|
bool _isInvitation;
|
|
|
|
bool _isBlocked;
|
|
|
|
String _status;
|
|
|
|
String _imagePath;
|
2021-03-17 22:40:33 +00:00
|
|
|
String _savePeerHistory;
|
2021-02-03 11:22:25 +00:00
|
|
|
int _unreadMessages = 0;
|
2021-03-17 22:56:59 +00:00
|
|
|
int _totalMessages = 0;
|
2021-04-13 00:04:51 +00:00
|
|
|
DateTime _lastMessageTime;
|
2021-02-03 11:22:25 +00:00
|
|
|
|
2021-04-22 21:15:27 +00:00
|
|
|
// todo: a nicer way to mdoel contats, groups and other "entities"
|
|
|
|
bool _isGroup;
|
|
|
|
String _server;
|
|
|
|
|
2021-03-24 23:35:24 +00:00
|
|
|
ContactInfoState({
|
|
|
|
this.profileOnion,
|
|
|
|
this.onion,
|
|
|
|
nickname = "",
|
|
|
|
isGroup = false,
|
|
|
|
isInvitation = false,
|
|
|
|
isBlocked = false,
|
|
|
|
status = "",
|
|
|
|
imagePath = "",
|
|
|
|
savePeerHistory = "DeleteHistoryConfirmed",
|
|
|
|
numMessages = 0,
|
|
|
|
numUnread = 0,
|
2021-04-13 00:04:51 +00:00
|
|
|
lastMessageTime = null,
|
2021-04-22 21:15:27 +00:00
|
|
|
server = "",
|
2021-03-24 23:35:24 +00:00
|
|
|
}) {
|
2021-02-03 11:22:25 +00:00
|
|
|
this._nickname = nickname;
|
|
|
|
this._isGroup = isGroup;
|
|
|
|
this._isInvitation = isInvitation;
|
|
|
|
this._isBlocked = isBlocked;
|
|
|
|
this._status = status;
|
|
|
|
this._imagePath = imagePath;
|
2021-03-17 22:54:41 +00:00
|
|
|
this._totalMessages = numMessages;
|
|
|
|
this._unreadMessages = numUnread;
|
2021-03-17 22:40:33 +00:00
|
|
|
this._savePeerHistory = savePeerHistory;
|
2021-04-13 00:04:51 +00:00
|
|
|
this._lastMessageTime = lastMessageTime;
|
2021-04-22 21:15:27 +00:00
|
|
|
this._server = server;
|
2021-02-03 11:22:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
get nickname => this._nickname;
|
2021-03-17 22:40:33 +00:00
|
|
|
|
|
|
|
get savePeerHistory => this._savePeerHistory;
|
|
|
|
set savePeerHistory(String newVal) {
|
|
|
|
this._savePeerHistory = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-02-03 11:22:25 +00:00
|
|
|
set nickname(String newVal) {
|
|
|
|
this._nickname = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-03-23 22:42:10 +00:00
|
|
|
get isGroup => this._isGroup;
|
|
|
|
set isGroup(bool newVal) {
|
|
|
|
this._isGroup = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-03-17 21:48:11 +00:00
|
|
|
get isBlocked => this._isBlocked;
|
|
|
|
set isBlocked(bool newVal) {
|
|
|
|
this._isBlocked = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-02-10 01:36:12 +00:00
|
|
|
get isInvitation => this._isInvitation;
|
|
|
|
set isInvitation(bool newVal) {
|
|
|
|
this._isInvitation = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
get status => this._status;
|
|
|
|
set status(String newVal) {
|
|
|
|
this._status = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-02-03 11:22:25 +00:00
|
|
|
get unreadMessages => this._unreadMessages;
|
|
|
|
set unreadMessages(int newVal) {
|
|
|
|
this._unreadMessages = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
2021-03-12 12:31:21 +00:00
|
|
|
|
2021-03-17 22:54:41 +00:00
|
|
|
get totalMessages => this._totalMessages;
|
|
|
|
set totalMessages(int newVal) {
|
|
|
|
this._totalMessages = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-03-12 12:31:21 +00:00
|
|
|
get imagePath => this._imagePath;
|
|
|
|
set imagePath(String newVal) {
|
|
|
|
this._imagePath = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
2021-04-13 00:04:51 +00:00
|
|
|
|
|
|
|
get lastMessageTime => this._lastMessageTime;
|
|
|
|
set lastMessageTime(DateTime newVal) {
|
|
|
|
this._lastMessageTime = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
2021-04-22 21:15:27 +00:00
|
|
|
|
|
|
|
// we only allow callers to fetch the server
|
|
|
|
get server => this._server;
|
|
|
|
|
|
|
|
bool isOnline() {
|
|
|
|
if (this.isGroup) {
|
|
|
|
return this.status == "Synced";
|
|
|
|
} else {
|
|
|
|
return this.status == "Authenticated";
|
|
|
|
}
|
|
|
|
}
|
2021-02-03 11:22:25 +00:00
|
|
|
}
|
|
|
|
|
2021-04-10 02:31:27 +00:00
|
|
|
class MessageState extends ChangeNotifier {
|
|
|
|
final String profileOnion;
|
|
|
|
final String contactHandle;
|
|
|
|
final int messageIndex;
|
|
|
|
String _message;
|
2021-04-14 02:33:21 +00:00
|
|
|
DateTime _timestamp;
|
|
|
|
String _senderOnion;
|
2021-04-10 02:31:27 +00:00
|
|
|
bool _ackd = false;
|
|
|
|
|
|
|
|
MessageState({
|
|
|
|
BuildContext context,
|
|
|
|
this.profileOnion,
|
|
|
|
this.contactHandle,
|
|
|
|
this.messageIndex,
|
|
|
|
}) {
|
|
|
|
Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) {
|
|
|
|
dynamic messageWrapper = jsonDecode(jsonMessage);
|
|
|
|
dynamic message = jsonDecode(messageWrapper['Message']);
|
|
|
|
this._message = message['d'];
|
2021-04-14 02:33:21 +00:00
|
|
|
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp']);
|
|
|
|
this._senderOnion = messageWrapper['PeerID'];
|
|
|
|
//update ackd last as it's changenotified
|
2021-04-10 02:31:27 +00:00
|
|
|
this._ackd = messageWrapper['Acknowledged'];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
get message => this._message;
|
|
|
|
get timestamp => this._timestamp;
|
|
|
|
get ackd => this._ackd;
|
2021-04-14 02:33:21 +00:00
|
|
|
get senderOnion => this._senderOnion;
|
2021-04-10 02:31:27 +00:00
|
|
|
|
|
|
|
set ackd(bool newVal) {
|
|
|
|
this._ackd = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-07 19:38:06 +00:00
|
|
|
/////////////
|
|
|
|
/// ACN ///
|
|
|
|
/////////////
|
2021-01-06 23:09:58 +00:00
|
|
|
|
2021-01-07 19:38:06 +00:00
|
|
|
class AppModel {
|
2021-01-12 06:21:18 +00:00
|
|
|
final Cwtch cwtch;
|
|
|
|
AppModel({this.cwtch});
|
2021-01-06 23:09:58 +00:00
|
|
|
|
|
|
|
Stream<String> contactEvents() async* {
|
|
|
|
while (true) {
|
2021-01-14 23:36:09 +00:00
|
|
|
String event = await cwtch.ContactEvents();
|
2021-01-06 23:09:58 +00:00
|
|
|
if (event != "") {
|
|
|
|
print(event);
|
|
|
|
yield event;
|
|
|
|
} else {
|
2021-02-03 11:22:25 +00:00
|
|
|
print("TEST TEST FAIL TEST FAIL 123");
|
2021-01-06 23:09:58 +00:00
|
|
|
await Future.delayed(Duration(seconds: 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Stream<String> torStatus() async* {
|
|
|
|
while (true) {
|
2021-01-14 23:36:09 +00:00
|
|
|
String event = await cwtch.ACNEvents();
|
2021-01-06 23:09:58 +00:00
|
|
|
if (event != "") {
|
|
|
|
yield event;
|
|
|
|
} else {
|
2021-02-03 11:22:25 +00:00
|
|
|
print("TOR TEST TEST FAIL TEST FAIL 123");
|
2021-01-06 23:09:58 +00:00
|
|
|
await Future.delayed(Duration(seconds: 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-07 19:38:06 +00:00
|
|
|
}
|