Skip to content
Open
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
84 changes: 44 additions & 40 deletions LoopFollow.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

45 changes: 20 additions & 25 deletions LoopFollow/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
// LoopFollow
// AppDelegate.swift

import AVFoundation
import CoreData
import EventKit
import UIKit
import UserNotifications

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let notificationCenter = UNUserNotificationCenter.current()
private let speechSynthesizer = AVSpeechSynthesizer()

func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
LogManager.shared.log(category: .general, message: "App started")
Expand Down Expand Up @@ -111,32 +112,28 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
completionHandler(.newData)
}

// MARK: - URL handling

// Note: with scene-based lifecycle (iOS 13+), URLs are delivered to
// SceneDelegate.scene(_:openURLContexts:) — not here. The scene delegate
// handles <urlScheme>://la-tap for Live Activity tap navigation.

// MARK: UISceneSession Lifecycle

func application(_: UIApplication, willFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// set the "prevent screen lock" option when the app is started
// This method doesn't seem to be working anymore. Added to view controllers as solution offered on SO
UIApplication.shared.isIdleTimerDisabled = Storage.shared.screenlockSwitchState.value

return true
}

func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
// MARK: - Quick Actions

func application(_: UIApplication, didDiscardSceneSessions _: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
func application(_: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
guard let bundleIdentifier = Bundle.main.bundleIdentifier else {
completionHandler(false)
return
}
let expectedType = bundleIdentifier + ".toggleSpeakBG"
if shortcutItem.type == expectedType {
Storage.shared.speakBG.value.toggle()
let message = Storage.shared.speakBG.value ? "BG Speak is now on" : "BG Speak is now off"
let utterance = AVSpeechUtterance(string: message)
speechSynthesizer.speak(utterance)
completionHandler(true)
} else {
completionHandler(false)
}
}

// MARK: - Core Data stack
Expand Down Expand Up @@ -186,10 +183,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
if response.actionIdentifier == "OPEN_APP_ACTION" {
if let window {
window.rootViewController?.dismiss(animated: true, completion: nil)
window.rootViewController?.present(MainViewController(), animated: true, completion: nil)
}
// Switch to Home tab
Observable.shared.selectedTabIndex.value = 0
}

if response.actionIdentifier == "snooze" {
Expand Down
456 changes: 0 additions & 456 deletions LoopFollow/Application/Base.lproj/Main.storyboard

This file was deleted.

21 changes: 21 additions & 0 deletions LoopFollow/Application/LoopFollowApp.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// LoopFollow
// LoopFollowApp.swift

import SwiftUI

@main
struct LoopFollowApp: App {
@UIApplicationDelegateAdaptor private var appDelegate: AppDelegate

var body: some Scene {
WindowGroup {
MainTabView()
.onOpenURL { url in
guard url.scheme == AppGroupID.urlScheme, url.host == "la-tap" else { return }
DispatchQueue.main.async {
NotificationCenter.default.post(name: .liveActivityDidForeground, object: nil)
}
}
}
}
}
62 changes: 62 additions & 0 deletions LoopFollow/Application/MainTabView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// LoopFollow
// MainTabView.swift

import SwiftUI

struct MainTabView: View {
@ObservedObject private var selectedTab = Observable.shared.selectedTabIndex
@ObservedObject private var homePosition = Storage.shared.homePosition
@ObservedObject private var alarmsPosition = Storage.shared.alarmsPosition
@ObservedObject private var remotePosition = Storage.shared.remotePosition
@ObservedObject private var nightscoutPosition = Storage.shared.nightscoutPosition
@ObservedObject private var snoozerPosition = Storage.shared.snoozerPosition
@ObservedObject private var statisticsPosition = Storage.shared.statisticsPosition
@ObservedObject private var treatmentsPosition = Storage.shared.treatmentsPosition

private var orderedItems: [TabItem] {
Storage.shared.orderedTabBarItems()
}

var body: some View {
TabView(selection: $selectedTab.value) {
ForEach(Array(orderedItems.prefix(4).enumerated()), id: \.element) { index, item in
tabContent(for: item)
.tabItem {
Label(item.displayName, systemImage: item.icon)
}
.tag(index)
}

NavigationStack {
MoreMenuView()
}
.tabItem {
Label("Menu", systemImage: "line.3.horizontal")
}
.tag(4)
}
.preferredColorScheme(Storage.shared.appearanceMode.value.colorScheme)
}

@ViewBuilder
private func tabContent(for item: TabItem) -> some View {
switch item {
case .home:
HomeContentView()
case .alarms:
AlarmsContainerView()
case .remote:
RemoteContentView()
case .nightscout:
NightscoutContentView()
case .snoozer:
SnoozerView()
case .treatments:
TreatmentsView()
case .stats:
NavigationStack {
AggregatedStatsContentView(mainViewController: MainViewController.shared)
}
}
}
}
83 changes: 0 additions & 83 deletions LoopFollow/Application/SceneDelegate.swift

This file was deleted.

64 changes: 10 additions & 54 deletions LoopFollow/Controllers/MainViewController+updateStats.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
// LoopFollow
// MainViewController+updateStats.swift

import Charts
import Foundation
import UIKit

extension MainViewController {
func updateStats() {
Expand All @@ -22,62 +20,20 @@ extension MainViewController {

let stats = StatsData(bgData: lastDayOfData)

statsLowPercent.text = String(format: "%.1f%%", stats.percentLow)
statsInRangePercent.text = String(format: "%.1f%%", stats.percentRange)
statsHighPercent.text = String(format: "%.1f%%", stats.percentHigh)
statsAvgBG.text = Localizer.toDisplayUnits(String(format: "%.0f", stats.avgBG))
statsDisplayModel.lowPercent = String(format: "%.1f%%", stats.percentLow)
statsDisplayModel.inRangePercent = String(format: "%.1f%%", stats.percentRange)
statsDisplayModel.highPercent = String(format: "%.1f%%", stats.percentHigh)
statsDisplayModel.avgBG = Localizer.toDisplayUnits(String(format: "%.0f", stats.avgBG))
if Storage.shared.useIFCC.value {
statsEstA1C.text = String(format: "%.0f", stats.a1C)
statsDisplayModel.estA1C = String(format: "%.0f", stats.a1C)
} else {
statsEstA1C.text = String(format: "%.1f", stats.a1C)
statsDisplayModel.estA1C = String(format: "%.1f", stats.a1C)
}
statsStdDev.text = String(format: "%.2f", stats.stdDev)
statsDisplayModel.stdDev = String(format: "%.2f", stats.stdDev)

createStatsPie(pieData: stats.pie)
statsDisplayModel.pieLow = Double(stats.percentLow)
statsDisplayModel.pieRange = Double(stats.percentRange)
statsDisplayModel.pieHigh = Double(stats.percentHigh)
}
}

fileprivate func createStatsPie(pieData: [DataStructs.pieData]) {
statsPieChart.legend.enabled = false
statsPieChart.drawEntryLabelsEnabled = false
statsPieChart.drawHoleEnabled = false
statsPieChart.rotationEnabled = false

var chartEntry = [PieChartDataEntry]()
var colors = [NSUIColor]()

for i in 0 ..< pieData.count {
var slice = Double(pieData[i].value)
if slice == 0 { slice = 0.1 }
let value = PieChartDataEntry(value: slice)
chartEntry.append(value)

if pieData[i].name == "high" {
colors.append(NSUIColor.systemYellow)
} else if pieData[i].name == "low" {
colors.append(NSUIColor.systemRed)
} else {
colors.append(NSUIColor.systemGreen)
}
}

let set = PieChartDataSet(entries: chartEntry, label: "")

set.drawIconsEnabled = false
set.sliceSpace = 2
set.drawValuesEnabled = false
set.valueLineWidth = 0
set.formLineWidth = 0
set.sliceSpace = 0

set.colors.removeAll()
if colors.count > 0 {
for i in 0 ..< colors.count {
set.addColor(colors[i])
}
}

let data = PieChartData(dataSet: set)
statsPieChart.data = data
}
}
4 changes: 2 additions & 2 deletions LoopFollow/Controllers/Nightscout/BGData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,9 @@ extension MainViewController {

func updateServerText(with serverText: String? = nil) {
if Storage.shared.showDisplayName.value, let displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String {
self.serverText.text = displayName
Observable.shared.serverText.value = displayName
} else if let serverText = serverText {
self.serverText.text = serverText
Observable.shared.serverText.value = serverText
}
}

Expand Down
Loading