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