rejig notification policy globally and conversationally

This commit is contained in:
Dan Ballard 2022-02-07 21:13:49 -05:00
parent b382c3d349
commit ddefcb8ff2
6 changed files with 114 additions and 110 deletions

View File

@ -60,25 +60,24 @@ class CwtchNotifier {
case "ContactCreated": case "ContactCreated":
EnvironmentConfig.debugLog("ContactCreated $data"); EnvironmentConfig.debugLog("ContactCreated $data");
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState( profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], int.parse(data["ConversationID"]), data["RemotePeer"],
data["ProfileOnion"], nickname: data["nick"],
int.parse(data["ConversationID"]), status: data["status"],
data["RemotePeer"], imagePath: data["picture"],
nickname: data["nick"], defaultImagePath: data["defaultPicture"],
status: data["status"], blocked: data["blocked"] == "true",
imagePath: data["picture"], accepted: data["accepted"] == "true",
defaultImagePath: data["defaultPicture"], savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"],
blocked: data["blocked"] == "true", numMessages: int.parse(data["numMessages"]),
accepted: data["accepted"] == "true", numUnread: int.parse(data["unread"]),
savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"], isGroup: false,
numMessages: int.parse(data["numMessages"]), // by definition
numUnread: int.parse(data["unread"]), server: null,
isGroup: false, // by definition archived: false,
server: null, lastMessageTime: DateTime.now(),
archived: false, //show at the top of the contact list even if no messages yet
lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet notificationPolicy: data["notificationPolicy"] ?? "ConversationNotificationPolicy.Default"));
options: data["options"]
));
break; break;
case "NewServer": case "NewServer":
EnvironmentConfig.debugLog("NewServer $data"); EnvironmentConfig.debugLog("NewServer $data");
@ -106,8 +105,10 @@ class CwtchNotifier {
} }
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(int.parse(data["ConversationID"])) == null) { if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(int.parse(data["ConversationID"])) == null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], int.parse(data["ConversationID"]), data["GroupID"], profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], int.parse(data["ConversationID"]), data["GroupID"],
blocked: false, // we created blocked: false,
accepted: true, // we created // we created
accepted: true,
// we created
imagePath: data["picture"], imagePath: data["picture"],
defaultImagePath: data["picture"], defaultImagePath: data["picture"],
nickname: data["GroupName"], nickname: data["GroupName"],
@ -115,7 +116,8 @@ class CwtchNotifier {
server: data["GroupServer"], server: data["GroupServer"],
isGroup: true, isGroup: true,
lastMessageTime: DateTime.now(), lastMessageTime: DateTime.now(),
options: data["options"])); notificationPolicy: data["notificationPolicy"] ?? "ConversationNotificationPolicy.Default"));
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(int.parse(data["ConversationID"]), DateTime.now()); profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(int.parse(data["ConversationID"]), DateTime.now());
} }
break; break;
@ -146,7 +148,6 @@ class CwtchNotifier {
} }
break; break;
case "NewMessageFromPeer": case "NewMessageFromPeer":
var identifier = int.parse(data["ConversationID"]); var identifier = int.parse(data["ConversationID"]);
var messageID = int.parse(data["Index"]); var messageID = int.parse(data["Index"]);
var timestamp = DateTime.tryParse(data['TimestampReceived'])!; var timestamp = DateTime.tryParse(data['TimestampReceived'])!;
@ -319,8 +320,10 @@ class CwtchNotifier {
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) { if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) {
var identifier = int.parse(data["ConversationID"]); var identifier = int.parse(data["ConversationID"]);
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], identifier, groupInvite["GroupID"], profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], identifier, groupInvite["GroupID"],
blocked: false, // NewGroup only issued on accepting invite blocked: false,
accepted: true, // NewGroup only issued on accepting invite // NewGroup only issued on accepting invite
accepted: true,
// NewGroup only issued on accepting invite
imagePath: data["picture"], imagePath: data["picture"],
nickname: groupInvite["GroupName"], nickname: groupInvite["GroupName"],
server: groupInvite["ServerHost"], server: groupInvite["ServerHost"],

View File

