Line data Source code
1 : import 'package:cwtch/config.dart'; 2 : import 'package:flutter/widgets.dart'; 3 : 4 : import 'contact.dart'; 5 : import 'profileservers.dart'; 6 : 7 : class ContactListState extends ChangeNotifier { 8 : ProfileServerListState? servers; 9 : List<ContactInfoState> _contacts = []; 10 0 : int get num => _contacts.length; 11 : 12 0 : void connectServers(ProfileServerListState servers) { 13 0 : this.servers = servers; 14 : } 15 : 16 0 : void addAll(Iterable<ContactInfoState> newContacts) { 17 0 : _contacts.addAll(newContacts); 18 0 : servers?.clearGroups(); 19 0 : _contacts.forEach((contact) { 20 0 : if (contact.isGroup) { 21 0 : servers?.addGroup(contact); 22 : } 23 : }); 24 0 : resort(); 25 0 : notifyListeners(); 26 : } 27 : 28 0 : void add(ContactInfoState newContact) { 29 0 : _contacts.add(newContact); 30 0 : if (newContact.isGroup) { 31 : // Copy the current known antispam value for the server 32 : // to the new contact. This lets us send messages straight away without 33 : // waiting for another update (or restarting the peer)... 34 : // Note for NEW servers we expect TokenServerInfo events to arrive after adding the group 35 : // this flow is only for existing servers... 36 : // FIXME: in Cwtch 1.14 37 : // NOTE: This is a bit hacky. Ideally this information would be stored per 38 : // Server not per Group, and creating a group would also trigger sharing 39 : // this information...on the backend all the accounting is done correctly. 40 0 : var otherGroups = servers?.getServer(newContact.server ?? "")?.groups; 41 0 : if (otherGroups != null && otherGroups.isNotEmpty) { 42 0 : EnvironmentConfig.debugLog("sharing antispam tickets to new group. FIXME: in Cwtch 1.14"); 43 0 : var antispamTickets = otherGroups[0].antispamTickets; 44 0 : _contacts.last.antispamTickets = antispamTickets; 45 : } 46 0 : servers?.addGroup(newContact); 47 : } 48 0 : resort(); 49 0 : notifyListeners(); 50 : } 51 : 52 0 : void resort() { 53 0 : _contacts.sort((ContactInfoState a, ContactInfoState b) { 54 : // return -1 = a first in list 55 : // return 1 = b first in list 56 : 57 : // pinned contacts first 58 0 : if (a.pinned != true && b.pinned == true) return 1; 59 0 : if (a.pinned == true && b.pinned != true) return -1; 60 : // blocked contacts last 61 0 : if (a.isBlocked == true && b.isBlocked != true) return 1; 62 0 : if (a.isBlocked != true && b.isBlocked == true) return -1; 63 : // archive is next... 64 0 : if (!a.isArchived && b.isArchived) return -1; 65 0 : if (a.isArchived && !b.isArchived) return 1; 66 : 67 : // unapproved top 68 0 : if (a.isInvitation && !b.isInvitation) return -1; 69 0 : if (!a.isInvitation && b.isInvitation) return 1; 70 : 71 : // special sorting for contacts with no messages in either history 72 0 : if (a.lastMessageReceivedTime.millisecondsSinceEpoch == 0 && b.lastMessageReceivedTime.millisecondsSinceEpoch == 0) { 73 : // online contacts first 74 0 : if (a.isOnline() && !b.isOnline()) return -1; 75 0 : if (!a.isOnline() && b.isOnline()) return 1; 76 : // finally resort to onion 77 0 : return a.onion.toString().compareTo(b.onion.toString()); 78 : } 79 : // finally... most recent history first 80 0 : if (a.lastMessageReceivedTime.millisecondsSinceEpoch == 0) return 1; 81 0 : if (b.lastMessageReceivedTime.millisecondsSinceEpoch == 0) return -1; 82 0 : return b.lastMessageReceivedTime.compareTo(a.lastMessageReceivedTime); 83 : }); 84 : //<todo> if(changed) { 85 0 : notifyListeners(); 86 : //} </todo> 87 : } 88 : 89 0 : void updateLastMessageReceivedTime(int forIdentifier, DateTime newMessageTime) { 90 0 : var contact = getContact(forIdentifier); 91 : if (contact == null) return; 92 : 93 : // Assert that the new time is after the current last message time AND that 94 : // new message time is before the current time. 95 0 : if (newMessageTime.isAfter(contact.lastMessageReceivedTime)) { 96 0 : if (newMessageTime.isBefore(DateTime.now().toLocal())) { 97 0 : contact.lastMessageReceivedTime = newMessageTime; 98 : } else { 99 : // Otherwise set the last message time to now... 100 0 : contact.lastMessageReceivedTime = DateTime.now().toLocal(); 101 : } 102 0 : resort(); 103 : } 104 : } 105 : 106 0 : List<ContactInfoState> get contacts => _contacts; //todo: copy?? dont want caller able to bypass changenotifier 107 : 108 0 : ContactInfoState? getContact(int identifier) { 109 0 : int idx = _contacts.indexWhere((element) => element.identifier == identifier); 110 0 : return idx >= 0 ? _contacts[idx] : null; 111 : } 112 : 113 0 : void removeContact(int identifier) { 114 0 : int idx = _contacts.indexWhere((element) => element.identifier == identifier); 115 0 : if (idx >= 0) { 116 0 : _contacts.removeAt(idx); 117 0 : notifyListeners(); 118 : } 119 : } 120 : 121 0 : void removeContactByHandle(String handle) { 122 0 : int idx = _contacts.indexWhere((element) => element.onion == handle); 123 0 : _contacts.removeAt(idx); 124 0 : notifyListeners(); 125 : } 126 : 127 0 : ContactInfoState? findContact(String byHandle) { 128 0 : int idx = _contacts.indexWhere((element) => element.onion == byHandle); 129 0 : return idx >= 0 ? _contacts[idx] : null; 130 : } 131 : 132 0 : void newMessage(int identifier, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String contenthash, bool selectedConversation) { 133 0 : getContact(identifier)?.newMessage(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash, selectedConversation); 134 0 : updateLastMessageReceivedTime(identifier, DateTime.now()); 135 : } 136 : 137 0 : int cacheMemUsage() { 138 0 : return _contacts.map((e) => e.messageCache.size()).fold(0, (previousValue, element) => previousValue + element); 139 : } 140 : }