Преглед изворни кода

Restore Speak BG for Bluetooth and Improve Announcement Logic

Jonas Björkert пре 1 година
родитељ
комит
4c565f2e52
2 измењених фајлова са 43 додато и 22 уклоњено
  1. 20 19
      LoopFollow/Controllers/SpeakBG.swift
  2. 23 3
      LoopFollow/ViewControllers/MainViewController.swift

+ 20 - 19
LoopFollow/Controllers/SpeakBG.swift

@@ -121,37 +121,38 @@ extension MainViewController {
     }
     }
 
 
     // Speaks the current blood glucose value and the change from the previous value.
     // Speaks the current blood glucose value and the change from the previous value.
-    // Repeated calls to the function within 30 seconds are prevented.
     func speakBG(currentValue: Int, previousValue: Int) {
     func speakBG(currentValue: Int, previousValue: Int) {
+        // 1. Check if there's a new, unspoken BG value
+        guard let lastBG = bgData.last else {
+            // No data, so nothing to speak.
+            return
+        }
+
+        // Compare the timestamp of the latest BG reading with the last one we spoke.
+        guard lastBG.date > lastSpokenBGDate else {
+            // The latest value has already been spoken, so we do nothing.
+            return
+        }
+
+        // 2. Now that we know we need to speak, activate the audio session.
         let audioSession = AVAudioSession.sharedInstance()
         let audioSession = AVAudioSession.sharedInstance()
         do {
         do {
-            try audioSession.setCategory(.playback, mode: .default)
+            try audioSession.setCategory(.playback, mode: .default, options: .duckOthers)
             try audioSession.setActive(true)
             try audioSession.setActive(true)
         } catch {
         } catch {
-            LogManager.shared.log(category: .alarm, message: "speakBG, Failed to set up audio session: \(error)")
-        }
-
-        // Get the current time
-        let currentTime = Date()
-
-        // Check if speakBG was called less than 30 seconds ago. If so, prevent repeated announcements and return.
-        // If `lastSpeechTime` is `nil` (i.e., this is the first time `speakBG` is being called), use `Date.distantPast` as the default
-        // value to ensure that the `guard` statement passes and the announcement is made.
-        guard currentTime.timeIntervalSince(lastSpeechTime ?? .distantPast) >= 30 else {
-            LogManager.shared.log(category: .general, message: "Repeated calls to speakBG detected!", isDebug: true)
+            LogManager.shared.log(category: .general, message: "speakBG, Failed to set up audio session: \(error)")
             return
             return
         }
         }
 
 
-        // Update the last speech time
-        lastSpeechTime = currentTime
+        // 3. Mark this BG value as spoken
+        // This prevents race conditions where another call might try to speak the same value.
+        lastSpokenBGDate = lastBG.date
 
 
+        // 4. Generate announcement text.
         let bloodGlucoseDifference = currentValue - previousValue
         let bloodGlucoseDifference = currentValue - previousValue
-
         let preferredLanguage = Storage.shared.speakLanguage.value
         let preferredLanguage = Storage.shared.speakLanguage.value
         let voiceLanguageCode = LanguageVoiceMapping.voiceLanguageCode(forAppLanguage: preferredLanguage)
         let voiceLanguageCode = LanguageVoiceMapping.voiceLanguageCode(forAppLanguage: preferredLanguage)
-
         let texts = AnnouncementTexts.forLanguage(preferredLanguage)
         let texts = AnnouncementTexts.forLanguage(preferredLanguage)
-
         let negligibleThreshold = 3
         let negligibleThreshold = 3
         let localizedCurrentValue = Localizer.toDisplayUnits(String(currentValue)).replacingOccurrences(of: ",", with: ".")
         let localizedCurrentValue = Localizer.toDisplayUnits(String(currentValue)).replacingOccurrences(of: ",", with: ".")
         let announcementText: String
         let announcementText: String
@@ -164,9 +165,9 @@ extension MainViewController {
             announcementText = "\(texts.currentBGIs) \(localizedCurrentValue) \(directionText) \(absoluteDifference)"
             announcementText = "\(texts.currentBGIs) \(localizedCurrentValue) \(directionText) \(absoluteDifference)"
         }
         }
 
 
+        // 5. Speak.
         let speechUtterance = AVSpeechUtterance(string: announcementText)
         let speechUtterance = AVSpeechUtterance(string: announcementText)
         speechUtterance.voice = AVSpeechSynthesisVoice(language: voiceLanguageCode)
         speechUtterance.voice = AVSpeechSynthesisVoice(language: voiceLanguageCode)
-
         speechSynthesizer.speak(speechUtterance)
         speechSynthesizer.speak(speechUtterance)
     }
     }
 }
 }

+ 23 - 3
LoopFollow/ViewControllers/MainViewController.swift

@@ -103,8 +103,8 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     // calendar setup
     // calendar setup
     let store = EKEventStore()
     let store = EKEventStore()
 
 
-    // Stores the time of the last speech announcement to prevent repeated announcements.
-    var lastSpeechTime: Date?
+    // Stores the timestamp of the last BG value that was spoken.
+    var lastSpokenBGDate: TimeInterval = 0
 
 
     var autoScrollPauseUntil: Date? = nil
     var autoScrollPauseUntil: Date? = nil
 
 
@@ -274,6 +274,8 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             .store(in: &cancellables)
             .store(in: &cancellables)
 
 
         updateQuickActions()
         updateQuickActions()
+
+        speechSynthesizer.delegate = self
     }
     }
 
 
     // Update the Home Screen Quick Action for toggling the "Speak BG" feature based on the current speakBG setting.
     // Update the Home Screen Quick Action for toggling the "Speak BG" feature based on the current speakBG setting.
@@ -327,7 +329,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         currentCage = nil
         currentCage = nil
         currentSage = nil
         currentSage = nil
         currentIage = nil
         currentIage = nil
-        lastSpeechTime = nil
+        lastSpokenBGDate = 0
         refreshControl.endRefreshing()
         refreshControl.endRefreshing()
     }
     }
 
 
@@ -736,3 +738,21 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         Storage.shared.infoVisible.value = visibleArray
         Storage.shared.infoVisible.value = visibleArray
     }
     }
 }
 }
+
+extension MainViewController: AVSpeechSynthesizerDelegate {
+    func speechSynthesizer(_: AVSpeechSynthesizer, didFinish _: AVSpeechUtterance) {
+        let appState = UIApplication.shared.applicationState
+        let isSilentTuneMode = Storage.shared.backgroundRefreshType.value == .silentTune
+
+        if isSilentTuneMode, appState == .background {
+            LogManager.shared.log(category: .general, message: "Silent tune active in background; not deactivating session.", isDebug: true)
+        } else {
+            do {
+                try AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
+                LogManager.shared.log(category: .general, message: "Audio session deactivated after speech.", isDebug: true)
+            } catch {
+                LogManager.shared.log(category: .alarm, message: "Failed to deactivate audio session: \(error)")
+            }
+        }
+    }
+}