@ -4,14 +4,32 @@ import 'package:flutter/widgets.dart';
import 'message.dart'; import 'message.dart';
import 'messagecache.dart'; import 'messagecache.dart';
enum ConversationNotificationPolicy {
Default,
OptIn,
Never,
}
extension Nameable on ConversationNotificationPolicy {
String get toName {
switch (this) {
case ConversationNotificationPolicy.Default:
return "Default";
case ConversationNotificationPolicy.OptIn:
return "Opt In";
case ConversationNotificationPolicy.Never:
return "Never";
}
}
}
class ContactInfoState extends ChangeNotifier { class ContactInfoState extends ChangeNotifier {
final String profileOnion; final String profileOnion;
final int identifier; final int identifier;
final String onion; final String onion;
late String _nickname; late String _nickname;
late bool _notificationsOptIn; late ConversationNotificationPolicy _notificationPolicy;
late bool _notificationsOptOut;
late bool _accepted; late bool _accepted;
late bool _blocked; late bool _blocked;
@ -48,7 +66,7 @@ class ContactInfoState extends ChangeNotifier {
lastMessageTime, lastMessageTime,
server, server,
archived = false, archived = false,
options = const {} }) { notificationPolicy = "ConversationNotificationPolicy.Default"}) {
this._nickname = nickname; this._nickname = nickname;
this._isGroup = isGroup; this._isGroup = isGroup;
this._accepted = accepted; this._accepted = accepted;
@ -62,9 +80,7 @@ class ContactInfoState extends ChangeNotifier {
this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime; this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime;
this._server = server; this._server = server;
this._archived = archived; this._archived = archived;
print("Contact: $_nickname, Options: $options opt-in: ${options["notification-opt-in"]} opt-out: ${options["notification-opt-out"]} "); this._notificationPolicy = notificationPolicyFromString(notificationPolicy);
this._notificationsOptIn = (options["notification-opt-in"] ?? "false") == "true";
this._notificationsOptOut = (options["notification-opt-out"] ?? "false") == "true";
this.messageCache = new MessageCache(); this.messageCache = new MessageCache();
keys = Map<String, GlobalKey<MessageRowState>>(); keys = Map<String, GlobalKey<MessageRowState>>();
} }
@ -202,15 +218,10 @@ class ContactInfoState extends ChangeNotifier {
} }
} }
bool get notificationsOptIn => _notificationsOptIn; ConversationNotificationPolicy get notificationsPolicy => _notificationPolicy;
set notificationsOptIn(bool newVal) {
_notificationsOptIn = newVal;
notifyListeners();
}
bool get notificationsOptOut => _notificationsOptOut; set notificationsPolicy(ConversationNotificationPolicy newVal) {
set notificationsOptOut(bool newVal) { _notificationPolicy = newVal;
_notificationsOptOut = newVal;
notifyListeners(); notifyListeners();
} }
@ -257,4 +268,16 @@ class ContactInfoState extends ChangeNotifier {
this.messageCache.ackCache(messageID); this.messageCache.ackCache(messageID);
notifyListeners(); notifyListeners();
} }
static ConversationNotificationPolicy notificationPolicyFromString(String val) {
switch (val) {
case "ConversationNotificationPolicy.Default":
return ConversationNotificationPolicy.Default;
case "ConversationNotificationPolicy.OptIn":
return ConversationNotificationPolicy.OptIn;
case "ConversationNotificationPolicy.Never":
return ConversationNotificationPolicy.Never;
}
return ConversationNotificationPolicy.Never;
}
} }

View File

