mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Working Focus mode - focus type not yet supported
This commit is contained in:
parent
4444df4cb4
commit
b643f1d905
3 changed files with 95 additions and 29 deletions
|
|
@ -89,10 +89,16 @@ struct DoNotDisturbLiveActivity: View {
|
||||||
private var labelText: String {
|
private var labelText: String {
|
||||||
let trimmed = manager.currentFocusModeName.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmed = manager.currentFocusModeName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
if !trimmed.isEmpty {
|
if !trimmed.isEmpty {
|
||||||
|
if focusMode == .doNotDisturb {
|
||||||
|
return "Focus"
|
||||||
|
}
|
||||||
return trimmed
|
return trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
let fallback = focusMode.displayName
|
let fallback = focusMode.displayName
|
||||||
|
if focusMode == .doNotDisturb {
|
||||||
|
return "Focus"
|
||||||
|
}
|
||||||
return fallback.isEmpty ? "Focus" : fallback
|
return fallback.isEmpty ? "Focus" : fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,15 @@ struct FocusIndicator: View {
|
||||||
|
|
||||||
private var accessibilityLabel: String {
|
private var accessibilityLabel: String {
|
||||||
let trimmedName = manager.currentFocusModeName.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmedName = manager.currentFocusModeName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let name = trimmedName.isEmpty ? focusMode.displayName : trimmedName
|
let baseName: String
|
||||||
return "Focus active: \(name)"
|
if trimmedName.isEmpty {
|
||||||
|
baseName = focusMode.displayName
|
||||||
|
} else {
|
||||||
|
baseName = trimmedName
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalName = focusMode == .doNotDisturb ? "Focus" : baseName
|
||||||
|
return "Focus active: \(finalName)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ final class DoNotDisturbManager: ObservableObject {
|
||||||
|
|
||||||
private let notificationCenter = DistributedNotificationCenter.default()
|
private let notificationCenter = DistributedNotificationCenter.default()
|
||||||
private let metadataExtractionQueue = DispatchQueue(label: "com.dynamicisland.focus.metadata", qos: .userInitiated)
|
private let metadataExtractionQueue = DispatchQueue(label: "com.dynamicisland.focus.metadata", qos: .userInitiated)
|
||||||
private var hasLoggedPayload = false
|
|
||||||
|
|
||||||
private init() {}
|
private init() {}
|
||||||
|
|
||||||
|
|
@ -85,14 +84,17 @@ final class DoNotDisturbManager: ObservableObject {
|
||||||
let trimmedIdentifier = metadata.identifier?.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmedIdentifier = metadata.identifier?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
let trimmedName = metadata.name?.trimmingCharacters(in: .whitespacesAndNewlines)
|
let trimmedName = metadata.name?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
|
||||||
let incomingIdentifier = (trimmedIdentifier?.isEmpty == false ? trimmedIdentifier! : self.currentFocusModeIdentifier)
|
let resolvedMode = FocusModeType.resolve(identifier: trimmedIdentifier, name: trimmedName)
|
||||||
let incomingName = (trimmedName?.isEmpty == false ? trimmedName! : self.currentFocusModeName)
|
|
||||||
|
|
||||||
let resolvedMode = FocusModeType.resolve(identifier: incomingIdentifier, name: incomingName)
|
debugPrint("[DoNotDisturbManager] Focus update -> notification: \(notification.name.rawValue) | identifier: \(trimmedIdentifier ?? "<nil>") | name: \(trimmedName ?? "<nil>") | resolved: \(resolvedMode.rawValue)")
|
||||||
|
|
||||||
debugPrint("[DoNotDisturbManager] Focus update -> identifier: \(trimmedIdentifier ?? "<nil>") (incoming: \(incomingIdentifier.isEmpty ? "<empty>" : incomingIdentifier)) | name: \(trimmedName ?? "<nil>") | resolved: \(resolvedMode.rawValue)")
|
let finalIdentifier: String
|
||||||
|
if let identifier = trimmedIdentifier, !identifier.isEmpty {
|
||||||
|
finalIdentifier = identifier
|
||||||
|
} else {
|
||||||
|
finalIdentifier = resolvedMode.rawValue
|
||||||
|
}
|
||||||
|
|
||||||
let finalIdentifier = resolvedMode.rawValue
|
|
||||||
if finalIdentifier != self.currentFocusModeIdentifier {
|
if finalIdentifier != self.currentFocusModeIdentifier {
|
||||||
self.currentFocusModeIdentifier = finalIdentifier
|
self.currentFocusModeIdentifier = finalIdentifier
|
||||||
}
|
}
|
||||||
|
|
@ -102,8 +104,8 @@ final class DoNotDisturbManager: ObservableObject {
|
||||||
finalName = name
|
finalName = name
|
||||||
} else if !resolvedMode.displayName.isEmpty {
|
} else if !resolvedMode.displayName.isEmpty {
|
||||||
finalName = resolvedMode.displayName
|
finalName = resolvedMode.displayName
|
||||||
} else if !incomingName.isEmpty {
|
} else if let identifier = trimmedIdentifier, !identifier.isEmpty {
|
||||||
finalName = incomingName
|
finalName = identifier
|
||||||
} else {
|
} else {
|
||||||
finalName = "Focus"
|
finalName = "Focus"
|
||||||
}
|
}
|
||||||
|
|
@ -124,10 +126,7 @@ final class DoNotDisturbManager: ObservableObject {
|
||||||
private func extractMetadata(from notification: Notification) -> (name: String?, identifier: String?) {
|
private func extractMetadata(from notification: Notification) -> (name: String?, identifier: String?) {
|
||||||
guard let userInfo = notification.userInfo else { return (nil, nil) }
|
guard let userInfo = notification.userInfo else { return (nil, nil) }
|
||||||
|
|
||||||
if !hasLoggedPayload {
|
debugPrint("[DoNotDisturbManager] raw focus payload -> name: \(notification.name.rawValue), object: \(String(describing: notification.object)), userInfo: \(userInfo)")
|
||||||
hasLoggedPayload = true
|
|
||||||
debugPrint("[DoNotDisturbManager] focus notification payload: \(userInfo)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let identifierKeys = [
|
let identifierKeys = [
|
||||||
"FocusModeIdentifier",
|
"FocusModeIdentifier",
|
||||||
|
|
@ -149,15 +148,8 @@ final class DoNotDisturbManager: ObservableObject {
|
||||||
"Name"
|
"Name"
|
||||||
]
|
]
|
||||||
|
|
||||||
let identifier = identifierKeys
|
let identifier = firstMatch(for: identifierKeys, in: userInfo)
|
||||||
.compactMap { userInfo[$0] as? String }
|
let name = firstMatch(for: nameKeys, in: userInfo)
|
||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
||||||
.first { !$0.isEmpty }
|
|
||||||
|
|
||||||
let name = nameKeys
|
|
||||||
.compactMap { userInfo[$0] as? String }
|
|
||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
||||||
.first { !$0.isEmpty }
|
|
||||||
|
|
||||||
return (name, identifier)
|
return (name, identifier)
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +178,7 @@ enum FocusModeType: String, CaseIterable {
|
||||||
|
|
||||||
var displayName: String {
|
var displayName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .doNotDisturb: return "DnD"
|
case .doNotDisturb: return "Do Not Disturb"
|
||||||
case .work: return "Work"
|
case .work: return "Work"
|
||||||
case .personal: return "Personal"
|
case .personal: return "Personal"
|
||||||
case .sleep: return "Sleep"
|
case .sleep: return "Sleep"
|
||||||
|
|
@ -285,18 +277,79 @@ extension FocusModeType {
|
||||||
}
|
}
|
||||||
|
|
||||||
static func resolve(identifier: String?, name: String?) -> FocusModeType {
|
static func resolve(identifier: String?, name: String?) -> FocusModeType {
|
||||||
|
let trimmedName = name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||||
|
if !trimmedName.isEmpty {
|
||||||
|
if let match = FocusModeType.allCases.first(where: {
|
||||||
|
guard !$0.displayName.isEmpty else { return false }
|
||||||
|
return $0.displayName.compare(trimmedName, options: [.caseInsensitive, .diacriticInsensitive]) == .orderedSame
|
||||||
|
}) {
|
||||||
|
return match
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let trimmedIdentifier = identifier?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
let trimmedIdentifier = identifier?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
||||||
if !trimmedIdentifier.isEmpty {
|
if !trimmedIdentifier.isEmpty {
|
||||||
return FocusModeType(identifier: trimmedIdentifier)
|
return FocusModeType(identifier: trimmedIdentifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
let trimmedName = name?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
||||||
if !trimmedName.isEmpty {
|
|
||||||
if let match = FocusModeType.allCases.first(where: { !$0.displayName.isEmpty && $0.displayName.compare(trimmedName, options: [.caseInsensitive, .diacriticInsensitive]) == .orderedSame }) {
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return .doNotDisturb
|
return .doNotDisturb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Metadata helpers
|
||||||
|
|
||||||
|
private extension DoNotDisturbManager {
|
||||||
|
func firstMatch(for keys: [String], in value: Any) -> String? {
|
||||||
|
if let dictionary = value as? [AnyHashable: Any] {
|
||||||
|
for key in keys {
|
||||||
|
if let candidate = dictionary[key], let string = normalizedString(from: candidate) {
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for nestedValue in dictionary.values {
|
||||||
|
if let nestedMatch = firstMatch(for: keys, in: nestedValue) {
|
||||||
|
return nestedMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let array = value as? [Any] {
|
||||||
|
for element in array {
|
||||||
|
if let nestedMatch = firstMatch(for: keys, in: element) {
|
||||||
|
return nestedMatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizedString(from value: Any) -> String? {
|
||||||
|
switch value {
|
||||||
|
case let string as String:
|
||||||
|
let trimmed = string.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
return trimmed.isEmpty ? nil : trimmed
|
||||||
|
case let number as NSNumber:
|
||||||
|
return number.stringValue
|
||||||
|
case let uuid as UUID:
|
||||||
|
return uuid.uuidString
|
||||||
|
case let uuid as NSUUID:
|
||||||
|
return uuid.uuidString
|
||||||
|
case let data as Data:
|
||||||
|
if let string = String(data: data, encoding: .utf8) {
|
||||||
|
return string.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case let dict as [AnyHashable: Any]:
|
||||||
|
// Attempt to pull common keys from nested dictionaries
|
||||||
|
if let nested = firstMatch(for: ["identifier", "Identifier", "uuid", "UUID"], in: dict) {
|
||||||
|
return nested
|
||||||
|
}
|
||||||
|
if let name = firstMatch(for: ["name", "Name", "displayName", "display_name"], in: dict) {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue