From c550437aa516be1013df58c6d257886994966ad5 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 3 Feb 2022 21:17:08 -0500 Subject: [PATCH 1/8] settings pane headers, and notification settings options --- lib/settings.dart | 80 +++++++++++++++++++++++ lib/views/globalsettingsview.dart | 105 +++++++++++++++++++++--------- 2 files changed, 155 insertions(+), 30 deletions(-) diff --git a/lib/settings.dart b/lib/settings.dart index 8bfaa3f9..ab1cca53 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -22,6 +22,17 @@ enum DualpaneMode { CopyPortrait, } +enum NotificationPolicy { + None, + OptIn, + OptOut, +} + +enum NotificationContent { + SimpleEvent, + ContactInfo, +} + /// Settings govern the *Globally* relevant settings like Locale, Theme and Experiments. /// We also provide access to the version information here as it is also accessed from the /// Settings Pane. @@ -29,12 +40,16 @@ class Settings extends ChangeNotifier { Locale locale; late PackageInfo packageInfo; OpaqueThemeType theme; + // explicitly set experiments to false until told otherwise... bool experimentsEnabled = false; HashMap experiments = HashMap.identity(); DualpaneMode _uiColumnModePortrait = DualpaneMode.Single; DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait; + NotificationPolicy _notificationPolicy = NotificationPolicy.OptOut; + NotificationContent _notificationContent = NotificationContent.SimpleEvent; + bool blockUnknownConnections = false; bool streamerMode = false; String _downloadPath = ""; @@ -94,6 +109,9 @@ class Settings extends ChangeNotifier { _uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]); _uiColumnModeLandscape = uiColumnModeFromString(settings["UIColumnModeLandscape"]); + _notificationPolicy = notificationPolicyFromString(settings["NotificationPolicy"]); + _notificationContent = notificationContentFromString(settings["NotificationContent"]); + // auto-download folder _downloadPath = settings["DownloadPath"] ?? ""; @@ -173,17 +191,33 @@ class Settings extends ChangeNotifier { } DualpaneMode get uiColumnModePortrait => _uiColumnModePortrait; + set uiColumnModePortrait(DualpaneMode newval) { this._uiColumnModePortrait = newval; notifyListeners(); } DualpaneMode get uiColumnModeLandscape => _uiColumnModeLandscape; + set uiColumnModeLandscape(DualpaneMode newval) { this._uiColumnModeLandscape = newval; notifyListeners(); } + NotificationPolicy get notificationPolicy => _notificationPolicy; + + set notificationPolicy(NotificationPolicy newpol) { + this._notificationPolicy = newpol; + notifyListeners(); + } + + NotificationContent get notificationContent => _notificationContent; + + set notificationContent(NotificationContent newcon) { + this._notificationContent = newcon; + notifyListeners(); + } + List uiColumns(bool isLandscape) { var m = (!isLandscape || uiColumnModeLandscape == DualpaneMode.CopyPortrait) ? uiColumnModePortrait : uiColumnModeLandscape; switch (m) { @@ -238,6 +272,43 @@ class Settings extends ChangeNotifier { } } + static NotificationPolicy notificationPolicyFromString(String? np) { + switch (np) { + case "None": + return NotificationPolicy.None; + case "OptIn": + return NotificationPolicy.OptIn; + case "OptOut": + return NotificationPolicy.OptOut; + } + return NotificationPolicy.OptOut; + } + + static NotificationContent notificationContentFromString(String? nc) { + switch (nc) { + case "SimpleEvent": + return NotificationContent.SimpleEvent; + case "ContactInfo": + return NotificationContent.ContactInfo; + } + return NotificationContent.SimpleEvent; + } + + static String notificationPolicyToString(NotificationPolicy np, BuildContext context) { + switch (np) { + case NotificationPolicy.None: return "None"; + case NotificationPolicy.OptIn: return "OptIn"; + case NotificationPolicy.OptOut: return "OptOut"; + } + } + + static String notificationContentToString(NotificationContent nc, BuildContext context) { + switch (nc) { + case NotificationContent.SimpleEvent: return "SimpleEvent"; + case NotificationContent.ContactInfo: return "ContactInfo"; + } + } + // checks experiment settings and file extension for image previews // (ignores file size; if the user manually accepts the file, assume it's okay to preview) bool shouldPreview(String path) { @@ -247,18 +318,21 @@ class Settings extends ChangeNotifier { } String get downloadPath => _downloadPath; + set downloadPath(String newval) { _downloadPath = newval; notifyListeners(); } bool get allowAdvancedTorConfig => _allowAdvancedTorConfig; + set allowAdvancedTorConfig(bool torConfig) { _allowAdvancedTorConfig = torConfig; notifyListeners(); } bool get useTorCache => _useTorCache; + set useTorCache(bool useTorCache) { _useTorCache = useTorCache; notifyListeners(); @@ -266,18 +340,21 @@ class Settings extends ChangeNotifier { // Settings / Gettings for setting the custom tor config.. String get torConfig => _customTorConfig; + set torConfig(String torConfig) { _customTorConfig = torConfig; notifyListeners(); } int get socksPort => _socksPort; + set socksPort(int newSocksPort) { _socksPort = newSocksPort; notifyListeners(); } int get controlPort => _controlPort; + set controlPort(int controlPort) { _controlPort = controlPort; notifyListeners(); @@ -285,6 +362,7 @@ class Settings extends ChangeNotifier { // Setters / Getters for toggling whether the app should use a custom tor config bool get useCustomTorConfig => _useCustomTorConfig; + set useCustomTorConfig(bool useCustomTorConfig) { _useCustomTorConfig = useCustomTorConfig; notifyListeners(); @@ -302,6 +380,8 @@ class Settings extends ChangeNotifier { "ThemeMode": theme.mode, "PreviousPid": -1, "BlockUnknownConnections": blockUnknownConnections, + "NotificationPolicy": _notificationPolicy.toString(), + "NotificationContent": _notificationContent.toString(), "StreamerMode": streamerMode, "ExperimentsEnabled": this.experimentsEnabled, "Experiments": experiments, diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 71eea58c..368ab329 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -53,11 +53,13 @@ class _GlobalSettingsViewState extends State { isAlwaysShown: true, child: SingleChildScrollView( clipBehavior: Clip.antiAlias, + padding: EdgeInsets.all(20), child: ConstrainedBox( constraints: BoxConstraints( minHeight: viewportConstraints.maxHeight, ), child: Column(children: [ + Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text("Apperance", style: TextStyle(fontWeight: FontWeight.bold))]), ListTile( title: Text(AppLocalizations.of(context)!.settingLanguage, style: TextStyle(color: settings.current().mainTextColor)), leading: Icon(CwtchIcons.change_language, color: settings.current().mainTextColor), @@ -135,24 +137,76 @@ class _GlobalSettingsViewState extends State { style: TextStyle(color: settings.current().mainTextColor), ), leading: Icon(Icons.table_chart, color: settings.current().mainTextColor), - trailing: Container( - width: MediaQuery.of(context).size.width / 4, - child: DropdownButton( - isExpanded: true, - value: settings.uiColumnModeLandscape.toString(), - onChanged: (String? newValue) { - settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!); - saveSettings(context); - }, - items: Settings.uiColumnModeOptions(true).map>((DualpaneMode value) { - return DropdownMenuItem( - value: value.toString(), - child: Text( - Settings.uiColumnModeToString(value, context), - overflow: TextOverflow.ellipsis, - ), - ); - }).toList()))), + trailing: DropdownButton( + value: settings.uiColumnModeLandscape.toString(), + onChanged: (String? newValue) { + settings.uiColumnModeLandscape = Settings.uiColumnModeFromString(newValue!); + saveSettings(context); + }, + items: Settings.uiColumnModeOptions(true).map>((DualpaneMode value) { + return DropdownMenuItem( + value: value.toString(), + child: Text( + Settings.uiColumnModeToString(value, context), + overflow: TextOverflow.ellipsis, + ), + ); + }).toList())), + SwitchListTile( + title: Text(AppLocalizations.of(context)!.streamerModeLabel, style: TextStyle(color: settings.current().mainTextColor)), + subtitle: Text(AppLocalizations.of(context)!.descriptionStreamerMode), + value: settings.streamerMode, + onChanged: (bool value) { + settings.setStreamerMode(value); + // Save Settings... + saveSettings(context); + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: settings.theme.defaultButtonDisabledColor, + secondary: Icon(CwtchIcons.streamer_bunnymask, color: settings.current().mainTextColor), + ), + SizedBox( + height: 40, + ), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text("Behaviour", style: TextStyle(fontWeight: FontWeight.bold))]), + ListTile( + title: Text(/*AppLocalizations.of(context)!.themeColorLabel*/ "Notification Policy"), + trailing: DropdownButton( + value: settings.notificationPolicy, + onChanged: (NotificationPolicy? newValue) { + settings.notificationPolicy = newValue!; + saveSettings(context); + }, + items: NotificationPolicy.values.map>((NotificationPolicy value) { + return DropdownMenuItem( + value: value, + child: Text( + Settings.notificationPolicyToString(value, context), + overflow: TextOverflow.ellipsis, + ), + ); + }).toList()), + leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), + ), + ListTile( + title: Text(/*AppLocalizations.of(context)!.themeColorLabel*/ "Notification Content"), + trailing: DropdownButton( + value: settings.notificationContent, + onChanged: (NotificationContent? newValue) { + settings.notificationContent = newValue!; + saveSettings(context); + }, + items: NotificationContent.values.map>((NotificationContent value) { + return DropdownMenuItem( + value: value, + child: Text( + Settings.notificationContentToString(value, context), + overflow: TextOverflow.ellipsis, + ), + ); + }).toList()), + leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), + ), SwitchListTile( title: Text(AppLocalizations.of(context)!.blockUnknownLabel, style: TextStyle(color: settings.current().mainTextColor)), subtitle: Text(AppLocalizations.of(context)!.descriptionBlockUnknownConnections), @@ -171,19 +225,10 @@ class _GlobalSettingsViewState extends State { inactiveTrackColor: settings.theme.defaultButtonDisabledColor, secondary: Icon(CwtchIcons.block_unknown, color: settings.current().mainTextColor), ), - SwitchListTile( - title: Text(AppLocalizations.of(context)!.streamerModeLabel, style: TextStyle(color: settings.current().mainTextColor)), - subtitle: Text(AppLocalizations.of(context)!.descriptionStreamerMode), - value: settings.streamerMode, - onChanged: (bool value) { - settings.setStreamerMode(value); - // Save Settings... - saveSettings(context); - }, - activeTrackColor: settings.theme.defaultButtonColor, - inactiveTrackColor: settings.theme.defaultButtonDisabledColor, - secondary: Icon(CwtchIcons.streamer_bunnymask, color: settings.current().mainTextColor), + SizedBox( + height: 40, ), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text("Experiments", style: TextStyle(fontWeight: FontWeight.bold))]), SwitchListTile( title: Text(AppLocalizations.of(context)!.experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor)), subtitle: Text(AppLocalizations.of(context)!.descriptionExperiments), -- 2.25.1 From b382c3d349c17fd691134631b4739b37ab02f078 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Fri, 4 Feb 2022 17:19:02 -0500 Subject: [PATCH 2/8] desktop support for notification settings --- lib/cwtch/cwtchNotifier.dart | 22 +++++++++++++++--- lib/models/contact.dart | 21 ++++++++++++++++- lib/models/profile.dart | 4 +++- lib/settings.dart | 10 ++++---- lib/views/groupsettingsview.dart | 35 +++++++++++++++++++++++++++- lib/views/peersettingsview.dart | 40 +++++++++++++++++++++++++++++++- 6 files changed, 120 insertions(+), 12 deletions(-) diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index d3b74765..4136644d 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -77,6 +77,7 @@ class CwtchNotifier { server: null, archived: false, lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet + options: data["options"] )); break; case "NewServer": @@ -113,7 +114,8 @@ class CwtchNotifier { status: status, server: data["GroupServer"], isGroup: true, - lastMessageTime: DateTime.now())); + lastMessageTime: DateTime.now(), + options: data["options"])); profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(int.parse(data["ConversationID"]), DateTime.now()); } break; @@ -144,7 +146,7 @@ class CwtchNotifier { } break; case "NewMessageFromPeer": - notificationManager.notify("New Message From Peer!"); + var identifier = int.parse(data["ConversationID"]); var messageID = int.parse(data["Index"]); var timestamp = DateTime.tryParse(data['TimestampReceived'])!; @@ -154,6 +156,14 @@ class CwtchNotifier { String? contenthash = data['ContentHash']; var selectedProfile = appState.selectedProfile == data["ProfileOnion"]; var selectedConversation = selectedProfile && appState.selectedConversation == identifier; + var notification = data["notification"]; + + if (notification == "SimpleEvent") { + notificationManager.notify(/*TODO l10n */ "New Message"); + } else if (notification == "ContactInfo") { + var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier); + notificationManager.notify(/*TODO l10n */ "New Message from " + (contact?.nickname ?? senderHandle.toString())); + } profileCN.getProfile(data["ProfileOnion"])?.newMessage( identifier, @@ -209,6 +219,7 @@ class CwtchNotifier { String? contenthash = data['ContentHash']; var selectedProfile = appState.selectedProfile == data["ProfileOnion"]; var selectedConversation = selectedProfile && appState.selectedConversation == identifier; + var notification = data["notification"]; // Only bother to do anything if we know about the group and the provided index is greater than our current total... if (currentTotal != null && idx >= currentTotal) { @@ -224,7 +235,12 @@ class CwtchNotifier { // and `local now`. profileCN.getProfile(data["ProfileOnion"])?.newMessage(identifier, idx, timestampSent, senderHandle, senderImage, isAuto, data["Data"], contenthash, selectedProfile, selectedConversation); - notificationManager.notify("New Message From Group!"); + if (notification == "SimpleEvent") { + notificationManager.notify(/*TODO l10n */ "New Message"); + } else if (notification == "ContactInfo") { + var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier); + notificationManager.notify(/*TODO l10n */ "New Message from " + (contact?.nickname ?? senderHandle.toString())); + } appState.notifyProfileUnread(); } RemoteServerInfoState? server = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(contact.server ?? ""); diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 38cd31cd..3cfd604d 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -10,6 +10,9 @@ class ContactInfoState extends ChangeNotifier { final String onion; late String _nickname; + late bool _notificationsOptIn; + late bool _notificationsOptOut; + late bool _accepted; late bool _blocked; late String _status; @@ -44,7 +47,8 @@ class ContactInfoState extends ChangeNotifier { numUnread = 0, lastMessageTime, server, - archived = false}) { + archived = false, + options = const {} }) { this._nickname = nickname; this._isGroup = isGroup; this._accepted = accepted; @@ -58,6 +62,9 @@ class ContactInfoState extends ChangeNotifier { this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime; this._server = server; this._archived = archived; + print("Contact: $_nickname, Options: $options opt-in: ${options["notification-opt-in"]} opt-out: ${options["notification-opt-out"]} "); + this._notificationsOptIn = (options["notification-opt-in"] ?? "false") == "true"; + this._notificationsOptOut = (options["notification-opt-out"] ?? "false") == "true"; this.messageCache = new MessageCache(); keys = Map>(); } @@ -195,6 +202,18 @@ class ContactInfoState extends ChangeNotifier { } } + bool get notificationsOptIn => _notificationsOptIn; + set notificationsOptIn(bool newVal) { + _notificationsOptIn = newVal; + notifyListeners(); + } + + bool get notificationsOptOut => _notificationsOptOut; + set notificationsOptOut(bool newVal) { + _notificationsOptOut = newVal; + notifyListeners(); + } + GlobalKey getMessageKey(int conversation, int message) { String index = "c: " + conversation.toString() + " m:" + message.toString(); if (keys[index] == null) { diff --git a/lib/models/profile.dart b/lib/models/profile.dart index 070a25cc..a50c9922 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -63,7 +63,8 @@ class ProfileInfoState extends ChangeNotifier { isGroup: contact["isGroup"], server: contact["groupServer"], archived: contact["isArchived"] == true, - lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"]))); + lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])), + options: contact["options"]); })); // dummy set to invoke sort-on-load @@ -183,6 +184,7 @@ class ProfileInfoState extends ChangeNotifier { isGroup: contact["isGroup"], server: contact["groupServer"], lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])), + options: contact["options"], )); } unreadMessages += int.parse(contact["numUnread"]); diff --git a/lib/settings.dart b/lib/settings.dart index ab1cca53..b13eaea6 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -274,11 +274,11 @@ class Settings extends ChangeNotifier { static NotificationPolicy notificationPolicyFromString(String? np) { switch (np) { - case "None": + case "NotificationPolicy.None": return NotificationPolicy.None; - case "OptIn": + case "NotificationPolicy.OptIn": return NotificationPolicy.OptIn; - case "OptOut": + case "NotificationPolicy.OptOut": return NotificationPolicy.OptOut; } return NotificationPolicy.OptOut; @@ -286,9 +286,9 @@ class Settings extends ChangeNotifier { static NotificationContent notificationContentFromString(String? nc) { switch (nc) { - case "SimpleEvent": + case "NotificationContent.SimpleEvent": return NotificationContent.SimpleEvent; - case "ContactInfo": + case "NotificationContent.ContactInfo": return NotificationContent.ContactInfo; } return NotificationContent.SimpleEvent; diff --git a/lib/views/groupsettingsview.dart b/lib/views/groupsettingsview.dart index 1026bf05..9d7f2bf9 100644 --- a/lib/views/groupsettingsview.dart +++ b/lib/views/groupsettingsview.dart @@ -130,7 +130,40 @@ class _GroupSettingsViewState extends State { SizedBox( height: 20, ), - // TODO + Visibility( + visible: Provider.of(context, listen: false).notificationPolicy == NotificationPolicy.OptOut, + child: SwitchListTile( + title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/"Notifications Opt Out", 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(context).notificationsOptOut, + onChanged: (bool optOut) { + Provider.of(context, listen: false).notificationsOptOut = optOut; + var profileOnion = Provider.of(context, listen: false).profileOnion; + var identifier = Provider.of(context, listen: false).identifier; + const NotificationOptOutKey = "profile.notification-opt-out"; + Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptOutKey, optOut.toString()); + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: settings.theme.defaultButtonDisabledColor, + )), + Visibility( + visible: Provider.of(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(context).notificationsOptIn, + onChanged: (bool optIn) { + Provider.of(context, listen: false).notificationsOptIn = optIn; + var profileOnion = Provider.of(context, listen: false).profileOnion; + var identifier = Provider.of(context, listen: false).identifier; + const NotificationOptInKey = "profile.notification-opt-in"; + Provider.of(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: [ diff --git a/lib/views/peersettingsview.dart b/lib/views/peersettingsview.dart index 0206c175..b45954c8 100644 --- a/lib/views/peersettingsview.dart +++ b/lib/views/peersettingsview.dart @@ -39,9 +39,13 @@ class _PeerSettingsViewState extends State { @override Widget build(BuildContext context) { + var handle = Provider.of(context).nickname; + if (handle.isEmpty) { + handle = Provider.of(context).onion; + } return Scaffold( appBar: AppBar( - title: Text(Provider.of(context).onion), + title: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings), ), body: _buildSettingsList(), ); @@ -197,6 +201,40 @@ class _PeerSettingsViewState extends State { child: Text(value), ); }).toList())), + Visibility( + visible: Provider.of(context, listen: false).notificationPolicy == NotificationPolicy.OptOut, + child: SwitchListTile( + title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/"Notifications Opt Out", 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(context).notificationsOptOut, + onChanged: (bool optOut) { + Provider.of(context, listen: false).notificationsOptOut = optOut; + var profileOnion = Provider.of(context, listen: false).profileOnion; + var identifier = Provider.of(context, listen: false).identifier; + const NotificationOptOutKey = "profile.notification-opt-out"; + Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptOutKey, optOut.toString()); + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: settings.theme.defaultButtonDisabledColor, + )), + Visibility( + visible: Provider.of(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(context).notificationsOptIn, + onChanged: (bool optIn) { + Provider.of(context, listen: false).notificationsOptIn = optIn; + var profileOnion = Provider.of(context, listen: false).profileOnion; + var identifier = Provider.of(context, listen: false).identifier; + const NotificationOptInKey = "profile.notification-opt-in"; + Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptInKey, optIn.toString()); + }, + activeTrackColor: settings.theme.defaultButtonColor, + inactiveTrackColor: settings.theme.defaultButtonDisabledColor, + )) ]), Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ SizedBox( -- 2.25.1 From ddefcb8ff289aaae0ec70a63db01c45b84dd195b Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Mon, 7 Feb 2022 21:13:49 -0500 Subject: [PATCH 3/8] rejig notification policy globally and conversationally --- lib/cwtch/cwtchNotifier.dart | 53 +++++++++++++++++--------------- lib/models/contact.dart | 51 +++++++++++++++++++++--------- lib/models/profile.dart | 10 ++++-- lib/settings.dart | 16 +++++----- lib/views/groupsettingsview.dart | 46 ++++++++++----------------- lib/views/peersettingsview.dart | 48 ++++++++++------------------- 6 files changed, 114 insertions(+), 110 deletions(-) diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 4136644d..2f22b89c 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -60,25 +60,24 @@ class CwtchNotifier { case "ContactCreated": EnvironmentConfig.debugLog("ContactCreated $data"); - profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState( - data["ProfileOnion"], - int.parse(data["ConversationID"]), - data["RemotePeer"], - nickname: data["nick"], - status: data["status"], - imagePath: data["picture"], - defaultImagePath: data["defaultPicture"], - blocked: data["blocked"] == "true", - accepted: data["accepted"] == "true", - savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"], - numMessages: int.parse(data["numMessages"]), - numUnread: int.parse(data["unread"]), - isGroup: false, // by definition - server: null, - archived: false, - lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet - options: data["options"] - )); + profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], int.parse(data["ConversationID"]), data["RemotePeer"], + nickname: data["nick"], + status: data["status"], + imagePath: data["picture"], + defaultImagePath: data["defaultPicture"], + blocked: data["blocked"] == "true", + accepted: data["accepted"] == "true", + savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"], + numMessages: int.parse(data["numMessages"]), + numUnread: int.parse(data["unread"]), + isGroup: false, + // by definition + server: null, + archived: false, + lastMessageTime: DateTime.now(), + //show at the top of the contact list even if no messages yet + notificationPolicy: data["notificationPolicy"] ?? "ConversationNotificationPolicy.Default")); + break; case "NewServer": EnvironmentConfig.debugLog("NewServer $data"); @@ -106,8 +105,10 @@ class CwtchNotifier { } 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"], - blocked: false, // we created - accepted: true, // we created + blocked: false, + // we created + accepted: true, + // we created imagePath: data["picture"], defaultImagePath: data["picture"], nickname: data["GroupName"], @@ -115,7 +116,8 @@ class CwtchNotifier { server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now(), - options: data["options"])); + notificationPolicy: data["notificationPolicy"] ?? "ConversationNotificationPolicy.Default")); + profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(int.parse(data["ConversationID"]), DateTime.now()); } break; @@ -146,7 +148,6 @@ class CwtchNotifier { } break; case "NewMessageFromPeer": - var identifier = int.parse(data["ConversationID"]); var messageID = int.parse(data["Index"]); var timestamp = DateTime.tryParse(data['TimestampReceived'])!; @@ -319,8 +320,10 @@ class CwtchNotifier { if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) { var identifier = int.parse(data["ConversationID"]); profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], identifier, groupInvite["GroupID"], - blocked: false, // NewGroup only issued on accepting invite - accepted: true, // NewGroup only issued on accepting invite + blocked: false, + // NewGroup only issued on accepting invite + accepted: true, + // NewGroup only issued on accepting invite imagePath: data["picture"], nickname: groupInvite["GroupName"], server: groupInvite["ServerHost"], diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 3cfd604d..78ac97b8 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -4,14 +4,32 @@ import 'package:flutter/widgets.dart'; import 'message.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 { final String profileOnion; final int identifier; final String onion; late String _nickname; - late bool _notificationsOptIn; - late bool _notificationsOptOut; + late ConversationNotificationPolicy _notificationPolicy; late bool _accepted; late bool _blocked; @@ -48,7 +66,7 @@ class ContactInfoState extends ChangeNotifier { lastMessageTime, server, archived = false, - options = const {} }) { + notificationPolicy = "ConversationNotificationPolicy.Default"}) { this._nickname = nickname; this._isGroup = isGroup; this._accepted = accepted; @@ -62,9 +80,7 @@ class ContactInfoState extends ChangeNotifier { this._lastMessageTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime; this._server = server; this._archived = archived; - print("Contact: $_nickname, Options: $options opt-in: ${options["notification-opt-in"]} opt-out: ${options["notification-opt-out"]} "); - this._notificationsOptIn = (options["notification-opt-in"] ?? "false") == "true"; - this._notificationsOptOut = (options["notification-opt-out"] ?? "false") == "true"; + this._notificationPolicy = notificationPolicyFromString(notificationPolicy); this.messageCache = new MessageCache(); keys = Map>(); } @@ -202,15 +218,10 @@ class ContactInfoState extends ChangeNotifier { } } - bool get notificationsOptIn => _notificationsOptIn; - set notificationsOptIn(bool newVal) { - _notificationsOptIn = newVal; - notifyListeners(); - } + ConversationNotificationPolicy get notificationsPolicy => _notificationPolicy; - bool get notificationsOptOut => _notificationsOptOut; - set notificationsOptOut(bool newVal) { - _notificationsOptOut = newVal; + set notificationsPolicy(ConversationNotificationPolicy newVal) { + _notificationPolicy = newVal; notifyListeners(); } @@ -257,4 +268,16 @@ class ContactInfoState extends ChangeNotifier { this.messageCache.ackCache(messageID); 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; + } } diff --git a/lib/models/profile.dart b/lib/models/profile.dart index a50c9922..1f287401 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -64,7 +64,7 @@ class ProfileInfoState extends ChangeNotifier { server: contact["groupServer"], archived: contact["isArchived"] == true, lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])), - options: contact["options"]); + notificationPolicy: contact["notificationPolicy"] ?? "ConversationNotificationPolicy.Default"); })); // dummy set to invoke sort-on-load @@ -101,6 +101,7 @@ class ProfileInfoState extends ChangeNotifier { // Getters and Setters for Online Status bool get isOnline => this._online; + set isOnline(bool newValue) { this._online = newValue; notifyListeners(); @@ -110,24 +111,28 @@ class ProfileInfoState extends ChangeNotifier { bool get isEncrypted => this._encrypted; String get nickname => this._nickname; + set nickname(String newValue) { this._nickname = newValue; notifyListeners(); } String get imagePath => this._imagePath; + set imagePath(String newVal) { this._imagePath = newVal; notifyListeners(); } String get defaultImagePath => this._defaultImagePath; + set defaultImagePath(String newVal) { this._defaultImagePath = newVal; notifyListeners(); } int get unreadMessages => this._unreadMessages; + set unreadMessages(int newVal) { this._unreadMessages = newVal; notifyListeners(); @@ -145,6 +150,7 @@ class ProfileInfoState extends ChangeNotifier { } ContactListState get contactList => this._contacts; + ProfileServerListState get serverList => this._servers; @override @@ -184,7 +190,7 @@ class ProfileInfoState extends ChangeNotifier { isGroup: contact["isGroup"], server: contact["groupServer"], lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])), - options: contact["options"], + notificationPolicy: contact["notificationPolicy"] ?? "ConversationNotificationPolicy.Default", )); } unreadMessages += int.parse(contact["numUnread"]); diff --git a/lib/settings.dart b/lib/settings.dart index b13eaea6..154af0b9 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -23,9 +23,9 @@ enum DualpaneMode { } enum NotificationPolicy { - None, + Mute, OptIn, - OptOut, + DefaultAll, } enum NotificationContent { @@ -47,7 +47,7 @@ class Settings extends ChangeNotifier { DualpaneMode _uiColumnModePortrait = DualpaneMode.Single; DualpaneMode _uiColumnModeLandscape = DualpaneMode.CopyPortrait; - NotificationPolicy _notificationPolicy = NotificationPolicy.OptOut; + NotificationPolicy _notificationPolicy = NotificationPolicy.DefaultAll; NotificationContent _notificationContent = NotificationContent.SimpleEvent; bool blockUnknownConnections = false; @@ -275,13 +275,13 @@ class Settings extends ChangeNotifier { static NotificationPolicy notificationPolicyFromString(String? np) { switch (np) { case "NotificationPolicy.None": - return NotificationPolicy.None; + return NotificationPolicy.Mute; case "NotificationPolicy.OptIn": return NotificationPolicy.OptIn; case "NotificationPolicy.OptOut": - return NotificationPolicy.OptOut; + return NotificationPolicy.DefaultAll; } - return NotificationPolicy.OptOut; + return NotificationPolicy.DefaultAll; } static NotificationContent notificationContentFromString(String? nc) { @@ -296,9 +296,9 @@ class Settings extends ChangeNotifier { static String notificationPolicyToString(NotificationPolicy np, BuildContext context) { switch (np) { - case NotificationPolicy.None: return "None"; + case NotificationPolicy.Mute: return "Mute"; case NotificationPolicy.OptIn: return "OptIn"; - case NotificationPolicy.OptOut: return "OptOut"; + case NotificationPolicy.DefaultAll: return "DefaultAll"; } } diff --git a/lib/views/groupsettingsview.dart b/lib/views/groupsettingsview.dart index 9d7f2bf9..6d31535d 100644 --- a/lib/views/groupsettingsview.dart +++ b/lib/views/groupsettingsview.dart @@ -130,40 +130,26 @@ class _GroupSettingsViewState extends State { SizedBox( height: 20, ), - Visibility( - visible: Provider.of(context, listen: false).notificationPolicy == NotificationPolicy.OptOut, - child: SwitchListTile( - title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/"Notifications Opt Out", 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(context).notificationsOptOut, - onChanged: (bool optOut) { - Provider.of(context, listen: false).notificationsOptOut = optOut; + ListTile( + title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/ "Conversation Notification Policy", style: TextStyle(color: settings.current().mainTextColor)), + subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/ "The system blah blah..."), + leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), + trailing: DropdownButton( + value: Provider.of(context).notificationsPolicy, + items: ConversationNotificationPolicy.values.map>((ConversationNotificationPolicy value) { + return DropdownMenuItem( + value: value, + child: Text(value.toName), + ); + }).toList(), + onChanged: (ConversationNotificationPolicy? newVal) { + Provider.of(context, listen: false).notificationsPolicy = newVal!; var profileOnion = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; - const NotificationOptOutKey = "profile.notification-opt-out"; - Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptOutKey, optOut.toString()); + const NotificationPolicyKey = "profile.notification-policy"; + Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationPolicyKey, newVal.toString()); }, - activeTrackColor: settings.theme.defaultButtonColor, - inactiveTrackColor: settings.theme.defaultButtonDisabledColor, )), - Visibility( - visible: Provider.of(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(context).notificationsOptIn, - onChanged: (bool optIn) { - Provider.of(context, listen: false).notificationsOptIn = optIn; - var profileOnion = Provider.of(context, listen: false).profileOnion; - var identifier = Provider.of(context, listen: false).identifier; - const NotificationOptInKey = "profile.notification-opt-in"; - Provider.of(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: [ diff --git a/lib/views/peersettingsview.dart b/lib/views/peersettingsview.dart index b45954c8..112c2c3a 100644 --- a/lib/views/peersettingsview.dart +++ b/lib/views/peersettingsview.dart @@ -201,40 +201,26 @@ class _PeerSettingsViewState extends State { child: Text(value), ); }).toList())), - Visibility( - visible: Provider.of(context, listen: false).notificationPolicy == NotificationPolicy.OptOut, - child: SwitchListTile( - title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/"Notifications Opt Out", 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(context).notificationsOptOut, - onChanged: (bool optOut) { - Provider.of(context, listen: false).notificationsOptOut = optOut; - var profileOnion = Provider.of(context, listen: false).profileOnion; - var identifier = Provider.of(context, listen: false).identifier; - const NotificationOptOutKey = "profile.notification-opt-out"; - Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptOutKey, optOut.toString()); - }, - activeTrackColor: settings.theme.defaultButtonColor, - inactiveTrackColor: settings.theme.defaultButtonDisabledColor, - )), - Visibility( - visible: Provider.of(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(context).notificationsOptIn, - onChanged: (bool optIn) { - Provider.of(context, listen: false).notificationsOptIn = optIn; + ListTile( + title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/ "Conversation Notification Policy", style: TextStyle(color: settings.current().mainTextColor)), + subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/ "The system blah blah..."), + leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), + trailing: DropdownButton( + value: Provider.of(context).notificationsPolicy, + items: ConversationNotificationPolicy.values.map>((ConversationNotificationPolicy value) { + return DropdownMenuItem( + value: value, + child: Text(value.toName), + ); + }).toList(), + onChanged: (ConversationNotificationPolicy? newVal) { + Provider.of(context, listen: false).notificationsPolicy = newVal!; var profileOnion = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; - const NotificationOptInKey = "profile.notification-opt-in"; - Provider.of(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, NotificationOptInKey, optIn.toString()); + const NotificationPolicyKey = "profile.notification-policy"; + Provider.of(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: [ SizedBox( -- 2.25.1 From fa6e399aab17005c0106fdc18101bd6a00cde0ab Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Tue, 8 Feb 2022 11:32:05 -0500 Subject: [PATCH 4/8] android work --- .../kotlin/im/cwtch/flwtch/FlwtchWorker.kt | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt index 44ba19f3..c72b1969 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -67,36 +67,53 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val data = JSONObject(evt.Data) val handle = if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID"); if (data["RemotePeer"] != data["ProfileOnion"]) { - val channelId = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createMessageNotificationChannel(handle, handle) - } else { - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" - } val loader = FlutterInjector.instance().flutterLoader() val key = loader.getLookupKeyForAsset("assets/" + data.getString("picture"))//"assets/profiles/001-centaur.png") val fh = applicationContext.assets.open(key) + val notification = data["notification"] - val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> - intent.action = Intent.ACTION_RUN - intent.putExtra("EventType", "NotificationClicked") - intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) - intent.putExtra("Handle", handle) + if (notification != "None") { + + if (notification == "SimpleEvent") { + + } else if (notification == "ContactInfo") { + + } + + + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createMessageNotificationChannel(handle, handle) + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + } + + val loader = FlutterInjector.instance().flutterLoader() + val key = loader.getLookupKeyForAsset("assets/" + data.getString("Picture"))//"assets/profiles/001-centaur.png") + val fh = applicationContext.assets.open(key) + + + val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> + intent.action = Intent.ACTION_RUN + intent.putExtra("EventType", "NotificationClicked") + intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) + intent.putExtra("Handle", handle) + } + + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setContentTitle(data.getString("Nick")) + .setContentText("New message")//todo: translate + .setLargeIcon(BitmapFactory.decodeStream(fh)) + .setSmallIcon(R.mipmap.knott_transparent) + .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setAutoCancel(true) + .build() + notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification) } - - val newNotification = NotificationCompat.Builder(applicationContext, channelId) - .setContentTitle(data.getString("Nick")) - .setContentText("New message")//todo: translate - .setLargeIcon(BitmapFactory.decodeStream(fh)) - .setSmallIcon(R.mipmap.knott_transparent) - .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) - .setAutoCancel(true) - .build() - notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification) } } else if (evt.EventType == "FileDownloadProgressUpdate") { try { -- 2.25.1 From d1d3f23f826b8ca0fe5c79ca7ddc17f4afb43364 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Tue, 8 Feb 2022 12:35:25 -0500 Subject: [PATCH 5/8] android notification policy/content --- .../kotlin/im/cwtch/flwtch/FlwtchWorker.kt | 89 +++++++++++-------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt index c72b1969..d9575127 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -68,52 +68,69 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val handle = if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID"); if (data["RemotePeer"] != data["ProfileOnion"]) { - val loader = FlutterInjector.instance().flutterLoader() - val key = loader.getLookupKeyForAsset("assets/" + data.getString("picture"))//"assets/profiles/001-centaur.png") - val fh = applicationContext.assets.open(key) + val notification = data["notification"] - if (notification != "None") { + + if (notification == "SimpleEvent") { + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createMessageNotificationChannel("Cwtch", "Cwtch") + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + } + val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> + intent.action = Intent.ACTION_RUN + intent.putExtra("EventType", "NotificationClicked") + } + + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setContentTitle("Cwtch") + .setContentText("New message")//todo: translate + .setSmallIcon(R.mipmap.knott_transparent) + .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setAutoCancel(true) + .build() + + notificationManager.notify(getNotificationID("Cwtch", "Cwtch"), newNotification) } else if (notification == "ContactInfo") { + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createMessageNotificationChannel(handle, handle) + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + } + val loader = FlutterInjector.instance().flutterLoader() + val key = loader.getLookupKeyForAsset("assets/" + data.getString("Picture"))//"assets/profiles/001-centaur.png") + val fh = applicationContext.assets.open(key) + val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> + intent.action = Intent.ACTION_RUN + intent.putExtra("EventType", "NotificationClicked") + intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) + intent.putExtra("Handle", handle) + } + + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setContentTitle(data.getString("Nick")) + .setContentText("New message")//todo: translate + .setLargeIcon(BitmapFactory.decodeStream(fh)) + .setSmallIcon(R.mipmap.knott_transparent) + .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setAutoCancel(true) + .build() + + notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification) } - - val channelId = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createMessageNotificationChannel(handle, handle) - } else { - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" - } - - val loader = FlutterInjector.instance().flutterLoader() - val key = loader.getLookupKeyForAsset("assets/" + data.getString("Picture"))//"assets/profiles/001-centaur.png") - val fh = applicationContext.assets.open(key) - - - val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> - intent.action = Intent.ACTION_RUN - intent.putExtra("EventType", "NotificationClicked") - intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) - intent.putExtra("Handle", handle) - } - - val newNotification = NotificationCompat.Builder(applicationContext, channelId) - .setContentTitle(data.getString("Nick")) - .setContentText("New message")//todo: translate - .setLargeIcon(BitmapFactory.decodeStream(fh)) - .setSmallIcon(R.mipmap.knott_transparent) - .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) - .setAutoCancel(true) - .build() - notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification) - } } } else if (evt.EventType == "FileDownloadProgressUpdate") { try { -- 2.25.1 From 4c471989778eddfaf54b86d49c39adbabc8bbefb Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Tue, 8 Feb 2022 16:54:17 -0500 Subject: [PATCH 6/8] notification policy work, translations, even for notifications --- .../kotlin/im/cwtch/flwtch/FlwtchWorker.kt | 18 +++++++++------- lib/cwtch/cwtch.dart | 4 ++++ lib/cwtch/cwtchNotifier.dart | 19 ++++++++++++----- lib/cwtch/ffi.dart | 12 +++++++++++ lib/cwtch/gomobile.dart | 13 ++++++++++++ lib/l10n/intl_de.arb | 21 ++++++++++++++++++- lib/l10n/intl_en.arb | 21 ++++++++++++++++++- lib/l10n/intl_es.arb | 21 ++++++++++++++++++- lib/l10n/intl_fr.arb | 21 ++++++++++++++++++- lib/l10n/intl_it.arb | 21 ++++++++++++++++++- lib/l10n/intl_pl.arb | 21 ++++++++++++++++++- lib/l10n/intl_pt.arb | 21 ++++++++++++++++++- lib/l10n/intl_ru.arb | 21 ++++++++++++++++++- lib/main.dart | 1 - lib/models/contact.dart | 10 +++++---- lib/settings.dart | 10 ++++----- lib/views/globalsettingsview.dart | 12 ++++++----- lib/views/groupsettingsview.dart | 6 +++--- lib/views/peersettingsview.dart | 6 +++--- lib/views/splashView.dart | 8 +++++++ 20 files changed, 245 insertions(+), 42 deletions(-) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt index d9575127..f1664f78 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -29,6 +29,9 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : private var notificationID: MutableMap = mutableMapOf() private var notificationIDnext: Int = 1 + private var notificationSimple: String? = null + private var notificationConversationInfo: String? = null + override suspend fun doWork(): Result { val method = inputData.getString(KEY_METHOD) ?: return Result.failure() @@ -67,14 +70,8 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val data = JSONObject(evt.Data) val handle = if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID"); if (data["RemotePeer"] != data["ProfileOnion"]) { - - - val notification = data["notification"] - - - if (notification == "SimpleEvent") { val channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -92,7 +89,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val newNotification = NotificationCompat.Builder(applicationContext, channelId) .setContentTitle("Cwtch") - .setContentText("New message")//todo: translate + .setContentText(notificationSimple ?: "New Message") .setSmallIcon(R.mipmap.knott_transparent) .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) .setAutoCancel(true) @@ -121,7 +118,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val newNotification = NotificationCompat.Builder(applicationContext, channelId) .setContentTitle(data.getString("Nick")) - .setContentText("New message")//todo: translate + .setContentText((notificationConversationInfo ?: "New Message From %1").replace("%1", data.getString("Nick"))) .setLargeIcon(BitmapFactory.decodeStream(fh)) .setSmallIcon(R.mipmap.knott_transparent) .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) @@ -397,6 +394,11 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val v = (a.get("Val") as? String) ?: "" Cwtch.setServerAttribute(serverOnion, key, v) } + "L10nInit" -> { + notificationSimple = (a.get("notificationSimple") as? String) ?: "New Message" + notificationConversationInfo = (a.get("notificationConversationInfo") as? String) + ?: "New Message From " + } else -> { Log.i("FlwtchWorker", "unknown command: " + method); return Result.failure() diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index 8577b2a5..ca1a4521 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -105,5 +105,9 @@ abstract class Cwtch { // non-ffi String defaultDownloadPath(); + bool isL10nInit(); + + void l10nInit(String notificationSimple, String notificationConversationInfo); + void dispose(); } diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 2f22b89c..0fe92f61 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -9,6 +9,7 @@ import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/servers.dart'; import 'package:cwtch/notification_manager.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import 'package:cwtch/torstatus.dart'; @@ -28,8 +29,11 @@ class CwtchNotifier { late AppState appState; late ServerListState serverListState; + String? notificationSimple; + String? notificationConversationInfo; + CwtchNotifier( - ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) { + ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) { profileCN = pcn; settings = settingsCN; error = errorCN; @@ -39,6 +43,11 @@ class CwtchNotifier { serverListState = serverListStateCN; } + void l10nInit(String notificationSimple, String notificationConversationInfo) { + this.notificationSimple = notificationSimple; + this.notificationConversationInfo = notificationConversationInfo; + } + void handleMessage(String type, dynamic data) { //EnvironmentConfig.debugLog("NewEvent $type $data"); switch (type) { @@ -160,10 +169,10 @@ class CwtchNotifier { var notification = data["notification"]; if (notification == "SimpleEvent") { - notificationManager.notify(/*TODO l10n */ "New Message"); + notificationManager.notify(notificationSimple ?? "New Message"); } else if (notification == "ContactInfo") { var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier); - notificationManager.notify(/*TODO l10n */ "New Message from " + (contact?.nickname ?? senderHandle.toString())); + notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString()))); } profileCN.getProfile(data["ProfileOnion"])?.newMessage( @@ -237,10 +246,10 @@ class CwtchNotifier { profileCN.getProfile(data["ProfileOnion"])?.newMessage(identifier, idx, timestampSent, senderHandle, senderImage, isAuto, data["Data"], contenthash, selectedProfile, selectedConversation); if (notification == "SimpleEvent") { - notificationManager.notify(/*TODO l10n */ "New Message"); + notificationManager.notify(notificationSimple ?? "New Message"); } else if (notification == "ContactInfo") { var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier); - notificationManager.notify(/*TODO l10n */ "New Message from " + (contact?.nickname ?? senderHandle.toString())); + notificationManager.notify( (notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString()))); } appState.notifyProfileUnread(); } diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index baafc04e..c5e56206 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -100,6 +100,7 @@ class CwtchFfi implements Cwtch { late CwtchNotifier cwtchNotifier; late Isolate cwtchIsolate; ReceivePort _receivePort = ReceivePort(); + bool _isL10nInit = false; static String getLibraryPath() { if (Platform.isWindows) { @@ -744,4 +745,15 @@ class CwtchFfi implements Cwtch { malloc.free(utf8newpass); malloc.free(utf8newpasssagain); } + + @override + bool isL10nInit() { + return _isL10nInit; + } + + @override + void l10nInit(String notificationSimple, String notificationConversationInfo) { + cwtchNotifier.l10nInit(notificationSimple, notificationConversationInfo); + _isL10nInit = true; + } } diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 73d30bec..07501e17 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -30,6 +30,7 @@ class CwtchGomobile implements Cwtch { late Future androidHomeDirectory; String androidHomeDirectoryStr = ""; late CwtchNotifier cwtchNotifier; + bool _isL10nInit = false; CwtchGomobile(CwtchNotifier _cwtchNotifier) { print("gomobile.dart: CwtchGomobile()"); @@ -295,4 +296,16 @@ class CwtchGomobile implements Cwtch { void ChangePassword(String profile, String pass, String newpass, String newpassAgain) { cwtchPlatform.invokeMethod("ChangePassword", {"ProfileOnion": profile, "OldPass": pass, "NewPass": newpass, "NewPassAgain": newpassAgain}); } + + @override + bool isL10nInit() { + return _isL10nInit; + } + + @override + void l10nInit(String notificationSimple, String notificationConversationInfo) { + cwtchNotifier.l10nInit(notificationSimple, notificationConversationInfo); + cwtchPlatform.invokeMethod("L10nInit", {"notificationSimple": notificationSimple, "notificationConversationInfo": notificationConversationInfo}); + _isL10nInit = true; + } } diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index cbea3bfd..385316b1 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,6 +1,25 @@ { "@@locale": "de", - "@@last_modified": "2022-02-07T21:17:01+01:00", + "@@last_modified": "2022-02-08T20:13:50+01:00", + "newMessageNotificationConversationInfo": "New Message From %1", + "newMessageNotificationSimple": "New Message", + "notificationContentContactInfo": "Conversation Information", + "notificationContentSimpleEvent": "Plain Event", + "conversationNotificationPolicySettingDescription": "Control this conversation's notification behaviour", + "conversationNotificationPolicySettingLabel": "Conversation Notification Policy", + "settingsGroupExperiments": "Experiments", + "settingsGroupAppearance": "Appearance", + "settingGroupBehaviour": "Behaviour", + "notificationContentSettingDescription": "Controls the contents of conversation notifications", + "notificationPolicySettingDescription": "Controls the default application notification behaviour", + "notificationContentSettingLabel": "Notification Content", + "notificationPolicySettingLabel": "Notification Policy", + "conversationNotificationPolicyNever": "Never", + "conversationNotificationPolicyOptIn": "Opt In", + "conversationNotificationPolicyDefault": "Default", + "notificationPolicyDefaultAll": "Default All", + "notificationPolicyOptIn": "Opt In", + "notificationPolicyMute": "Mute", "tooltipSelectACustomProfileImage": "Select a Custom Profile Image", "torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.", "torSettingsEnableCache": "Cache Tor Consensus", diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 6f481895..f9d4df28 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,6 +1,25 @@ { "@@locale": "en", - "@@last_modified": "2022-02-07T21:17:01+01:00", + "@@last_modified": "2022-02-08T20:13:50+01:00", + "newMessageNotificationConversationInfo": "New Message From %1", + "newMessageNotificationSimple": "New Message", + "notificationContentContactInfo": "Conversation Information", + "notificationContentSimpleEvent": "Plain Event", + "conversationNotificationPolicySettingDescription": "Control this conversation's notification behaviour", + "conversationNotificationPolicySettingLabel": "Conversation Notification Policy", + "settingsGroupExperiments": "Experiments", + "settingsGroupAppearance": "Appearance", + "settingGroupBehaviour": "Behaviour", + "notificationContentSettingDescription": "Controls the contents of conversation notifications", + "notificationPolicySettingDescription": "Controls the default application notification behaviour", + "notificationContentSettingLabel": "Notification Content", + "notificationPolicySettingLabel": "Notification Policy", + "conversationNotificationPolicyNever": "Never", + "conversationNotificationPolicyOptIn": "Opt In", + "conversationNotificationPolicyDefault": "Default", + "notificationPolicyDefaultAll": "Default All", + "notificationPolicyOptIn": "Opt In", + "notificationPolicyMute": "Mute", "tooltipSelectACustomProfileImage": "Select a Custom Profile Image", "editProfile": "Edit Profile", "torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index ff93defc..3f24d8eb 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,6 +1,25 @@ { "@@locale": "es", - "@@last_modified": "2022-02-07T21:17:01+01:00", + "@@last_modified": "2022-02-08T20:13:50+01:00", + "newMessageNotificationConversationInfo": "New Message From %1", + "newMessageNotificationSimple": "New Message", + "notificationContentContactInfo": "Conversation Information", + "notificationContentSimpleEvent": "Plain Event", + "conversationNotificationPolicySettingDescription": "Control this conversation's notification behaviour", + "conversationNotificationPolicySettingLabel": "Conversation Notification Policy", + "settingsGroupExperiments": "Experiments", + "settingsGroupAppearance": "Appearance", + "settingGroupBehaviour": "Behaviour", + "notificationContentSettingDescription": "Controls the contents of conversation notifications", + "notificationPolicySettingDescription": "Controls the default application notification behaviour", + "notificationContentSettingLabel": "Notification Content", + "notificationPolicySettingLabel": "Notification Policy", + "conversationNotificationPolicyNever": "Never", + "conversationNotificationPolicyOptIn": "Opt In", + "conversationNotificationPolicyDefault": "Default", + "notificationPolicyDefaultAll": "Default All", + "notificationPolicyOptIn": "Opt In", + "notificationPolicyMute": "Mute", "tooltipSelectACustomProfileImage": "Select a Custom Profile Image", "torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.", "torSettingsEnableCache": "Cache Tor Consensus", diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 7e8e365b..7418bea7 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,6 +1,25 @@ { "@@locale": "fr", - "@@last_modified": "2022-02-07T21:17:01+01:00", + "@@last_modified": "2022-02-08T20:13:50+01:00", + "newMessageNotificationConversationInfo": "New Message From %1", + "newMessageNotificationSimple": "New Message", + "notificationContentContactInfo": "Conversation Information", + "notificationContentSimpleEvent": "Plain Event", + "conversationNotificationPolicySettingDescription": "Control this conversation's notification behaviour", + "conversationNotificationPolicySettingLabel": "Conversation Notification Policy", + "settingsGroupExperiments": "Experiments", + "settingsGroupAppearance": "Appearance", + "settingGroupBehaviour": "Behaviour", + "notificationContentSettingDescription": "Controls the contents of conversation notifications", + "notificationPolicySettingDescription": "Controls the default application notification behaviour", + "notificationContentSettingLabel": "Notification Content", + "notificationPolicySettingLabel": "Notification Policy", + "conversationNotificationPolicyNever": "Never", + "conversationNotificationPolicyOptIn": "Opt In", + "conversationNotificationPolicyDefault": "Default", + "notificationPolicyDefaultAll": "Default All", + "notificationPolicyOptIn": "Opt In", + "notificationPolicyMute": "Mute", "tooltipSelectACustomProfileImage": "Select a Custom Profile Image", "editProfile": "Modifier le profil", "settingTheme": "Utilisez des thèmes clairs", diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index ac17d6b7..56e59cc7 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,6 +1,25 @@ { "@@locale": "it", - "@@last_modified": "2022-02-07T21:17:01+01:00", + "@@last_modified": "2022-02-08T20:13:50+01:00", + "newMessageNotificationConversationInfo": "New Message From %1", + "newMessageNotificationSimple": "New Message", + "notificationContentContactInfo": "Conversation Information", + "notificationContentSimpleEvent": "Plain Event", + "conversationNotificationPolicySettingDescription": "Control this conversation's notification behaviour", + "conversationNotificationPolicySettingLabel": "Conversation Notification Policy", + "settingsGroupExperiments": "Experiments", + "settingsGroupAppearance": "Appearance", + "settingGroupBehaviour": "Behaviour", + "notificationContentSettingDescription": "Controls the contents of conversation notifications", + "notificationPolicySettingDescription": "Controls the default application notification behaviour", + "notificationContentSettingLabel": "Notification Content", + "notificationPolicySettingLabel": "Notification Policy", + "conversationNotificationPolicyNever": "Never", + "conversationNotificationPolicyOptIn": "Opt In", + "conversationNotificationPolicyDefault": "Default", + "notificationPolicyDefaultAll": "Default All", + "notificationPolicyOptIn": "Opt In", + "notificationPolicyMute": "Mute", "tooltipSelectACustomProfileImage": "Select a Custom Profile Image", "torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.", "torSettingsEnableCache": "Cache Tor Consensus", diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index cf32bf98..3699ccb8 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,6 +1,25 @@ { "@@locale": "pl", - "@@last_modified": "2022-02-07T21:17:01+01:00", + "@@last_modified": "2022-02-08T20:13:50+01:00", + "newMessageNotificationConversationInfo": "New Message From %1", + "newMessageNotificationSimple": "New Message", + "notificationContentContactInfo": "Conversation Information", + "notificationContentSimpleEvent": "Plain Event", + "conversationNotificationPolicySettingDescription": "Control this conversation's notification behaviour", + "conversationNotificationPolicySettingLabel": "Conversation Notification Policy", + "settingsGroupExperiments": "Experiments", + "settingsGroupAppearance": "Appearance", + "settingGroupBehaviour": "Behaviour", + "notificationContentSettingDescription": "Controls the contents of conversation notifications", + "notificationPolicySettingDescription": "Controls the default application notification behaviour", + "notificationContentSettingLabel": "Notification Content", + "notificationPolicySettingLabel": "Notification Policy", + "conversationNotificationPolicyNever": "Never", + "conversationNotificationPolicyOptIn": "Opt In", + "conversationNotificationPolicyDefault": "Default", + "notificationPolicyDefaultAll": "Default All", + "notificationPolicyOptIn": "Opt In", + "notificationPolicyMute": "Mute", "tooltipSelectACustomProfileImage": "Select a Custom Profile Image", "torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.", "torSettingsEnableCache": "Cache Tor Consensus", diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index a8350528..c96e62f2 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,6 +1,25 @@ { "@@locale": "pt", - "@@last_modified": "2022-02-07T21:17:01+01:00", + "@@last_modified": "2022-02-08T20:13:50+01:00", + "newMessageNotificationConversationInfo": "New Message From %1", + "newMessageNotificationSimple": "New Message", + "notificationContentContactInfo": "Conversation Information", + "notificationContentSimpleEvent": "Plain Event", + "conversationNotificationPolicySettingDescription": "Control this conversation's notification behaviour", + "conversationNotificationPolicySettingLabel": "Conversation Notification Policy", + "settingsGroupExperiments": "Experiments", + "settingsGroupAppearance": "Appearance", + "settingGroupBehaviour": "Behaviour", + "notificationContentSettingDescription": "Controls the contents of conversation notifications", + "notificationPolicySettingDescription": "Controls the default application notification behaviour", + "notificationContentSettingLabel": "Notification Content", + "notificationPolicySettingLabel": "Notification Policy", + "conversationNotificationPolicyNever": "Never", + "conversationNotificationPolicyOptIn": "Opt In", + "conversationNotificationPolicyDefault": "Default", + "notificationPolicyDefaultAll": "Default All", + "notificationPolicyOptIn": "Opt In", + "notificationPolicyMute": "Mute", "tooltipSelectACustomProfileImage": "Select a Custom Profile Image", "torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.", "torSettingsEnableCache": "Cache Tor Consensus", diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index be2172b4..e9dc255c 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,6 +1,25 @@ { "@@locale": "ru", - "@@last_modified": "2022-02-07T21:17:01+01:00", + "@@last_modified": "2022-02-08T20:13:50+01:00", + "newMessageNotificationConversationInfo": "New Message From %1", + "newMessageNotificationSimple": "New Message", + "notificationContentContactInfo": "Conversation Information", + "notificationContentSimpleEvent": "Plain Event", + "conversationNotificationPolicySettingDescription": "Control this conversation's notification behaviour", + "conversationNotificationPolicySettingLabel": "Conversation Notification Policy", + "settingsGroupExperiments": "Experiments", + "settingsGroupAppearance": "Appearance", + "settingGroupBehaviour": "Behaviour", + "notificationContentSettingDescription": "Controls the contents of conversation notifications", + "notificationPolicySettingDescription": "Controls the default application notification behaviour", + "notificationContentSettingLabel": "Notification Content", + "notificationPolicySettingLabel": "Notification Policy", + "conversationNotificationPolicyNever": "Never", + "conversationNotificationPolicyOptIn": "Opt In", + "conversationNotificationPolicyDefault": "Default", + "notificationPolicyDefaultAll": "Default All", + "notificationPolicyOptIn": "Opt In", + "notificationPolicyMute": "Mute", "tooltipSelectACustomProfileImage": "Select a Custom Profile Image", "torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.", "torSettingsEnableCache": "Cache Tor Consensus", diff --git a/lib/main.dart b/lib/main.dart index c9e06c77..e9f1b98d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -37,7 +37,6 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); print("runApp()"); runApp(Flwtch()); - sleep(Duration(seconds: 1)); } class Flwtch extends StatefulWidget { diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 78ac97b8..94319c33 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -1,5 +1,7 @@ import 'package:cwtch/widgets/messagerow.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + import 'message.dart'; import 'messagecache.dart'; @@ -11,14 +13,14 @@ enum ConversationNotificationPolicy { } extension Nameable on ConversationNotificationPolicy { - String get toName { + String toName(BuildContext context) { switch (this) { case ConversationNotificationPolicy.Default: - return "Default"; + return AppLocalizations.of(context)!.conversationNotificationPolicyDefault; case ConversationNotificationPolicy.OptIn: - return "Opt In"; + return AppLocalizations.of(context)!.conversationNotificationPolicyOptIn; case ConversationNotificationPolicy.Never: - return "Never"; + return AppLocalizations.of(context)!.conversationNotificationPolicyNever; } } } diff --git a/lib/settings.dart b/lib/settings.dart index 154af0b9..2462cbb3 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -296,16 +296,16 @@ class Settings extends ChangeNotifier { static String notificationPolicyToString(NotificationPolicy np, BuildContext context) { switch (np) { - case NotificationPolicy.Mute: return "Mute"; - case NotificationPolicy.OptIn: return "OptIn"; - case NotificationPolicy.DefaultAll: return "DefaultAll"; + case NotificationPolicy.Mute: return AppLocalizations.of(context)!.notificationPolicyMute; + case NotificationPolicy.OptIn: return AppLocalizations.of(context)!.notificationPolicyOptIn; + case NotificationPolicy.DefaultAll: return AppLocalizations.of(context)!.notificationPolicyDefaultAll; } } static String notificationContentToString(NotificationContent nc, BuildContext context) { switch (nc) { - case NotificationContent.SimpleEvent: return "SimpleEvent"; - case NotificationContent.ContactInfo: return "ContactInfo"; + case NotificationContent.SimpleEvent: return AppLocalizations.of(context)!.notificationContentSimpleEvent; + case NotificationContent.ContactInfo: return AppLocalizations.of(context)!.notificationContentContactInfo; } } diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 368ab329..3d14065e 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -59,7 +59,7 @@ class _GlobalSettingsViewState extends State { minHeight: viewportConstraints.maxHeight, ), child: Column(children: [ - Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text("Apperance", style: TextStyle(fontWeight: FontWeight.bold))]), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(AppLocalizations.of(context)!.settingsGroupAppearance, style: TextStyle(fontWeight: FontWeight.bold))]), ListTile( title: Text(AppLocalizations.of(context)!.settingLanguage, style: TextStyle(color: settings.current().mainTextColor)), leading: Icon(CwtchIcons.change_language, color: settings.current().mainTextColor), @@ -168,9 +168,10 @@ class _GlobalSettingsViewState extends State { SizedBox( height: 40, ), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text("Behaviour", style: TextStyle(fontWeight: FontWeight.bold))]), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(AppLocalizations.of(context)!.settingGroupBehaviour, style: TextStyle(fontWeight: FontWeight.bold))]), ListTile( - title: Text(/*AppLocalizations.of(context)!.themeColorLabel*/ "Notification Policy"), + title: Text(AppLocalizations.of(context)!.notificationPolicySettingLabel), + subtitle: Text(AppLocalizations.of(context)!.notificationPolicySettingDescription), trailing: DropdownButton( value: settings.notificationPolicy, onChanged: (NotificationPolicy? newValue) { @@ -189,7 +190,8 @@ class _GlobalSettingsViewState extends State { leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), ), ListTile( - title: Text(/*AppLocalizations.of(context)!.themeColorLabel*/ "Notification Content"), + title: Text(AppLocalizations.of(context)!.notificationContentSettingLabel), + subtitle: Text(AppLocalizations.of(context)!.notificationContentSettingDescription), trailing: DropdownButton( value: settings.notificationContent, onChanged: (NotificationContent? newValue) { @@ -228,7 +230,7 @@ class _GlobalSettingsViewState extends State { SizedBox( height: 40, ), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text("Experiments", style: TextStyle(fontWeight: FontWeight.bold))]), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [Text(AppLocalizations.of(context)!.settingsGroupExperiments, style: TextStyle(fontWeight: FontWeight.bold))]), SwitchListTile( title: Text(AppLocalizations.of(context)!.experimentsEnabled, style: TextStyle(color: settings.current().mainTextColor)), subtitle: Text(AppLocalizations.of(context)!.descriptionExperiments), diff --git a/lib/views/groupsettingsview.dart b/lib/views/groupsettingsview.dart index 6d31535d..989e1bba 100644 --- a/lib/views/groupsettingsview.dart +++ b/lib/views/groupsettingsview.dart @@ -131,15 +131,15 @@ class _GroupSettingsViewState extends State { height: 20, ), ListTile( - title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/ "Conversation Notification Policy", style: TextStyle(color: settings.current().mainTextColor)), - subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/ "The system blah blah..."), + title: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingLabel, style: TextStyle(color: settings.current().mainTextColor)), + subtitle: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingDescription), leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), trailing: DropdownButton( value: Provider.of(context).notificationsPolicy, items: ConversationNotificationPolicy.values.map>((ConversationNotificationPolicy value) { return DropdownMenuItem( value: value, - child: Text(value.toName), + child: Text(value.toName(context)), ); }).toList(), onChanged: (ConversationNotificationPolicy? newVal) { diff --git a/lib/views/peersettingsview.dart b/lib/views/peersettingsview.dart index 112c2c3a..304e5749 100644 --- a/lib/views/peersettingsview.dart +++ b/lib/views/peersettingsview.dart @@ -202,15 +202,15 @@ class _PeerSettingsViewState extends State { ); }).toList())), ListTile( - title: Text(/*AppLocalizations.of(context)!.savePeerHistory*/ "Conversation Notification Policy", style: TextStyle(color: settings.current().mainTextColor)), - subtitle: Text(/*AppLocalizations.of(context)!.savePeerHistoryDescription*/ "The system blah blah..."), + title: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingLabel, style: TextStyle(color: settings.current().mainTextColor)), + subtitle: Text(AppLocalizations.of(context)!.conversationNotificationPolicySettingDescription), leading: Icon(CwtchIcons.chat_bubble_empty_24px, color: settings.current().mainTextColor), trailing: DropdownButton( value: Provider.of(context).notificationsPolicy, items: ConversationNotificationPolicy.values.map>((ConversationNotificationPolicy value) { return DropdownMenuItem( value: value, - child: Text(value.toName), + child: Text(value.toName(context)), ); }).toList(), onChanged: (ConversationNotificationPolicy? newVal) { diff --git a/lib/views/splashView.dart b/lib/views/splashView.dart index 2d5d14f5..47b504ed 100644 --- a/lib/views/splashView.dart +++ b/lib/views/splashView.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import '../main.dart'; import '../settings.dart'; class SplashView extends StatefulWidget { @@ -14,6 +15,13 @@ class SplashView extends StatefulWidget { class _SplashViewState extends State { @override Widget build(BuildContext context) { + var cwtch = Provider.of(context, listen: false).cwtch; + if (!cwtch.isL10nInit()) { + if (AppLocalizations.of(context) != null && AppLocalizations.of(context)!.newMessageNotificationSimple.isNotEmpty ) { + cwtch.l10nInit(AppLocalizations.of(context)!.newMessageNotificationSimple, AppLocalizations.of(context)!.newMessageNotificationConversationInfo); + } + } + return Consumer( builder: (context, appState, child) => Scaffold( key: Key("SplashView"), -- 2.25.1 From 706d1da51841206b5eb8ec5ba3c9ad0e4dbf3a90 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Tue, 8 Feb 2022 17:50:32 -0500 Subject: [PATCH 7/8] new lcg version --- LIBCWTCH-GO-MACOS.version | 2 +- LIBCWTCH-GO.version | 2 +- lib/cwtch/cwtchNotifier.dart | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LIBCWTCH-GO-MACOS.version b/LIBCWTCH-GO-MACOS.version index 00bd89ea..fc00f018 100644 --- a/LIBCWTCH-GO-MACOS.version +++ b/LIBCWTCH-GO-MACOS.version @@ -1 +1 @@ -2022-02-08-16-19-v1.5.4-36-g4467c40 \ No newline at end of file +2022-02-08-22-37-v1.5.4-41-gd0d5300 \ No newline at end of file diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index 5ec5256c..fc00f018 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -2022-02-08-21-19-v1.5.4-36-g4467c40 \ No newline at end of file +2022-02-08-22-37-v1.5.4-41-gd0d5300 \ No newline at end of file diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index ca72fb7b..5d677bff 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -33,7 +33,7 @@ class CwtchNotifier { String? notificationConversationInfo; CwtchNotifier( - ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) { + ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) { profileCN = pcn; settings = settingsCN; error = errorCN; @@ -172,7 +172,7 @@ class CwtchNotifier { notificationManager.notify(notificationSimple ?? "New Message"); } else if (notification == "ContactInfo") { var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier); - notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString()))); + notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString()))); } profileCN.getProfile(data["ProfileOnion"])?.newMessage( @@ -249,7 +249,7 @@ class CwtchNotifier { notificationManager.notify(notificationSimple ?? "New Message"); } else if (notification == "ContactInfo") { var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier); - notificationManager.notify( (notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString()))); + notificationManager.notify((notificationConversationInfo ?? "New Message from %1").replaceFirst("%1", (contact?.nickname ?? senderHandle.toString()))); } appState.notifyProfileUnread(); } -- 2.25.1 From 8ba54469eb74abf211db6f8ab7eb73e6bc8edc30 Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Tue, 8 Feb 2022 18:21:06 -0500 Subject: [PATCH 8/8] comment/format fix --- lib/cwtch/cwtchNotifier.dart | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 5d677bff..63e28097 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -79,12 +79,10 @@ class CwtchNotifier { savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"], numMessages: int.parse(data["numMessages"]), numUnread: int.parse(data["unread"]), - isGroup: false, - // by definition + isGroup: false, // by definition server: null, archived: false, - lastMessageTime: DateTime.now(), - //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")); break; @@ -114,10 +112,8 @@ class CwtchNotifier { } 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"], - blocked: false, - // we created - accepted: true, - // we created + blocked: false, // we created + accepted: true, // we created imagePath: data["picture"], defaultImagePath: data["picture"], nickname: data["GroupName"], @@ -329,10 +325,8 @@ class CwtchNotifier { if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) { var identifier = int.parse(data["ConversationID"]); profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], identifier, groupInvite["GroupID"], - blocked: false, - // NewGroup only issued on accepting invite - accepted: true, - // NewGroup only issued on accepting invite + blocked: false, // NewGroup only issued on accepting invite + accepted: true, // NewGroup only issued on accepting invite imagePath: data["picture"], nickname: groupInvite["GroupName"], server: groupInvite["ServerHost"], -- 2.25.1