@ -64,7 +64,7 @@ class ProfileInfoState extends ChangeNotifier {
server: contact["groupServer"], server: contact["groupServer"],
archived: contact["isArchived"] == true, archived: contact["isArchived"] == true,
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])), lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])),
options: contact["options"]); notificationPolicy: contact["notificationPolicy"] ?? "ConversationNotificationPolicy.Default");
})); }));
// dummy set to invoke sort-on-load // dummy set to invoke sort-on-load
@ -101,6 +101,7 @@ class ProfileInfoState extends ChangeNotifier {
// Getters and Setters for Online Status // Getters and Setters for Online Status
bool get isOnline => this._online; bool get isOnline => this._online;
set isOnline(bool newValue) { set isOnline(bool newValue) {
this._online = newValue; this._online = newValue;
notifyListeners(); notifyListeners();
@ -110,24 +111,28 @@ class ProfileInfoState extends ChangeNotifier {
bool get isEncrypted => this._encrypted; bool get isEncrypted => this._encrypted;
String get nickname => this._nickname; String get nickname => this._nickname;
set nickname(String newValue) { set nickname(String newValue) {
this._nickname = newValue; this._nickname = newValue;
notifyListeners(); notifyListeners();
} }
String get imagePath => this._imagePath; String get imagePath => this._imagePath;
set imagePath(String newVal) { set imagePath(String newVal) {
this._imagePath = newVal; this._imagePath = newVal;
notifyListeners(); notifyListeners();
} }
String get defaultImagePath => this._defaultImagePath; String get defaultImagePath => this._defaultImagePath;
set defaultImagePath(String newVal) { set defaultImagePath(String newVal) {
this._defaultImagePath = newVal; this._defaultImagePath = newVal;
notifyListeners(); notifyListeners();
} }
int get unreadMessages => this._unreadMessages; int get unreadMessages => this._unreadMessages;
set unreadMessages(int newVal) { set unreadMessages(int newVal) {
this._unreadMessages = newVal; this._unreadMessages = newVal;
notifyListeners(); notifyListeners();
@ -145,6 +150,7 @@ class ProfileInfoState extends ChangeNotifier {
} }
ContactListState get contactList => this._contacts; ContactListState get contactList => this._contacts;
ProfileServerListState get serverList => this._servers; ProfileServerListState get serverList => this._servers;
@override @override
@ -184,7 +190,7 @@ class ProfileInfoState extends ChangeNotifier {
isGroup: contact["isGroup"], isGroup: contact["isGroup"],
server: contact["groupServer"], server: contact["groupServer"],
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])), lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])),
options: contact["options"], notificationPolicy: contact["notificationPolicy"] ?? "ConversationNotificationPolicy.Default",
)); ));
} }
unreadMessages += int.parse(contact["numUnread"]); unreadMessages += int.parse(contact["numUnread"]);

View File

