ios: notification badge count (#822)

This commit is contained in:
Evgeny Poberezkin
2022-07-20 08:58:53 +01:00
committed by GitHub
parent add82d73fa
commit 252897d0ff
6 changed files with 58 additions and 14 deletions
+11
View File
@@ -127,6 +127,7 @@ final class ChatModel: ObservableObject {
addChat(Chat(c), at: i)
}
}
NtfManager.shared.setNtfBadgeCount(totalUnreadCount())
}
// func addGroup(_ group: SimpleXChat.Group) {
@@ -139,6 +140,7 @@ final class ChatModel: ObservableObject {
chats[i].chatItems = [cItem]
if case .rcvNew = cItem.meta.itemStatus {
chats[i].chatStats.unreadCount = chats[i].chatStats.unreadCount + 1
NtfManager.shared.incNtfBadgeCount()
}
if i > 0 {
if chatId == nil {
@@ -203,6 +205,9 @@ final class ChatModel: ObservableObject {
// remove from current chat
if chatId == cInfo.id {
if let i = chatItems.firstIndex(where: { $0.id == cItem.id }) {
if chatItems[i].isRcvNew() == true {
NtfManager.shared.decNtfBadgeCount()
}
_ = withAnimation {
self.chatItems.remove(at: i)
}
@@ -213,6 +218,7 @@ final class ChatModel: ObservableObject {
func markChatItemsRead(_ cInfo: ChatInfo) {
// update preview
if let chat = getChat(cInfo.id) {
NtfManager.shared.decNtfBadgeCount(by: chat.chatStats.unreadCount)
chat.chatStats = ChatStats()
}
// update current chat
@@ -230,6 +236,7 @@ final class ChatModel: ObservableObject {
func clearChat(_ cInfo: ChatInfo) {
// clear preview
if let chat = getChat(cInfo.id) {
NtfManager.shared.decNtfBadgeCount(by: chat.chatStats.unreadCount)
chat.chatItems = []
chat.chatStats = ChatStats()
chat.chatInfo = cInfo
@@ -251,6 +258,10 @@ final class ChatModel: ObservableObject {
}
}
func totalUnreadCount() -> Int {
chats.reduce(0, { count, chat in count + chat.chatStats.unreadCount })
}
func getPrevChatItem(_ ci: ChatItem) -> ChatItem? {
if let i = chatItems.firstIndex(where: { $0.id == ci.id }), i > 0 {
return chatItems[i - 1]
+13
View File
@@ -188,6 +188,19 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
addNotification(createCallInvitationNtf(invitation))
}
func setNtfBadgeCount(_ count: Int) {
UIApplication.shared.applicationIconBadgeNumber = count
ntfBadgeCountGroupDefault.set(count)
}
func decNtfBadgeCount(by count: Int = 1) {
setNtfBadgeCount(max(0, UIApplication.shared.applicationIconBadgeNumber - count))
}
func incNtfBadgeCount(by count: Int = 1) {
setNtfBadgeCount(UIApplication.shared.applicationIconBadgeNumber + count)
}
private func addNotification(_ content: UNMutableNotificationContent) {
if !granted { return }
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: ntfTimeInterval, repeats: false)
+1
View File
@@ -594,6 +594,7 @@ func startChat() throws {
m.userSMPServers = try getUserSMPServers()
let chats = try apiGetChats()
m.chats = chats.map { Chat.init($0) }
NtfManager.shared.setNtfBadgeCount(m.totalUnreadCount())
try refreshCallInvitations()
(m.savedToken, m.tokenStatus, m.notificationMode) = apiGetNtfToken()
if let token = m.deviceToken {
+15 -7
View File
@@ -16,11 +16,15 @@ let suspendingDelay: UInt64 = 2_000_000_000
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNNotificationContent?
var bestAttemptContent: UNMutableNotificationContent?
var badgeCount: Int = 0
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
logger.debug("NotificationService.didReceive")
bestAttemptContent = request.content
bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent
badgeCount = ntfBadgeCountGroupDefault.get() + 1
ntfBadgeCountGroupDefault.set(badgeCount)
bestAttemptContent?.badge = badgeCount as NSNumber
self.contentHandler = contentHandler
let appState = appStateGroupDefault.get()
switch appState {
@@ -39,20 +43,22 @@ class NotificationService: UNNotificationServiceExtension {
logger.debug("NotificationService: app state is \(state.rawValue, privacy: .public)")
if state.inactive {
receiveNtfMessages(request, contentHandler)
} else {
contentHandler(request.content)
} else if let content = bestAttemptContent {
contentHandler(content)
}
}
default:
logger.debug("NotificationService: app state is \(appState.rawValue, privacy: .public)")
contentHandler(request.content)
if let content = bestAttemptContent {
contentHandler(content)
}
}
}
func receiveNtfMessages(_ request: UNNotificationRequest, _ contentHandler: @escaping (UNNotificationContent) -> Void) {
logger.debug("NotificationService: receiveNtfMessages")
if case .documents = dbContainerGroupDefault.get() {
contentHandler(request.content)
if let content = bestAttemptContent { contentHandler(content) }
return
}
let userInfo = request.content.userInfo
@@ -65,9 +71,11 @@ class NotificationService: UNNotificationServiceExtension {
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfMsgInfo), privacy: .public)")
if let connEntity = ntfMsgInfo.connEntity {
bestAttemptContent = createConnectionEventNtf(connEntity)
bestAttemptContent?.badge = badgeCount as NSNumber
}
if let content = receiveMessageForNotification() {
logger.debug("NotificationService: receiveMessageForNotification: has message")
content.badge = badgeCount as NSNumber
contentHandler(content)
} else if let content = bestAttemptContent {
logger.debug("NotificationService: receiveMessageForNotification: no message")
@@ -105,7 +113,7 @@ func startChat() -> User? {
return nil
}
func receiveMessageForNotification() -> UNNotificationContent? {
func receiveMessageForNotification() -> UNMutableNotificationContent? {
logger.debug("NotificationService receiveMessages started")
while true {
if let res = recvSimpleXMsg() {
+17 -6
View File
@@ -14,6 +14,7 @@ let GROUP_DEFAULT_DB_CONTAINER = "dbContainer"
public let GROUP_DEFAULT_CHAT_LAST_START = "chatLastStart"
let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode"
let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages"
let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount"
let APP_GROUP_NAME = "group.chat.simplex.app"
@@ -62,6 +63,8 @@ public let ntfPreviewModeGroupDefault = EnumDefault<NotificationPreviewMode>(
public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES)
public let ntfBadgeCountGroupDefault = IntDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_BADGE_COUNT)
public class DateDefault {
var defaults: UserDefaults
var key: String
@@ -107,7 +110,19 @@ public class EnumDefault<T: RawRepresentable> where T.RawValue == String {
}
}
public class BoolDefault {
public class BoolDefault: Default<Bool> {
public func get() -> Bool {
self.defaults.bool(forKey: self.key)
}
}
public class IntDefault: Default<Int> {
public func get() -> Int {
self.defaults.integer(forKey: self.key)
}
}
public class Default<T> {
var defaults: UserDefaults
var key: String
@@ -116,11 +131,7 @@ public class BoolDefault {
self.key = forKey
}
public func get() -> Bool {
defaults.bool(forKey: key)
}
public func set(_ value: Bool) {
public func set(_ value: T) {
defaults.set(value, forKey: key)
defaults.synchronize()
}
+1 -1
View File
@@ -702,7 +702,7 @@ processChatCommand = \case
APIJoinGroup groupId -> withUser $ \user@User {userId} -> do
ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} <- withStore $ \db -> getGroupInvitation db user groupId
withChatLock . procCmd $ do
agentConnId <- withAgent $ \a -> joinConnection a connRequest . directMessage . XGrpAcpt $ memberId (membership:: GroupMember)
agentConnId <- withAgent $ \a -> joinConnection a connRequest . directMessage . XGrpAcpt $ memberId (membership :: GroupMember)
withStore' $ \db -> do
createMemberConnection db userId fromMember agentConnId
updateGroupMemberStatus db userId fromMember GSMemAccepted