Skip to content

Commit

Permalink
Migrate unread count observation to Swift
Browse files Browse the repository at this point in the history
  • Loading branch information
joelekstrom committed Jan 29, 2022
1 parent 884477d commit 18e8ebf
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 186 deletions.
10 changes: 4 additions & 6 deletions Fastmate.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
5D6E3B3B212824AF00ED16C9 /* Fastmate.js in Resources */ = {isa = PBXBuildFile; fileRef = 5D6E3B3A212824AF00ED16C9 /* Fastmate.js */; };
5D8DDE2A244399BD00747135 /* KVOBlockObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D8DDE29244399BD00747135 /* KVOBlockObserver.m */; };
5DA21A162128617900C765BF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5DA21A142128617900C765BF /* Main.storyboard */; };
5DA21A19212865F000C765BF /* UnreadCountObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA21A18212865F000C765BF /* UnreadCountObserver.m */; };
5DC2AF2D225933BE004C94E5 /* VersionChecker.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DC2AF2C225933BE004C94E5 /* VersionChecker.m */; };
5DC5E5952457F96D00C30171 /* NotificationCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DC5E5942457F96D00C30171 /* NotificationCenter.m */; };
5DCFE8792258CEC5006B1A21 /* SettingsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DCFE8782258CEC5006B1A21 /* SettingsViewController.m */; };
5DF817F52790B9B700D765E4 /* UnreadCount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF817F42790B9B700D765E4 /* UnreadCount.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -46,14 +46,13 @@
5D8DDE29244399BD00747135 /* KVOBlockObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KVOBlockObserver.m; sourceTree = "<group>"; };
5D8DDE2B2443AD4800747135 /* UserDefaultsKeys.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserDefaultsKeys.h; sourceTree = "<group>"; };
5DA21A152128617900C765BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
5DA21A17212865F000C765BF /* UnreadCountObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UnreadCountObserver.h; sourceTree = "<group>"; };
5DA21A18212865F000C765BF /* UnreadCountObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UnreadCountObserver.m; sourceTree = "<group>"; };
5DC2AF2B225933BE004C94E5 /* VersionChecker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VersionChecker.h; sourceTree = "<group>"; };
5DC2AF2C225933BE004C94E5 /* VersionChecker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VersionChecker.m; sourceTree = "<group>"; };
5DC5E5932457F96D00C30171 /* NotificationCenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationCenter.h; sourceTree = "<group>"; };
5DC5E5942457F96D00C30171 /* NotificationCenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NotificationCenter.m; sourceTree = "<group>"; };
5DCFE8772258CEC5006B1A21 /* SettingsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SettingsViewController.h; sourceTree = "<group>"; };
5DCFE8782258CEC5006B1A21 /* SettingsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SettingsViewController.m; sourceTree = "<group>"; };
5DF817F42790B9B700D765E4 /* UnreadCount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadCount.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -99,12 +98,11 @@
5D1DDBD127897C4D00244F04 /* AppDelegate.swift */,
5D1DDBD3278B718000244F04 /* UserDefaults.swift */,
5D1DDBD5278C483400244F04 /* MainWindowController.swift */,
5DF817F42790B9B700D765E4 /* UnreadCount.swift */,
5D6E3B352122FF9E00ED16C9 /* WebViewController.h */,
5D6E3B362122FF9E00ED16C9 /* WebViewController.m */,
5D0F8E93240B910A00287BD1 /* PrintManager.h */,
5D0F8E94240B910A00287BD1 /* PrintManager.m */,
5DA21A17212865F000C765BF /* UnreadCountObserver.h */,
5DA21A18212865F000C765BF /* UnreadCountObserver.m */,
5DCFE8772258CEC5006B1A21 /* SettingsViewController.h */,
5DCFE8782258CEC5006B1A21 /* SettingsViewController.m */,
5DC2AF2B225933BE004C94E5 /* VersionChecker.h */,
Expand Down Expand Up @@ -202,12 +200,12 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5DF817F52790B9B700D765E4 /* UnreadCount.swift in Sources */,
5D1DDBD227897C4D00244F04 /* AppDelegate.swift in Sources */,
5D8DDE2A244399BD00747135 /* KVOBlockObserver.m in Sources */,
5D1DDBD6278C483400244F04 /* MainWindowController.swift in Sources */,
5DC5E5952457F96D00C30171 /* NotificationCenter.m in Sources */,
5D1DDBD4278B718000244F04 /* UserDefaults.swift in Sources */,
5DA21A19212865F000C765BF /* UnreadCountObserver.m in Sources */,
5D39BEE32122383900693D7E /* main.m in Sources */,
5DCFE8792258CEC5006B1A21 /* SettingsViewController.m in Sources */,
5D6E3B372122FF9E00ED16C9 /* WebViewController.m in Sources */,
Expand Down
55 changes: 42 additions & 13 deletions Fastmate/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
private var isAutomaticUpdateCheck = false
private var subscriptions = Set<AnyCancellable>()