@ -23,9 +23,9 @@ enum DualpaneMode {
} }
enum NotificationPolicy { enum NotificationPolicy {
None, Mute,
OptIn, OptIn,
OptOut, DefaultAll,
} }
enum NotificationContent { enum NotificationContent {
@ -47,7 +47,7 @@ class Settings extends ChangeNotifier {
DualpaneMode _uiColumnModePortrait = DualpaneMode.Single; DualpaneMode _uiColumnModePortrait = DualpaneMode.Single;
DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait; DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait;
NotificationPolicy _notificationPolicy = NotificationPolicy.OptOut; NotificationPolicy _notificationPolicy = NotificationPolicy.DefaultAll;
NotificationContent _notificationContent = NotificationContent.SimpleEvent; NotificationContent _notificationContent = NotificationContent.SimpleEvent;
bool blockUnknownConnections = false; bool blockUnknownConnections = false;
@ -275,13 +275,13 @@ class Settings extends ChangeNotifier {
static NotificationPolicy notificationPolicyFromString(String? np) { static NotificationPolicy notificationPolicyFromString(String? np) {
switch (np) { switch (np) {
case "NotificationPolicy.None": case "NotificationPolicy.None":
return NotificationPolicy.None; return NotificationPolicy.Mute;
case "NotificationPolicy.OptIn": case "NotificationPolicy.OptIn":
return NotificationPolicy.OptIn; return NotificationPolicy.OptIn;
case "NotificationPolicy.OptOut": case "NotificationPolicy.OptOut":
return NotificationPolicy.OptOut; return NotificationPolicy.DefaultAll;
} }
return NotificationPolicy.OptOut; return NotificationPolicy.DefaultAll;
} }
static NotificationContent notificationContentFromString(String? nc) { static NotificationContent notificationContentFromString(String? nc) {
@ -296,9 +296,9 @@ class Settings extends ChangeNotifier {
static String notificationPolicyToString(NotificationPolicy np, BuildContext context) { static String notificationPolicyToString(NotificationPolicy np, BuildContext context) {
switch (np) { switch (np) {
case NotificationPolicy.None: return "None"; case NotificationPolicy.Mute: return "Mute";
case NotificationPolicy.OptIn: return "OptIn"; case NotificationPolicy.OptIn: return "OptIn";
case NotificationPolicy.OptOut: return "OptOut"; case NotificationPolicy.DefaultAll: return "DefaultAll";
} }
} }

View File

@ -130,40 +130,26 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
SizedBox( SizedBox(
height: 20, height: 20,
), ),
Visibility( ListTile(
visible: Provider.of<Settings>(context, listen: false).notificationPolicy == NotificationPolicy.OptOut, title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/ "Conversation Notification Policy", style: TextStyle(color: settings.current().mainTextColor)),
child: SwitchListTile( subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/ "The system blah blah..."),
title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/"Notifications Opt Out", style: TextStyle(color: settings.current().mainTextColor)), leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor),
subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/"The system blah blah..."), trailing: DropdownButton(
secondary: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), value: Provider.of<ContactInfoState>(context).notificationsPolicy,
value: Provider.of<ContactInfoState>(context).notificationsOptOut, items: ConversationNotificationPolicy.values.map<DropdownMenuItem<ConversationNotificationPolicy>>((ConversationNotificationPolicy value) {
onChanged: (bool optOut) { return DropdownMenuItem<ConversationNotificationPolicy>(
Provider.of<ContactInfoState>(context, listen: false).notificationsOptOut = optOut; value: value,
child: Text(value.toName),
);
}).toList(),
onChanged: (ConversationNotificationPolicy? newVal) {
Provider.of<ContactInfoState>(context, listen: false).notificationsPolicy = newVal!;
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion; var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier; var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
const NotificationOptOutKey = "profile.notification-opt-out"; const NotificationPolicyKey = "profile.notification-policy";
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptOutKey, optOut.toString()); Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationPolicyKey, newVal.toString());
}, },
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
)), )),
Visibility(
visible: Provider.of<Settings>(context, listen: false).notificationPolicy == NotificationPolicy.OptIn,
child: SwitchListTile(
title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/"Notifications Opt In", style: TextStyle(color: settings.current().mainTextColor)),
subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/"The system blah blah..."),
secondary: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor),
value: Provider.of<ContactInfoState>(context).notificationsOptIn,
onChanged: (bool optIn) {
Provider.of<ContactInfoState>(context, listen: false).notificationsOptIn = optIn;
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
const NotificationOptInKey = "profile.notification-opt-in";
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptInKey, optIn.toString());
},
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
))
]), ]),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [

View File

@ -201,40 +201,26 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
child: Text(value), child: Text(value),
); );
}).toList())), }).toList())),
Visibility( ListTile(
visible: Provider.of<Settings>(context, listen: false).notificationPolicy == NotificationPolicy.OptOut, title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/ "Conversation Notification Policy", style: TextStyle(color: settings.current().mainTextColor)),
child: SwitchListTile( subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/ "The system blah blah..."),
title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/"Notifications Opt Out", style: TextStyle(color: settings.current().mainTextColor)), leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor),
subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/"The system blah blah..."), trailing: DropdownButton(
secondary: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), value: Provider.of<ContactInfoState>(context).notificationsPolicy,
value: Provider.of<ContactInfoState>(context).notificationsOptOut, items: ConversationNotificationPolicy.values.map<DropdownMenuItem<ConversationNotificationPolicy>>((ConversationNotificationPolicy value) {
onChanged: (bool optOut) { return DropdownMenuItem<ConversationNotificationPolicy>(
Provider.of<ContactInfoState>(context, listen: false).notificationsOptOut = optOut; value: value,
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion; child: Text(value.toName),
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier; );
const NotificationOptOutKey = "profile.notification-opt-out"; }).toList(),
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptOutKey, optOut.toString()); onChanged: (ConversationNotificationPolicy? newVal) {
}, Provider.of<ContactInfoState>(context, listen: false).notificationsPolicy = newVal!;
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
)),
Visibility(
visible: Provider.of<Settings>(context, listen: false).notificationPolicy == NotificationPolicy.OptIn,
child: SwitchListTile(
title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/"Notifications Opt In", style: TextStyle(color: settings.current().mainTextColor)),
subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/"The system blah blah..."),
secondary: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor),
value: Provider.of<ContactInfoState>(context).notificationsOptIn,
onChanged: (bool optIn) {
Provider.of<ContactInfoState>(context, listen: false).notificationsOptIn = optIn;
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion; var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier; var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
const NotificationOptInKey = "profile.notification-opt-in"; const NotificationPolicyKey = "profile.notification-policy";
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptInKey, optIn.toString()); Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationPolicyKey, newVal.toString());
}, },
activeTrackColor: settings.theme.defaultButtonColor, )),
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
))
]), ]),
Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox( SizedBox(