2022-01-18 21:26:52 +00:00
import ' dart:convert ' ;
2024-01-03 22:10:07 +00:00
import ' dart:math ' ;
2022-01-18 21:26:52 +00:00
2023-04-04 20:58:42 +00:00
import ' package:cwtch/config.dart ' ;
2022-01-27 01:03:48 +00:00
import ' package:cwtch/models/remoteserver.dart ' ;
2023-08-02 16:43:27 +00:00
import ' package:cwtch/models/search.dart ' ;
2022-01-18 21:26:52 +00:00
import ' package:flutter/widgets.dart ' ;
2023-09-18 14:55:07 +00:00
import ' package:provider/provider.dart ' ;
2022-06-20 17:54:06 +00:00
import ' package:scrollable_positioned_list/scrollable_positioned_list.dart ' ;
2022-01-18 21:26:52 +00:00
2023-09-18 14:55:07 +00:00
import ' ../main.dart ' ;
2023-04-04 20:58:42 +00:00
import ' ../themes/opaque.dart ' ;
import ' ../views/contactsview.dart ' ;
2022-01-18 21:26:52 +00:00
import ' contact.dart ' ;
import ' contactlist.dart ' ;
import ' filedownloadprogress.dart ' ;
import ' profileservers.dart ' ;
class ProfileInfoState extends ChangeNotifier {
ProfileServerListState _servers = ProfileServerListState ( ) ;
ContactListState _contacts = ContactListState ( ) ;
final String onion ;
String _nickname = " " ;
String _imagePath = " " ;
2022-02-07 22:26:14 +00:00
String _defaultImagePath = " " ;
2022-01-18 21:26:52 +00:00
int _unreadMessages = 0 ;
bool _online = false ;
Map < String , FileDownloadProgress > _downloads = Map < String , FileDownloadProgress > ( ) ;
2022-02-05 00:57:31 +00:00
Map < String , int > _downloadTriggers = Map < String , int > ( ) ;
2022-06-20 17:54:06 +00:00
ItemScrollController contactListScrollController = new ItemScrollController ( ) ;
2022-01-18 21:26:52 +00:00
// assume profiles are encrypted...this will be set to false
// in the constructor if the profile is encrypted with the defacto password.
bool _encrypted = true ;
2022-12-05 19:58:44 +00:00
bool _autostart = true ;
bool _enabled = false ;
2023-09-14 01:38:08 +00:00
bool _appearOffline = false ;
2023-10-02 22:24:08 +00:00
bool _appearOfflineAtStartup = false ;
2022-12-05 19:58:44 +00:00
2022-01-18 21:26:52 +00:00
ProfileInfoState ( {
required this . onion ,
nickname = " " ,
imagePath = " " ,
2022-02-07 22:26:14 +00:00
defaultImagePath = " " ,
2022-01-18 21:26:52 +00:00
unreadMessages = 0 ,
contactsJson = " " ,
serversJson = " " ,
online = false ,
2022-12-05 19:58:44 +00:00
autostart = true ,
2022-01-18 21:26:52 +00:00
encrypted = true ,
2023-09-14 01:38:08 +00:00
appearOffline = false ,
2022-02-07 22:26:14 +00:00
String ,
2022-01-18 21:26:52 +00:00
} ) {
this . _nickname = nickname ;
this . _imagePath = imagePath ;
2022-02-07 22:26:14 +00:00
this . _defaultImagePath = defaultImagePath ;
2022-01-18 21:26:52 +00:00
this . _unreadMessages = unreadMessages ;
this . _online = online ;
2022-12-05 19:58:44 +00:00
this . _enabled = _enabled ;
this . _autostart = autostart ;
if ( autostart ) {
this . _enabled = true ;
}
2023-09-14 01:38:08 +00:00
this . _appearOffline = appearOffline ;
2023-10-02 22:24:08 +00:00
this . _appearOfflineAtStartup = appearOffline ;
2022-01-18 21:26:52 +00:00
this . _encrypted = encrypted ;
_contacts . connectServers ( this . _servers ) ;
if ( contactsJson ! = null & & contactsJson ! = " " & & contactsJson ! = " null " ) {
this . replaceServers ( serversJson ) ;
List < dynamic > contacts = jsonDecode ( contactsJson ) ;
this . _contacts . addAll ( contacts . map ( ( contact ) {
2022-04-01 22:54:06 +00:00
this . _unreadMessages + = contact [ " numUnread " ] as int ;
2023-08-21 17:50:55 +00:00
2022-01-18 21:26:52 +00:00
return ContactInfoState ( this . onion , contact [ " identifier " ] , contact [ " onion " ] ,
nickname: contact [ " name " ] ,
2023-08-21 17:50:55 +00:00
localNickname: contact [ " attributes " ] ? [ " local.profile.name " ] ? ? " " , // contact may not have a local name
2022-01-18 21:26:52 +00:00
status: contact [ " status " ] ,
imagePath: contact [ " picture " ] ,
2022-02-07 22:26:14 +00:00
defaultImagePath: contact [ " isGroup " ] ? contact [ " picture " ] : contact [ " defaultPicture " ] ,
2022-01-18 21:26:52 +00:00
accepted: contact [ " accepted " ] ,
blocked: contact [ " blocked " ] ,
savePeerHistory: contact [ " saveConversationHistory " ] ,
numMessages: contact [ " numMessages " ] ,
numUnread: contact [ " numUnread " ] ,
isGroup: contact [ " isGroup " ] ,
server: contact [ " groupServer " ] ,
archived: contact [ " isArchived " ] = = true ,
2022-02-04 22:19:02 +00:00
lastMessageTime: DateTime . fromMillisecondsSinceEpoch ( 1000 * int . parse ( contact [ " lastMsgTime " ] ) ) ,
2022-07-22 16:38:51 +00:00
pinned: contact [ " attributes " ] ? [ " local.profile.pinned " ] = = " true " ,
2022-02-08 02:13:49 +00:00
notificationPolicy: contact [ " notificationPolicy " ] ? ? " ConversationNotificationPolicy.Default " ) ;
2022-01-18 21:26:52 +00:00
} ) ) ;
// dummy set to invoke sort-on-load
if ( this . _contacts . num > 0 ) {
2023-09-26 20:26:52 +00:00
this . _contacts . updateLastMessageReceivedTime ( this . _contacts . contacts . first . identifier , this . _contacts . contacts . first . lastMessageReceivedTime ) ;
2022-01-18 21:26:52 +00:00
}
}
}
2023-08-02 16:43:27 +00:00
// Code for managing the state of the profile-wide search feature...
String activeSearchID = " " ;
List < SearchResult > activeSearchResults = List . empty ( growable: true ) ;
void newSearch ( String activeSearchID ) {
this . activeSearchID = activeSearchID ;
this . activeSearchResults . clear ( ) ;
notifyListeners ( ) ;
}
void handleSearchResult ( String searchID , int conversationIdentifier , int messageIndex ) {
if ( searchID = = activeSearchID ) {
activeSearchResults . add ( SearchResult ( searchID: searchID , conversationIdentifier: conversationIdentifier , messageIndex: messageIndex ) ) ;
notifyListeners ( ) ;
}
}
2022-01-18 21:26:52 +00:00
// Parse out the server list json into our server info state struct...
void replaceServers ( String serversJson ) {
if ( serversJson ! = " " & & serversJson ! = " null " ) {
List < dynamic > servers = jsonDecode ( serversJson ) ;
this . _servers . replace ( servers . map ( ( server ) {
// TODO Keys...
2022-03-04 01:00:36 +00:00
var preSyncStartTime = DateTime . tryParse ( server [ " syncProgress " ] [ " startTime " ] ) ;
var lastMessageTime = DateTime . tryParse ( server [ " syncProgress " ] [ " lastMessageTime " ] ) ;
return RemoteServerInfoState ( server [ " onion " ] , server [ " identifier " ] , server [ " description " ] , server [ " status " ] , lastPreSyncMessageTime: preSyncStartTime , mostRecentMessageTime: lastMessageTime ) ;
2022-01-18 21:26:52 +00:00
} ) ) ;
this . _contacts . contacts . forEach ( ( contact ) {
if ( contact . isGroup ) {
_servers . addGroup ( contact ) ;
}
} ) ;
notifyListeners ( ) ;
}
}
//
void updateServerStatusCache ( String server , String status ) {
this . _servers . updateServerState ( server , status ) ;
notifyListeners ( ) ;
}
// Getters and Setters for Online Status
bool get isOnline = > this . _online ;
2022-02-08 02:13:49 +00:00
2022-01-18 21:26:52 +00:00
set isOnline ( bool newValue ) {
this . _online = newValue ;
notifyListeners ( ) ;
}
// Check encrypted status for profile info screen
bool get isEncrypted = > this . _encrypted ;
2022-12-06 20:11:55 +00:00
set isEncrypted ( bool newValue ) {
this . _encrypted = newValue ;
notifyListeners ( ) ;
}
2022-01-18 21:26:52 +00:00
String get nickname = > this . _nickname ;
2022-02-08 02:13:49 +00:00
2022-01-18 21:26:52 +00:00
set nickname ( String newValue ) {
this . _nickname = newValue ;
notifyListeners ( ) ;
}
String get imagePath = > this . _imagePath ;
2022-02-08 02:13:49 +00:00
2022-01-18 21:26:52 +00:00
set imagePath ( String newVal ) {
this . _imagePath = newVal ;
notifyListeners ( ) ;
}
2022-12-05 19:58:44 +00:00
bool get enabled = > this . _enabled ;
set enabled ( bool newVal ) {
this . _enabled = newVal ;
notifyListeners ( ) ;
}
bool get autostart = > this . _autostart ;
set autostart ( bool newVal ) {
this . _autostart = newVal ;
notifyListeners ( ) ;
}
2023-10-02 22:24:08 +00:00
bool get appearOfflineAtStartup = > this . _appearOfflineAtStartup ;
set appearOfflineAtStartup ( bool newVal ) {
this . _appearOfflineAtStartup = newVal ;
notifyListeners ( ) ;
}
2023-09-14 01:38:08 +00:00
bool get appearOffline = > this . _appearOffline ;
set appearOffline ( bool newVal ) {
this . _appearOffline = newVal ;
notifyListeners ( ) ;
}
2022-02-07 22:26:14 +00:00
String get defaultImagePath = > this . _defaultImagePath ;
2022-02-08 02:13:49 +00:00
2022-02-07 22:26:14 +00:00
set defaultImagePath ( String newVal ) {
this . _defaultImagePath = newVal ;
notifyListeners ( ) ;
}
2022-01-18 21:26:52 +00:00
int get unreadMessages = > this . _unreadMessages ;
2022-02-08 02:13:49 +00:00
2022-01-18 21:26:52 +00:00
set unreadMessages ( int newVal ) {
this . _unreadMessages = newVal ;
notifyListeners ( ) ;
}
2022-02-03 13:43:02 +00:00
void recountUnread ( ) {
this . _unreadMessages = _contacts . contacts . fold ( 0 , ( i , c ) = > i + c . unreadMessages ) ;
}
2022-01-18 21:26:52 +00:00
// Remove a contact from a list. Currently only used when rejecting a group invitation.
// Eventually will also be used for other removals.
void removeContact ( String handle ) {
this . contactList . removeContactByHandle ( handle ) ;
notifyListeners ( ) ;
}
ContactListState get contactList = > this . _contacts ;
2022-02-08 02:13:49 +00:00
2022-01-18 21:26:52 +00:00
ProfileServerListState get serverList = > this . _servers ;
@ override
void dispose ( ) {
super . dispose ( ) ;
}
void updateFrom ( String onion , String name , String picture , String contactsJson , String serverJson , bool online ) {
this . _nickname = name ;
this . _imagePath = picture ;
this . _online = online ;
2022-04-01 22:54:06 +00:00
this . _unreadMessages = 0 ;
2022-01-18 21:26:52 +00:00
this . replaceServers ( serverJson ) ;
2024-02-14 04:02:33 +00:00
if ( contactsJson ! = " " & & contactsJson ! = " null " ) {
2022-01-18 21:26:52 +00:00
List < dynamic > contacts = jsonDecode ( contactsJson ) ;
contacts . forEach ( ( contact ) {
var profileContact = this . _contacts . getContact ( contact [ " identifier " ] ) ;
2022-04-01 22:54:06 +00:00
this . _unreadMessages + = contact [ " numUnread " ] as int ;
2022-01-18 21:26:52 +00:00
if ( profileContact ! = null ) {
profileContact . status = contact [ " status " ] ;
2022-04-20 03:46:37 +00:00
2024-01-03 22:10:07 +00:00
var newCount = contact [ " numMessages " ] as int ;
2022-04-28 15:57:31 +00:00
if ( newCount ! = profileContact . totalMessages ) {
2024-01-03 22:10:07 +00:00
if ( newCount < profileContact . totalMessages ) {
// on Android, when sharing a file the UI may be briefly unloaded for the
// OS to display the file management/selection screen. Afterwards a
// call to ReconnectCwtchForeground will be made which will refresh all values (including count of numMessages)
// **at the same time** the foreground will increment .totalMessages and send a new message to the backend.
// This will result in a negative number of messages being calculated here, and an incorrect totalMessage count.
// This bug is exacerbated in debug mode, and when multiple files are sent in succession. Both cases result in multiple ReconnectCwtchForeground
// events that have the potential to conflict with currentMessageCounts.
// Note that *if* a new message came in at the same time, we would be unable to distinguish this case - as such this is specific instance of a more general problem
// TODO: A true-fix to this bug is to implement a syncing step in the foreground where totalMessages and inFlightMessages can be distinguished
// This requires a change to the backend to confirm submission of an inFlightMessage, which will be implemented in #664
EnvironmentConfig . debugLog ( " Conflicting message counts: $ newCount ${ profileContact . totalMessages } " ) ;
newCount = max ( newCount , profileContact . totalMessages ) ;
}
2022-04-28 15:57:31 +00:00
profileContact . messageCache . addFrontIndexGap ( newCount - profileContact . totalMessages ) ;
2022-04-20 00:00:19 +00:00
}
2022-04-28 15:57:31 +00:00
profileContact . totalMessages = newCount ;
profileContact . unreadMessages = contact [ " numUnread " ] ;
2023-09-26 20:26:52 +00:00
profileContact . lastMessageReceivedTime = DateTime . fromMillisecondsSinceEpoch ( 1000 * int . parse ( contact [ " lastMsgTime " ] ) ) ;
2022-01-18 21:26:52 +00:00
} else {
this . _contacts . add ( ContactInfoState (
2022-01-19 21:58:52 +00:00
this . onion ,
contact [ " identifier " ] ,
contact [ " onion " ] ,
nickname: contact [ " name " ] ,
2022-02-07 22:26:14 +00:00
defaultImagePath: contact [ " defaultPicture " ] ,
2022-01-19 21:58:52 +00:00
status: contact [ " status " ] ,
imagePath: contact [ " picture " ] ,
accepted: contact [ " accepted " ] ,
blocked: contact [ " blocked " ] ,
savePeerHistory: contact [ " saveConversationHistory " ] ,
numMessages: contact [ " numMessages " ] ,
numUnread: contact [ " numUnread " ] ,
isGroup: contact [ " isGroup " ] ,
server: contact [ " groupServer " ] ,
lastMessageTime: DateTime . fromMillisecondsSinceEpoch ( 1000 * int . parse ( contact [ " lastMsgTime " ] ) ) ,
2022-02-08 02:13:49 +00:00
notificationPolicy: contact [ " notificationPolicy " ] ? ? " ConversationNotificationPolicy.Default " ,
2022-01-19 21:58:52 +00:00
) ) ;
2022-01-18 21:26:52 +00:00
}
} ) ;
}
this . _contacts . resort ( ) ;
}
2022-02-05 00:57:31 +00:00
void newMessage (
2022-03-23 23:08:19 +00:00
int identifier , int messageID , DateTime timestamp , String senderHandle , String senderImage , bool isAuto , String data , String contenthash , bool selectedProfile , bool selectedConversation ) {
2022-01-25 22:37:51 +00:00
if ( ! selectedProfile ) {
unreadMessages + + ;
notifyListeners ( ) ;
}
2022-02-05 00:57:31 +00:00
contactList . newMessage ( identifier , messageID , timestamp , senderHandle , senderImage , isAuto , data , contenthash , selectedConversation ) ;
2022-01-25 22:37:51 +00:00
}
2022-01-18 21:26:52 +00:00
void downloadInit ( String fileKey , int numChunks ) {
this . _downloads [ fileKey ] = FileDownloadProgress ( numChunks , DateTime . now ( ) ) ;
2022-01-21 20:08:23 +00:00
notifyListeners ( ) ;
2022-01-18 21:26:52 +00:00
}
void downloadUpdate ( String fileKey , int progress , int numChunks ) {
if ( ! downloadActive ( fileKey ) ) {
this . _downloads [ fileKey ] = FileDownloadProgress ( numChunks , DateTime . now ( ) ) ;
if ( progress < 0 ) {
this . _downloads [ fileKey ] ! . interrupted = true ;
}
} else {
if ( this . _downloads [ fileKey ] ! . interrupted ) {
this . _downloads [ fileKey ] ! . interrupted = false ;
}
this . _downloads [ fileKey ] ! . chunksDownloaded = progress ;
this . _downloads [ fileKey ] ! . chunksTotal = numChunks ;
2023-04-20 22:48:00 +00:00
this . _downloads [ fileKey ] ! . markUpdate ( ) ;
2022-01-18 21:26:52 +00:00
}
notifyListeners ( ) ;
}
void downloadMarkManifest ( String fileKey ) {
if ( ! downloadActive ( fileKey ) ) {
this . _downloads [ fileKey ] = FileDownloadProgress ( 1 , DateTime . now ( ) ) ;
}
this . _downloads [ fileKey ] ! . gotManifest = true ;
2023-04-20 22:48:00 +00:00
this . _downloads [ fileKey ] ! . markUpdate ( ) ;
2022-01-18 21:26:52 +00:00
notifyListeners ( ) ;
}
void downloadMarkFinished ( String fileKey , String finalPath ) {
if ( ! downloadActive ( fileKey ) ) {
// happens as a result of a CheckDownloadStatus call,
// invoked from a historical (timeline) download message
// so setting numChunks correctly shouldn't matter
this . downloadInit ( fileKey , 1 ) ;
}
2022-02-05 00:57:31 +00:00
// Update the contact with a custom profile image if we are
// waiting for one...
if ( this . _downloadTriggers . containsKey ( fileKey ) ) {
int identifier = this . _downloadTriggers [ fileKey ] ! ;
this . contactList . getContact ( identifier ) ! . imagePath = finalPath ;
notifyListeners ( ) ;
}
2022-01-18 21:26:52 +00:00
// only update if different
if ( ! this . _downloads [ fileKey ] ! . complete ) {
this . _downloads [ fileKey ] ! . timeEnd = DateTime . now ( ) ;
this . _downloads [ fileKey ] ! . downloadedTo = finalPath ;
this . _downloads [ fileKey ] ! . complete = true ;
2023-04-20 22:48:00 +00:00
this . _downloads [ fileKey ] ! . markUpdate ( ) ;
2022-01-18 21:26:52 +00:00
notifyListeners ( ) ;
}
}
bool downloadKnown ( String fileKey ) {
return this . _downloads . containsKey ( fileKey ) ;
}
bool downloadActive ( String fileKey ) {
return this . _downloads . containsKey ( fileKey ) & & ! this . _downloads [ fileKey ] ! . interrupted ;
}
bool downloadGotManifest ( String fileKey ) {
return this . _downloads . containsKey ( fileKey ) & & this . _downloads [ fileKey ] ! . gotManifest ;
}
bool downloadComplete ( String fileKey ) {
return this . _downloads . containsKey ( fileKey ) & & this . _downloads [ fileKey ] ! . complete ;
}
bool downloadInterrupted ( String fileKey ) {
2022-07-06 19:14:40 +00:00
if ( this . _downloads . containsKey ( fileKey ) ) {
if ( this . _downloads [ fileKey ] ! . interrupted ) {
return true ;
}
}
return false ;
2022-01-18 21:26:52 +00:00
}
void downloadMarkResumed ( String fileKey ) {
if ( this . _downloads . containsKey ( fileKey ) ) {
this . _downloads [ fileKey ] ! . interrupted = false ;
2022-07-06 19:14:40 +00:00
this . _downloads [ fileKey ] ! . requested = DateTime . now ( ) ;
2023-04-20 22:48:00 +00:00
this . _downloads [ fileKey ] ! . markUpdate ( ) ;
2022-01-21 20:08:23 +00:00
notifyListeners ( ) ;
2022-01-18 21:26:52 +00:00
}
}
double downloadProgress ( String fileKey ) {
return this . _downloads . containsKey ( fileKey ) ? this . _downloads [ fileKey ] ! . progress ( ) : 0.0 ;
}
// used for loading interrupted download info; use downloadMarkFinished for successful downloads
void downloadSetPath ( String fileKey , String path ) {
if ( this . _downloads . containsKey ( fileKey ) ) {
this . _downloads [ fileKey ] ! . downloadedTo = path ;
2022-01-21 20:09:58 +00:00
notifyListeners ( ) ;
2022-01-18 21:26:52 +00:00
}
}
2022-01-20 22:19:06 +00:00
// set the download path for the sender
void downloadSetPathForSender ( String fileKey , String path ) {
2022-01-21 18:12:49 +00:00
// we may trigger this event for auto-downloaded receivers too,
// as such we don't assume anything else about the file...other than that
// it exists.
2022-01-21 20:08:23 +00:00
if ( ! this . _downloads . containsKey ( fileKey ) ) {
2022-01-21 18:12:49 +00:00
// this will be overwritten by download update if the file is being downloaded
2022-01-20 22:42:45 +00:00
this . _downloads [ fileKey ] = FileDownloadProgress ( 1 , DateTime . now ( ) ) ;
}
2022-01-21 18:12:49 +00:00
this . _downloads [ fileKey ] ! . downloadedTo = path ;
2022-01-21 20:09:58 +00:00
notifyListeners ( ) ;
2022-01-20 21:59:54 +00:00
}
2022-01-18 21:26:52 +00:00
String ? downloadFinalPath ( String fileKey ) {
return this . _downloads . containsKey ( fileKey ) ? this . _downloads [ fileKey ] ! . downloadedTo : null ;
}
String downloadSpeed ( String fileKey ) {
if ( ! downloadActive ( fileKey ) | | this . _downloads [ fileKey ] ! . chunksDownloaded = = 0 ) {
return " 0 B/s " ;
}
var bytes = this . _downloads [ fileKey ] ! . chunksDownloaded * 4096 ;
var seconds = ( this . _downloads [ fileKey ] ! . timeEnd ? ? DateTime . now ( ) ) . difference ( this . _downloads [ fileKey ] ! . timeStart ! ) . inSeconds ;
if ( seconds = = 0 ) {
return " 0 B/s " ;
}
return prettyBytes ( ( bytes / seconds ) . round ( ) ) + " /s " ;
}
2022-02-05 00:57:31 +00:00
void waitForDownloadComplete ( int identifier , String fileKey ) {
_downloadTriggers [ fileKey ] = identifier ;
notifyListeners ( ) ;
}
2022-04-14 22:34:17 +00:00
int cacheMemUsage ( ) {
return _contacts . cacheMemUsage ( ) ;
}
2022-04-26 19:16:09 +00:00
void downloadReset ( String fileKey ) {
this . _downloads . remove ( fileKey ) ;
notifyListeners ( ) ;
}
2023-04-04 20:58:42 +00:00
// Profile Attributes. Can be set in Profile Edit View...
List < String ? > attributes = [ null , null , null ] ;
void setAttribute ( int i , String ? value ) {
this . attributes [ i ] = value ;
notifyListeners ( ) ;
}
ProfileStatusMenu availabilityStatus = ProfileStatusMenu . available ;
void setAvailabilityStatus ( String status ) {
switch ( status ) {
case " available " :
availabilityStatus = ProfileStatusMenu . available ;
break ;
case " busy " :
availabilityStatus = ProfileStatusMenu . busy ;
break ;
case " away " :
availabilityStatus = ProfileStatusMenu . away ;
break ;
default :
ProfileStatusMenu . available ;
}
notifyListeners ( ) ;
}
Color getBorderColor ( OpaqueThemeType theme ) {
switch ( this . availabilityStatus ) {
case ProfileStatusMenu . available:
return theme . portraitOnlineBorderColor ;
case ProfileStatusMenu . away:
return theme . portraitOnlineAwayColor ;
case ProfileStatusMenu . busy:
return theme . portraitOnlineBusyColor ;
2023-09-18 14:55:07 +00:00
default :
throw UnimplementedError ( " not a valid status " ) ;
2023-04-04 20:58:42 +00:00
}
}
2023-09-18 14:55:07 +00:00
// during deactivation it is possible that the event bus is cleaned up prior to statuses being updated
// this method nicely cleans up our current state so that the UI functions as expected.
// FIXME: Cwtch should be sending these events prior to shutting down the engine...
void deactivatePeerEngine ( BuildContext context ) {
Provider . of < FlwtchState > ( context , listen: false ) . cwtch . DeactivatePeerEngine ( onion ) ;
this . contactList . contacts . forEach ( ( element ) {
element . status = " Disconnected " ;
2023-09-20 00:11:09 +00:00
// reset retry time to allow for instant reconnection...
element . lastRetryTime = element . loaded ;
2023-09-18 14:55:07 +00:00
} ) ;
this . serverList . servers . forEach ( ( element ) {
element . status = " Disconnected " ;
} ) ;
}
2022-01-19 21:58:52 +00:00
}