Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add Notification Service Extension #2105

Merged
merged 31 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9a4d8c3
add DcNotificationService
r10s Mar 2, 2024
2a1b747
fix issue number one: different deployment targets stand in the way o…
r10s Mar 3, 2024
fd7ec26
fix compiler complaining about bad versions
r10s Mar 3, 2024
98d0c52
assign DcNotificationService to group.chat.delta.ios so it can access…
r10s Mar 4, 2024
314f49f
remove dead code
r10s Mar 4, 2024
c04fa76
make notifications discardable, use more reasonable fallback
r10s Mar 3, 2024
33636dd
set body to 'N messages' as needed
r10s Mar 4, 2024
e546b3d
skip String.localized() for now as not working in extension (but is w…
r10s Mar 4, 2024
2717d9b
link DcCore, to fix 'umbrella header missing' - not sure why that wor…
r10s Mar 6, 2024
8c08f43
tune down unrelevant messages until we can discard them
r10s Mar 6, 2024
9c3c645
unify summary for groups
r10s Mar 6, 2024
8cb1f63
update badge counter
r10s Mar 6, 2024
c774325
close databases when fetch is done
r10s Mar 6, 2024
df48825
if a notification spans more than one chat, add all of them to the ti…
r10s Mar 6, 2024
8426bee
force UI updates in case app was suspended
r10s Mar 6, 2024
84a988c
a trigger seems not to be needed, therefore also no need to remove pe…
r10s Mar 7, 2024
c004172
because of accounts not using PUSH, we do not have a notification for…
r10s Mar 7, 2024
020e98f
threadIdentifier not needed if the same everywhere; this also groups …
r10s Mar 7, 2024
bc171eb
move 'future' code to a comment
r10s Mar 9, 2024
9412997
set all of account_id/chat_id/message_id for all notifications
r10s Mar 9, 2024
c858303
switch to correct account on tapping a notification; do not just dism…
r10s Mar 9, 2024
99e75ea
respect account_id when removing notifications for a chat
r10s Mar 9, 2024
91252e3
handle unspecific notifications
r10s Mar 9, 2024
45526b8
do not add images to notifications; this is easily compromising, just…
r10s Mar 10, 2024
c271aef
create explicit silenceNotification() method
r10s Mar 11, 2024
8ee77e2
Localize notification-strings
zeitschlag Mar 12, 2024
c4a1316
Enable localization for NSE
zeitschlag Mar 12, 2024
0e6b345
let NSE-fetch and MainApp run mutually exclusive
r10s Mar 12, 2024
2356834
fix references to Localizable.strings|stringsdict after rebasing (re-…
r10s Mar 20, 2024
bea2abb
remove irrelevant notification on app-start or background-fetch
r10s Mar 20, 2024
fbdb862
slight code improvements
r10s Mar 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions DcCore/DcCore/Extensions/UserDefaults+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,26 @@ public extension UserDefaults {
static var hasExtensionAttemptedToSend = "hasExtensionAttemptedToSend"
static var hasSavedKeyToKeychain = "hasSavedKeyToKeychain"
static var upgradedKeychainEntry = "upgradedKeychainEntry_"
static var mainAppRunningKey = "mainAppRunning"
static var nseFetchingKey = "nseFetching"

static var shared: UserDefaults? {
return UserDefaults(suiteName: "group.chat.delta.ios")
}

static var mainAppRunning: Bool {
return shared?.bool(forKey: mainAppRunningKey) ?? false
}

static func setMainAppRunning(_ value: Bool = true) {
shared?.setValue(value, forKey: mainAppRunningKey)
}

static var nseFetching: Bool {
return shared?.bool(forKey: nseFetchingKey) ?? false
}

static func setNseFetching(_ value: Bool = true) {
shared?.setValue(value, forKey: nseFetchingKey)
}
}
1 change: 0 additions & 1 deletion DcCore/DcCore/Helper/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@ public struct Constants {
static let deltachatImapEmailKey = "__DELTACHAT_IMAP_EMAIL_KEY__"
static let deltachatImapPasswordKey = "__DELTACHAT_IMAP_PASSWORD_KEY__"
}
public static let notificationIdentifier = "deltachat-ios-local-notifications"
}
14 changes: 14 additions & 0 deletions DcNotificationService/DcNotificationService.entitlements
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.chat.delta.ios</string>
</array>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)group.chat.delta.ios</string>
</array>
</dict>
</plist>
13 changes: 13 additions & 0 deletions DcNotificationService/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.usernotifications.service</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
</dict>
</dict>
</plist>
98 changes: 98 additions & 0 deletions DcNotificationService/NotificationService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import UserNotifications
import DcCore