lazy var unreadCountObserver = UnreadCountObserver()
@Published var mainWebViewController: WebViewController?

@objc var mainWebViewController: WebViewController? {
didSet {
unreadCountObserver.webViewController = mainWebViewController
func applicationDidFinishLaunching(_ notification: Notification) {
UserDefaults.standard.registerFastmateDefaults()

FastmateNotificationCenter.sharedInstance().delegate = self
FastmateNotificationCenter.sharedInstance().registerForNotifications()

DispatchQueue.global().async {
self.createUserScriptsFolderIfNeeded()
}
}

func applicationDidFinishLaunching(_ notification: Notification) {
NSWorkspace.shared.notificationCenter.publisher(for: NSWorkspace.didWakeNotification, object: nil)
.sink { _ in self.mainWebViewController?.reload() }
.store(in: &subscriptions)
Expand All @@ -35,14 +38,18 @@ class AppDelegate: NSObject, NSApplicationDelegate {
.assign(to: \.statusItemVisible, on: self)
.store(in: &subscriptions)

DispatchQueue.global().async {
self.createUserScriptsFolderIfNeeded()
}
let unreadCountPublisher = $mainWebViewController
.compactMap(\.?.unreadCountPublisher)
.switchToLatest()
.share()

FastmateNotificationCenter.sharedInstance().delegate = self
FastmateNotificationCenter.sharedInstance().registerForNotifications()
dockBadgeLabelPublisher(with: unreadCountPublisher.eraseToAnyPublisher())
.assign(to: \.badgeLabel, on: NSApplication.shared.dockTile)
.store(in: &subscriptions)

UserDefaults.standard.registerFastmateDefaults()
statusItemImagePublisher(with: unreadCountPublisher.eraseToAnyPublisher())
.sink { self.statusItem?.button?.image = $0 }
.store(in: &subscriptions)
}

func applicationDidBecomeActive(_ notification: Notification) {
Expand All @@ -56,7 +63,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
self.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
self.statusItem?.button?.target = self
self.statusItem?.button?.action = #selector(statusItemSelected(sender:))
self.unreadCountObserver.statusItem = self.statusItem
} else if let item = statusItem {
NSStatusBar.system.removeStatusItem(item)
}
Expand Down Expand Up @@ -102,6 +108,29 @@ class AppDelegate: NSObject, NSApplicationDelegate {
mainWebViewController?.handleMailtoURL(url)
}
}

func dockBadgeLabelPublisher(with unreadCount: AnyPublisher<Int, Never>) -> AnyPublisher<String?, Never> {
unreadCount
.combineLatest(
UserDefaults.standard.publisher(for: \.shouldShowUnreadMailInDock),
UserDefaults.standard.publisher(for: \.shouldShowUnreadMailCountInDock)
).map { count, shouldShowBadge, shouldShowCountInBadge in
guard count > 0, shouldShowBadge else { return nil }
return shouldShowCountInBadge ? String(count) : " "
}.eraseToAnyPublisher()
}

func statusItemImagePublisher(with unreadCount: AnyPublisher<Int, Never>) -> AnyPublisher<NSImage?, Never> {
unreadCount
.map { $0 > 0 }
.combineLatest(
UserDefaults.standard.publisher(for: \.shouldShowUnreadMailInStatusBar),
UserDefaults.standard.publisher(for: \.shouldShowUnreadMailIndicator)
)
.map { $0 && $1 && $2 ? "status-bar-unread" : "status-bar" }
.map(NSImage.init(imageLiteralResourceName:))
.eraseToAnyPublisher()
}
}

extension AppDelegate: FastmateNotificationCenterDelegate {
Expand Down
1 change: 0 additions & 1 deletion Fastmate/Fastmate-Bridging-Header.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#import "WebViewController.h"
#import "KVOBlockObserver.h"
#import "NotificationCenter.h"
#import "UnreadCountObserver.h"
#import "VersionChecker.h"
#import "PrintManager.h"
2 changes: 1 addition & 1 deletion Fastmate/MainWindowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class MainWindowController: NSWindowController, NSWindowDelegate {
return
}

webViewController.webView.publisher(for: \.title)
webViewController.publisher(for: \.webView?.title)
.replaceNil(with: "Fastmate")
.assign(to: \.title, on: window)
.store(in: &subscriptions)
Expand Down
55 changes: 55 additions & 0 deletions Fastmate/UnreadCount.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import Combine

private extension Dictionary where Key == String, Value == NSNumber {
// Extracts total unread count from a dictionary with folder name -> count
var unreadCount: Int {
mapValues(\.intValue)
.reduce(0, { $0 + $1.value })
}
}

private extension String {
// Extracts unread count from a Fastmail web view title
var unreadCount: Int {
let regex = try! NSRegularExpression(pattern: "^(\\d+) •", options: .anchorsMatchLines)
let result = regex.firstMatch(in: self, options: [], range: NSRange(location: 0, length: self.count))
if let result = result, result.numberOfRanges > 1 {
let range = Range(result.range(at: 1), in: self)!
let countString = self[range]
return Int(String(countString)) ?? 0
}
return 0
}
}

extension WebViewController {
var unreadCountPublisher: AnyPublisher<Int, Never> {
let titleCount = publisher(for: \.webView?.title)
.map(\.?.unreadCount)
.replaceNil(with: 0)

let allFoldersCount = publisher(for: \.mailboxes)
.map(\.unreadCount)

let watchedFolders = UserDefaults.standard.publisher(for: \.watchedFolders)
.map { $0.split(separator: ",").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }}

let specificFoldersCount = publisher(for: \.mailboxes)
.combineLatest(watchedFolders)
.map { mailboxes, watchedFolders in
mailboxes.filter { title, _ in watchedFolders.contains(title) }
}
.map(\.unreadCount)

return UserDefaults.standard.publisher(for: \.watchedFolderType)
.map { type -> AnyPublisher<Int, Never> in
switch type {
case .selected: return titleCount.eraseToAnyPublisher()
case .all: return allFoldersCount.eraseToAnyPublisher()
case .specific: return specificFoldersCount.eraseToAnyPublisher()
}
}
.switchToLatest()
.eraseToAnyPublisher()
}
}
9 changes: 0 additions & 9 deletions Fastmate/UnreadCountObserver.h

This file was deleted.

151 changes: 0 additions & 151 deletions Fastmate/UnreadCountObserver.m

This file was deleted.

4 changes: 2 additions & 2 deletions Fastmate/UserDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extension UserDefaults {
#keyPath(shouldShowUnreadMailInStatusBar): true,
#keyPath(shouldUseFastmailBeta): false,
#keyPath(shouldUseTransparentTitleBar): true,
#keyPath(watchedFolderType): WatchedFolderType.default.rawValue,
#keyPath(watchedFolderType): WatchedFolderType.selected.rawValue,
#keyPath(watchedFolders): "",
])
}
Expand Down Expand Up @@ -52,7 +52,7 @@ extension UserDefaults {
}

@objc enum WatchedFolderType: UInt {
case `default`
case selected
case all
case specific
}
Loading

0 comments on commit 18e8ebf

Please sign in to comment.