Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 28 additions & 16 deletions Loop.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Loop/DefaultAssets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "notification-permissions-on.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion Loop/Extensions/SettingsStore+SimulatedCoreData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ fileprivate extension StoredSettings {
providesAppNotificationSettings: true,
announcementSetting: .enabled,
timeSensitiveSetting: .enabled,
scheduledDeliverySetting: .disabled)
scheduledDeliverySetting: .disabled,
temporaryMuteAlertsSetting: .disabled)
let controllerDevice = StoredSettings.ControllerDevice(name: "Controller Name",
systemName: "Controller System Name",
systemVersion: "Controller System Version",
Expand Down
128 changes: 128 additions & 0 deletions Loop/Managers/AlertMuter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//
// AlertMuter.swift
// Loop
//
// Created by Nathaniel Hamming on 2022-09-14.
// Copyright © 2022 LoopKit Authors. All rights reserved.
//

import Foundation
import Combine
import SwiftUI
import LoopKit

public class AlertMuter: ObservableObject {
struct Configuration: Equatable, RawRepresentable {
typealias RawValue = [String: Any]

enum ConfigurationKey: String {
case duration
case startTime
}

init?(rawValue: [String : Any]) {
guard let duration = rawValue[ConfigurationKey.duration.rawValue] as? TimeInterval
else { return nil }

self.duration = duration
self.startTime = rawValue[ConfigurationKey.startTime.rawValue] as? Date
}

var rawValue: [String : Any] {
var rawValue: [String : Any] = [:]
rawValue[ConfigurationKey.duration.rawValue] = duration
rawValue[ConfigurationKey.startTime.rawValue] = startTime
return rawValue
}

var duration: TimeInterval

var startTime: Date?

var shouldMute: Bool {
guard let mutingEndTime = mutingEndTime else { return false }
return mutingEndTime >= Date()
}

var mutingEndTime: Date? {
startTime?.addingTimeInterval(duration)
}

init(startTime: Date? = nil, duration: TimeInterval = AlertMuter.allowedDurations[0]) {
self.duration = duration
self.startTime = startTime
}

func shouldMuteAlert(scheduledAt timeFromNow: TimeInterval = 0, now: Date = Date()) -> Bool {
guard timeFromNow >= 0 else { return false }

guard let mutingEndTime = mutingEndTime else { return false }

let alertTriggerTime = now.advanced(by: timeFromNow)
guard alertTriggerTime < mutingEndTime
else { return false }

return true
}
}

@Published var configuration: Configuration {
didSet {
if oldValue != configuration {
updateMutePeriodEndingWatcher()
}
}
}

private var mutePeriodEndingTimer: Timer?

private lazy var cancellables = Set<AnyCancellable>()

static var allowedDurations: [TimeInterval] { [.minutes(30), .hours(1), .hours(2), .hours(4)] }

init(configuration: Configuration = Configuration()) {
self.configuration = configuration

NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.updateMutePeriodEndingWatcher()
}
.store(in: &cancellables)

updateMutePeriodEndingWatcher()
}

convenience init(startTime: Date? = nil, duration: TimeInterval = AlertMuter.allowedDurations[0]) {
self.init(configuration: Configuration(startTime: startTime, duration: duration))
}

private func updateMutePeriodEndingWatcher(_ now: Date = Date()) {
mutePeriodEndingTimer?.invalidate()

guard let mutingEndTime = configuration.mutingEndTime else { return }

guard mutingEndTime > now else {
configuration.startTime = nil
return
}

let timeInterval = mutingEndTime.timeIntervalSince(now)
mutePeriodEndingTimer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { [weak self] _ in
self?.configuration.startTime = nil
}
}

func shouldMuteAlert(scheduledAt timeFromNow: TimeInterval = 0) -> Bool {
return configuration.shouldMuteAlert(scheduledAt: timeFromNow)
}

func shouldMuteAlert(_ alert: LoopKit.Alert, issuedDate: Date? = nil, now: Date = Date()) -> Bool {
switch alert.trigger {
case .immediate:
return shouldMuteAlert(scheduledAt: (issuedDate ?? now).timeIntervalSince(now))
case .delayed(let interval), .repeating(let interval):
let triggerInterval = ((issuedDate ?? now) + interval).timeIntervalSince(now)
return shouldMuteAlert(scheduledAt: triggerInterval)
}
}
}
Loading