class NotificationService: UNNotificationServiceExtension {
let dcAccounts = DcAccounts.shared

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
guard let bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) else { return }

if UserDefaults.mainAppRunning {
contentHandler(silenceNotification(bestAttemptContent))
return
}
UserDefaults.setNseFetching()

// as we're mixing in notifications from accounts without PUSH and we cannot add multiple notifications,
// it is best to move everything to the same thread - and set just no threadIdentifier

dcAccounts.openDatabase(writeable: false)
let eventEmitter = dcAccounts.getEventEmitter()

if !dcAccounts.backgroundFetch(timeout: 25) {
UserDefaults.setNseFetching(false)
contentHandler(bestAttemptContent)
return
}
UserDefaults.setNseFetching(false)

var messageCount = 0
var uniqueChats: [String: String] = [:]
while true {
guard let event = eventEmitter.getNextEvent() else { break }
if event.id == DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE { break }
if event.id == DC_EVENT_INCOMING_MSG {
let dcContext = dcAccounts.get(id: event.accountId)
let chat = dcContext.getChat(chatId: event.data1Int)
if !UserDefaults.standard.bool(forKey: "notifications_disabled") && !chat.isMuted {
let msg = dcContext.getMessage(id: event.data2Int)
let sender = msg.getSenderName(dcContext.getContact(id: msg.fromContactId))
bestAttemptContent.title = chat.isGroup ? chat.name : sender
bestAttemptContent.body = (chat.isGroup ? "\(sender): " : "") + (msg.summary(chars: 80) ?? "")
bestAttemptContent.userInfo["account_id"] = dcContext.id
bestAttemptContent.userInfo["chat_id"] = chat.id
bestAttemptContent.userInfo["message_id"] = msg.id

uniqueChats["\(dcContext.id)-\(chat.id)"] = bestAttemptContent.title
messageCount += 1
}
}
}

if messageCount == 0 {
dcAccounts.closeDatabase()
contentHandler(silenceNotification(bestAttemptContent))
} else {
bestAttemptContent.badge = dcAccounts.getFreshMessageCount() as NSNumber
dcAccounts.closeDatabase()
if messageCount > 1 {
bestAttemptContent.userInfo["message_id"] = nil
if uniqueChats.count == 1 {
bestAttemptContent.body = String.localized(stringID: "n_messages", parameter: messageCount)
} else {
bestAttemptContent.userInfo["open_as_overview"] = true // leaving chat_id as is removes the notification when one of the chats is opened (does not matter which)
bestAttemptContent.title = uniqueChats.values.joined(separator: ", ")
bestAttemptContent.body = String.localized(stringID: "n_messages_in_m_chats", parameter: messageCount, uniqueChats.count)
}
}
if #available(iOS 15.0, *) {
bestAttemptContent.relevanceScore = 1.0
}
UserDefaults.shared?.set(true, forKey: UserDefaults.hasExtensionAttemptedToSend) // force UI updates in case app was suspended
contentHandler(bestAttemptContent)
}
}

override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.

// For Delta Chat, it is just fine to do nothing - assume eg. bad network or mail servers not reachable,
// then a "You have new messages" is the best that can be done.
UserDefaults.setNseFetching(false)
}

private func silenceNotification(_ bestAttemptContent: UNMutableNotificationContent) -> UNMutableNotificationContent {
// with `com.apple.developer.usernotifications.filtering` entitlement,
// one can use `contentHandler(UNMutableNotificationContent())` to not display a notifcation
// and remove all "irrelevant" handling
bestAttemptContent.sound = nil
bestAttemptContent.body = String.localized("videochat_tap_to_open")
bestAttemptContent.userInfo["irrelevant"] = true
if #available(iOS 15.0, *) {
bestAttemptContent.interruptionLevel = .passive
bestAttemptContent.relevanceScore = 0.0
}
return bestAttemptContent
}
}
1 change: 1 addition & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ in Xcode:
b) increase the build number in the same dialog
c) navigate to "DcShare / Build Settings / Versioning"
and adapt "Marketing Version" and "Current Project Version"
d) same for "DcNotificationService"

6. a) select "Any iOS Device (arm64)" in the toolbar
b) select menu "Product/Archive"
Expand Down
Loading