diff --git a/Loop Widget Extension/Bootstrap/Bootstrap.swift b/Loop Widget Extension/Bootstrap/Bootstrap.swift
new file mode 100644
index 0000000000..00823471c1
--- /dev/null
+++ b/Loop Widget Extension/Bootstrap/Bootstrap.swift	
@@ -0,0 +1,11 @@
+//
+//  Bootstrap.swift
+//  Loop Widget Extension
+//
+//  Created by Bastiaan Verhaar on 25/06/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import Foundation
+
+class Bootstrap{}
diff --git a/Loop Widget Extension/Helpers/LocalizedString.swift b/Loop Widget Extension/Helpers/LocalizedString.swift
new file mode 100644
index 0000000000..158181755d
--- /dev/null
+++ b/Loop Widget Extension/Helpers/LocalizedString.swift	
@@ -0,0 +1,21 @@
+//
+//  LocalizedString.swift
+//  Loop Widget Extension
+//
+//  Created by Bastiaan Verhaar on 25/06/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import Foundation
+
+private class FrameworkBundle {
+    static let main = Bundle(for: Bootstrap.self)
+}
+
+func LocalizedString(_ key: String, tableName: String? = nil, value: String? = nil, comment: String) -> String {
+    if let value = value {
+        return NSLocalizedString(key, tableName: tableName, bundle: FrameworkBundle.main, value: value, comment: comment)
+    } else {
+        return NSLocalizedString(key, tableName: tableName, bundle: FrameworkBundle.main, comment: comment)
+    }
+}
diff --git a/Loop Widget Extension/Live Activity/BasalViewActivity.swift b/Loop Widget Extension/Live Activity/BasalViewActivity.swift
new file mode 100644
index 0000000000..915335c5fb
--- /dev/null
+++ b/Loop Widget Extension/Live Activity/BasalViewActivity.swift	
@@ -0,0 +1,46 @@
+//
+//  BasalView.swift
+//  Loop
+//
+//  Created by Noah Brauner on 8/15/22.
+//  Copyright © 2022 LoopKit Authors. All rights reserved.
+//
+
+import SwiftUI
+
+struct BasalViewActivity: View {
+    let percent: Double
+    let rate: Double
+    
+    var body: some View {        
+        VStack(spacing: 1) {
+            BasalRateView(percent: percent)
+                .overlay(
+                    BasalRateView(percent: percent)
+                        .stroke(Color("insulin"), lineWidth: 2)
+                )
+                .foregroundColor(Color("insulin").opacity(0.5))
+                .frame(width: 44, height: 22)
+
+            if let rateString = decimalFormatter.string(from: NSNumber(value: rate)) {
+                Text("\(rateString)U")
+                    .font(.subheadline)
+            }
+            else {
+                Text("-U")
+                    .font(.subheadline)
+            }
+        }
+    }
+    
+    private let decimalFormatter: NumberFormatter = {
+        let formatter = NumberFormatter()
+        formatter.numberStyle = .decimal
+        formatter.minimumFractionDigits = 1
+        formatter.minimumIntegerDigits = 1
+        formatter.positiveFormat = "+0.0##"
+        formatter.negativeFormat = "-0.0##"
+
+        return formatter
+    }()
+}
diff --git a/Loop Widget Extension/Live Activity/ChartView.swift b/Loop Widget Extension/Live Activity/ChartView.swift
new file mode 100644
index 0000000000..b65e02c98e
--- /dev/null
+++ b/Loop Widget Extension/Live Activity/ChartView.swift	
@@ -0,0 +1,159 @@
+//
+//  ChartValues.swift
+//  Loop Widget Extension
+//
+//  Created by Bastiaan Verhaar on 25/06/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import Foundation
+import SwiftUI
+import Charts
+
+struct ChartView: View {
+    private let glucoseSampleData: [ChartValues]
+    private let predicatedData: [ChartValues]
+    private let glucoseRanges: [GlucoseRangeValue]
+    private let preset: Preset?
+    private let yAxisMarks: [Double]
+    
+    init(glucoseSamples: [GlucoseSampleAttributes], predicatedGlucose: [Double], predicatedStartDate: Date?, predicatedInterval: TimeInterval?, useLimits: Bool, lowerLimit: Double, upperLimit: Double, glucoseRanges: [GlucoseRangeValue], preset: Preset?, yAxisMarks: [Double]) {
+        self.glucoseSampleData = ChartValues.convert(data: glucoseSamples, useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit)
+        self.predicatedData = ChartValues.convert(
+            data: predicatedGlucose,
+            startDate: predicatedStartDate ?? Date.now,
+            interval: predicatedInterval ?? .minutes(5),
+            useLimits: useLimits,
+            lowerLimit: lowerLimit,
+            upperLimit: upperLimit
+        )
+        self.preset = preset
+        self.glucoseRanges = glucoseRanges
+        self.yAxisMarks = yAxisMarks
+    }
+    
+    init(glucoseSamples: [GlucoseSampleAttributes], useLimits: Bool, lowerLimit: Double, upperLimit: Double, glucoseRanges: [GlucoseRangeValue], preset: Preset?, yAxisMarks: [Double]) {
+        self.glucoseSampleData = ChartValues.convert(data: glucoseSamples, useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit)
+        self.predicatedData = []
+        self.preset = preset
+        self.glucoseRanges = glucoseRanges
+        self.yAxisMarks = yAxisMarks
+    }
+    
+    var body: some View {
+        ZStack(alignment: Alignment(horizontal: .trailing, vertical: .top)){
+            Chart {
+                if let preset = self.preset, predicatedData.count > 0, preset.endDate > Date.now.addingTimeInterval(.hours(-6)) {
+                    RectangleMark(
+                        xStart: .value("Start", preset.startDate),
+                        xEnd: .value("End", preset.endDate),
+                        yStart: .value("Preset override", preset.minValue),
+                        yEnd: .value("Preset override", preset.maxValue)
+                    )
+                    .foregroundStyle(.primary)
+                    .opacity(0.6)
+                }
+                
+                ForEach(glucoseRanges) { item in
+                    RectangleMark(
+                        xStart: .value("Start", item.startDate),
+                        xEnd: .value("End", item.endDate),
+                        yStart: .value("Glucose range", item.minValue),
+                        yEnd: .value("Glucose range", item.maxValue)
+                    )
+                    .foregroundStyle(.primary)
+                    .opacity(0.3)
+                }
+                
+                ForEach(glucoseSampleData) { item in
+                    PointMark (x: .value("Date", item.x),
+                               y: .value("Glucose level", item.y)
+                    )
+                    .symbolSize(20)
+                    .foregroundStyle(by: .value("Color", item.color))
+                }
+                
+                ForEach(predicatedData) { item in
+                    LineMark (x: .value("Date", item.x),
+                              y: .value("Glucose level", item.y)
+                    )
+                    .lineStyle(StrokeStyle(lineWidth: 3, dash: [2, 3]))
+                }
+            }
+            .chartForegroundStyleScale([
+                "Good": .green,
+                "High": .orange,
+                "Low": .red,
+                "Default": .blue
+            ])
+            .chartPlotStyle { plotContent in
+                plotContent.background(.cyan.opacity(0.15))
+            }
+            .chartLegend(.hidden)
+            .chartYScale(domain: [yAxisMarks.first ?? 0, yAxisMarks.last ?? 0])
+            .chartYAxis {
+                AxisMarks(values: yAxisMarks)
+            }
+            .chartYAxis {
+                AxisMarks(position: .leading) { _ in
+                    AxisValueLabel().foregroundStyle(Color.primary)
+                    AxisGridLine(stroke: .init(lineWidth: 0.1, dash: [2, 3]))
+                        .foregroundStyle(Color.primary)
+                }
+            }
+            .chartXAxis {
+                AxisMarks(position: .automatic, values: .stride(by: .hour)) { _ in
+                    AxisValueLabel(format: .dateTime.hour(.twoDigits(amPM: .narrow)), anchor: .top)
+                        .foregroundStyle(Color.primary)
+                    AxisGridLine(stroke: .init(lineWidth: 0.1, dash: [2, 3]))
+                        .foregroundStyle(Color.primary)
+                }
+            }
+            
+            if let preset = self.preset, preset.endDate > Date.now {
+                Text(preset.title)
+                    .font(.footnote)
+                    .padding(.trailing, 5)
+                    .padding(.top, 2)
+            }
+        }
+    }
+}
+
+struct ChartValues: Identifiable {
+    public let id: UUID
+    public let x: Date
+    public let y: Double
+    public let color: String
+    
+    init(x: Date, y: Double, color: String) {
+        self.id = UUID()
+        self.x = x
+        self.y = y
+        self.color = color
+    }
+    
+    static func convert(data: [Double], startDate: Date, interval: TimeInterval, useLimits: Bool, lowerLimit: Double, upperLimit: Double) -> [ChartValues] {
+        let twoHours = Date.now.addingTimeInterval(.hours(4))
+        
+        return data.enumerated().filter { (index, item) in
+            return startDate.addingTimeInterval(interval * Double(index)) < twoHours
+        }.map { (index, item) in
+            return ChartValues(
+                x: startDate.addingTimeInterval(interval * Double(index)),
+                y: item,
+                color: !useLimits ? "Default" : item < lowerLimit ? "Low" : item > upperLimit ? "High" : "Good"
+            )
+        }
+    }
+    
+    static func convert(data: [GlucoseSampleAttributes], useLimits: Bool, lowerLimit: Double, upperLimit: Double) -> [ChartValues] {
+        return data.map { item in
+            return ChartValues(
+                x: item.x,
+                y: item.y,
+                color: !useLimits ? "Default" : item.y < lowerLimit ? "Low" : item.y > upperLimit ? "High" : "Good"
+            )
+        }
+    }
+}
diff --git a/Loop Widget Extension/Live Activity/GlucoseLiveActivityConfiguration.swift b/Loop Widget Extension/Live Activity/GlucoseLiveActivityConfiguration.swift
new file mode 100644
index 0000000000..4d5ed5ef3e
--- /dev/null
+++ b/Loop Widget Extension/Live Activity/GlucoseLiveActivityConfiguration.swift	
@@ -0,0 +1,298 @@
+//
+//  LiveActivityConfiguration.swift
+//  Loop Widget Extension
+//
+//  Created by Bastiaan Verhaar on 23/06/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import ActivityKit
+import LoopKit
+import SwiftUI
+import LoopCore
+import WidgetKit
+import Charts
+import HealthKit
+
+@available(iOS 16.2, *)
+struct GlucoseLiveActivityConfiguration: Widget {
+    private let timeFormatter: DateFormatter = {
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateStyle = .none
+        dateFormatter.timeStyle = .short
+        
+        return dateFormatter
+    }()
+    
+    var body: some WidgetConfiguration {
+        ActivityConfiguration(for: GlucoseActivityAttributes.self) { context in
+            // Create the presentation that appears on the Lock Screen and as a
+            // banner on the Home Screen of devices that don't support the Dynamic Island.
+            ZStack {
+                VStack {
+                    if context.attributes.mode == .large {
+                        HStack(spacing: 15) {
+                            loopIcon(context)
+                            if context.attributes.addPredictiveLine {
+                                ChartView(
+                                    glucoseSamples: context.state.glucoseSamples,
+                                    predicatedGlucose: context.state.predicatedGlucose,
+                                    predicatedStartDate: context.state.predicatedStartDate,
+                                    predicatedInterval: context.state.predicatedInterval,
+                                    useLimits: context.attributes.useLimits,
+                                    lowerLimit: context.state.isMmol ? context.attributes.lowerLimitChartMmol : context.attributes.lowerLimitChartMg,
+                                    upperLimit: context.state.isMmol ? context.attributes.upperLimitChartMmol : context.attributes.upperLimitChartMg,
+                                    glucoseRanges: context.state.glucoseRanges,
+                                    preset: context.state.preset,
+                                    yAxisMarks: context.state.yAxisMarks
+                                )
+                                .frame(height: 85)
+                            } else {
+                                ChartView(
+                                    glucoseSamples: context.state.glucoseSamples,
+                                    useLimits: context.attributes.useLimits,
+                                    lowerLimit: context.state.isMmol ? context.attributes.lowerLimitChartMmol : context.attributes.lowerLimitChartMg,
+                                    upperLimit: context.state.isMmol ? context.attributes.upperLimitChartMmol : context.attributes.upperLimitChartMg,
+                                    glucoseRanges: context.state.glucoseRanges,
+                                    preset: context.state.preset,
+                                    yAxisMarks: context.state.yAxisMarks
+                                )
+                                .frame(height: 85)
+                            }
+                        }
+                    }
+                    
+                    HStack {
+                        bottomSpacer(border: false)
+                        
+                        let endIndex = context.state.bottomRow.endIndex - 1
+                        ForEach(Array(context.state.bottomRow.enumerated()), id: \.element) { (index, item) in
+                            switch (item.type) {
+                            case .generic:
+                                bottomItemGeneric(
+                                    title: item.label,
+                                    value: item.value,
+                                    unit: LocalizedString(item.unit, comment: "No comment")
+                                )
+                                
+                            case .basal:
+                                BasalViewActivity(percent: item.percentage, rate: item.rate)
+                                
+                            case .currentBg:
+                                bottomItemCurrentBG(
+                                    value: item.value,
+                                    trend: item.trend,
+                                    context: context
+                                )
+                                
+                            case .loopCircle:
+                                bottomItemLoopCircle(context: context)
+                            }
+                            
+                            if index != endIndex {
+                                bottomSpacer(border: true)
+                            }
+                        }
+                        
+                        bottomSpacer(border: false)
+                    }
+                }
+                if context.state.ended {
+                    VStack {
+                        Spacer()
+                        HStack {
+                            Spacer()
+                            Text(NSLocalizedString("Open the app to update the widget", comment: "No comment"))
+                            Spacer()
+                        }
+                        Spacer()
+                    }
+                    .background(.ultraThinMaterial.opacity(0.8))
+                    .padding(.all, -15)
+                }
+            }
+                .privacySensitive()
+                .padding(.all, 15)
+                .background(BackgroundStyle.background.opacity(0.4))
+                .activityBackgroundTint(Color.clear)
+        } dynamicIsland: { context in
+            let glucoseFormatter = NumberFormatter.glucoseFormatter(for: context.state.isMmol ? HKUnit.millimolesPerLiter : HKUnit.milligramsPerDeciliter)
+            
+            return DynamicIsland {
+                DynamicIslandExpandedRegion(.leading) {
+                    HStack(alignment: .center) {
+                        loopIcon(context)
+                            .frame(width: 40, height: 40, alignment: .trailing)
+                        Spacer()
+                        Text("\(glucoseFormatter.string(from: context.state.currentGlucose) ?? "??")\(getArrowImage(context.state.trendType))")
+                            .foregroundStyle(getGlucoseColor(context.state.currentGlucose, context: context))
+                            .font(.headline)
+                            .fontWeight(.heavy)
+                    }
+                }
+                DynamicIslandExpandedRegion(.trailing) {
+                    HStack{
+                        Text(context.state.delta)
+                            .foregroundStyle(Color(white: 0.9))
+                            .font(.headline)
+                        Text(context.state.isMmol ? HKUnit.millimolesPerLiter.localizedShortUnitString : HKUnit.milligramsPerDeciliter.localizedShortUnitString)
+                            .foregroundStyle(Color(white: 0.7))
+                            .font(.subheadline)
+                    }
+                }
+                DynamicIslandExpandedRegion(.bottom) {
+                    if context.attributes.addPredictiveLine {
+                        ChartView(
+                            glucoseSamples: context.state.glucoseSamples,
+                            predicatedGlucose: context.state.predicatedGlucose,
+                            predicatedStartDate: context.state.predicatedStartDate,
+                            predicatedInterval: context.state.predicatedInterval,
+                            useLimits: context.attributes.useLimits,
+                            lowerLimit: context.state.isMmol ? context.attributes.lowerLimitChartMmol : context.attributes.lowerLimitChartMg,
+                            upperLimit: context.state.isMmol ? context.attributes.upperLimitChartMmol : context.attributes.upperLimitChartMg,
+                            glucoseRanges: context.state.glucoseRanges,
+                            preset: context.state.preset,
+                            yAxisMarks: context.state.yAxisMarks
+                        )
+                            .frame(height: 75)
+                    } else {
+                        ChartView(
+                            glucoseSamples: context.state.glucoseSamples,
+                            useLimits: context.attributes.useLimits,
+                            lowerLimit: context.state.isMmol ? context.attributes.lowerLimitChartMmol : context.attributes.lowerLimitChartMg,
+                            upperLimit: context.state.isMmol ? context.attributes.upperLimitChartMmol : context.attributes.upperLimitChartMg,
+                            glucoseRanges: context.state.glucoseRanges,
+                            preset: context.state.preset,
+                            yAxisMarks: context.state.yAxisMarks
+                        )
+                            .frame(height: 75)
+                    }
+                }
+            } compactLeading: {
+                Text("\(glucoseFormatter.string(from: context.state.currentGlucose) ?? "??")\(getArrowImage(context.state.trendType))")
+                    .foregroundStyle(getGlucoseColor(context.state.currentGlucose, context: context))
+                    .minimumScaleFactor(0.1)
+            } compactTrailing: {
+                Text(context.state.delta)
+                    .foregroundStyle(Color(white: 0.9))
+                    .minimumScaleFactor(0.1)
+            } minimal: {
+                Text(glucoseFormatter.string(from: context.state.currentGlucose) ?? "??")
+                    .foregroundStyle(getGlucoseColor(context.state.currentGlucose, context: context))
+                    .minimumScaleFactor(0.1)
+            }
+        }
+    }
+    
+    @ViewBuilder
+    private func loopIcon(_ context: ActivityViewContext<GlucoseActivityAttributes>) -> some View {
+        Circle()
+            .trim(from: context.state.isCloseLoop ? 0 : 0.2, to: 1)
+            .stroke(getLoopColor(context.state.lastCompleted), lineWidth: 8)
+            .rotationEffect(Angle(degrees: -126))
+            .frame(width: 36, height: 36)
+    }
+    
+    @ViewBuilder
+    private func bottomItemGeneric(title: String, value: String, unit: String) -> some View {
+        VStack(alignment: .center) {
+            Text("\(value)\(unit)")
+                .font(.headline)
+                .foregroundStyle(.primary)
+                .fontWeight(.heavy)
+                .font(Font.body.leading(.tight))
+            Text(title)
+                .font(.subheadline)
+        }
+    }
+    
+    @ViewBuilder
+    private func bottomItemCurrentBG(value: String, trend: GlucoseTrend?, context: ActivityViewContext<GlucoseActivityAttributes>) -> some View {
+        VStack(alignment: .center) {
+            HStack {
+                Text(value + getArrowImage(trend))
+                    .font(.title)
+                    .foregroundStyle(!context.attributes.useLimits ? .primary : getGlucoseColor(context.state.currentGlucose, context: context))
+                    .fontWeight(.heavy)
+                    .font(Font.body.leading(.tight))
+            }
+        }
+    }
+    
+    @ViewBuilder
+    private func bottomItemLoopCircle(context: ActivityViewContext<GlucoseActivityAttributes>) -> some View {
+        VStack(alignment: .center) {
+            loopIcon(context)
+        }
+    }
+    
+    @ViewBuilder
+    private func bottomSpacer(border: Bool) -> some View {
+        Spacer()
+        if (border) {
+            Divider()
+                .background(.secondary)
+            Spacer()
+        }
+        
+    }
+    
+    private func getArrowImage(_ trendType: GlucoseTrend?) -> String {
+        switch trendType {
+        case .upUpUp:
+            return "\u{2191}\u{2191}" // ↑↑
+        case .upUp:
+            return "\u{2191}" // ↑
+        case .up:
+            return "\u{2197}" // ↗
+        case .flat:
+            return "\u{2192}" // →
+        case .down:
+            return "\u{2198}" // ↘
+        case .downDown:
+            return "\u{2193}" // ↓
+        case .downDownDown:
+            return "\u{2193}\u{2193}" // ↓↓
+        case .none:
+            return ""
+        }
+    }
+    
+    private func getLoopColor(_ age: Date?) -> Color {
+        var freshness: LoopCompletionFreshness = .stale
+        if let age = age {
+            freshness = LoopCompletionFreshness(age: abs(min(0, age.timeIntervalSinceNow)))
+        }
+        
+        switch freshness {
+        case .fresh:
+            return Color("fresh")
+        case .aging:
+            return Color("warning")
+        case .stale:
+            return .red
+        }
+    }
+    
+    private func getGlucoseColor(_ value: Double, context: ActivityViewContext<GlucoseActivityAttributes>) -> Color {
+        guard context.attributes.useLimits else {
+            return .primary
+        }
+        
+        if
+            context.state.isMmol && value < context.attributes.lowerLimitChartMmol ||
+            !context.state.isMmol && value < context.attributes.lowerLimitChartMg
+        {
+            return .red
+        }
+        
+        if
+            context.state.isMmol && value > context.attributes.upperLimitChartMmol ||
+            !context.state.isMmol && value > context.attributes.upperLimitChartMg
+        {
+            return .orange
+        }
+        
+        return .green
+    }
+}
diff --git a/Loop Widget Extension/LoopWidgets.swift b/Loop Widget Extension/LoopWidgets.swift
index 26f92edb45..684bf07355 100644
--- a/Loop Widget Extension/LoopWidgets.swift	
+++ b/Loop Widget Extension/LoopWidgets.swift	
@@ -14,5 +14,6 @@ struct LoopWidgets: WidgetBundle {
     @WidgetBundleBuilder
     var body: some Widget {
         SystemStatusWidget()
+        GlucoseLiveActivityConfiguration()
     }
 }
diff --git a/Loop.xcconfig b/Loop.xcconfig
index 9e1bb856ae..7827f574d5 100644
--- a/Loop.xcconfig
+++ b/Loop.xcconfig
@@ -43,7 +43,7 @@ LOOP_PROVISIONING_PROFILE_SPECIFIER_WATCHAPP_RELEASE =
 LOOP_PROVISIONING_PROFILE_SPECIFIER_WATCHAPP_EXTENSION_RELEASE =
 
 // Min iOS Version [DEFAULT]					
-IPHONEOS_DEPLOYMENT_TARGET = 15.1
+IPHONEOS_DEPLOYMENT_TARGET = 16.2
 
 // Base string for opening app via URL [DEFAULT]
 URL_SCHEME_NAME = $(MAIN_APP_DISPLAY_NAME)
diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj
index 1181951609..069408d7cc 100644
--- a/Loop.xcodeproj/project.pbxproj
+++ b/Loop.xcodeproj/project.pbxproj
@@ -401,6 +401,23 @@
 		B4E96D5D248A82A2002DABAD /* StatusBarHUDView.xib in Resources */ = {isa = PBXBuildFile; fileRef = B4E96D5C248A82A2002DABAD /* StatusBarHUDView.xib */; };
 		B4F3D25124AF890C0095CE44 /* BluetoothStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F3D25024AF890C0095CE44 /* BluetoothStateManager.swift */; };
 		B4FEEF7D24B8A71F00A8DF9B /* DeviceDataManager+DeviceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4FEEF7C24B8A71F00A8DF9B /* DeviceDataManager+DeviceStatus.swift */; };
+		B813C5682C9C30B400306DAF /* QuickStatsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B813C5672C9C30B400306DAF /* QuickStatsViewModel.swift */; };
+		B813C56C2C9C30DC00306DAF /* QuickStatsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B813C56B2C9C30DC00306DAF /* QuickStatsView.swift */; };
+		B82181F62C93628300478A91 /* LiveActivityManagementViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82181F52C93628300478A91 /* LiveActivityManagementViewModel.swift */; };
+		B82182002C93716A00478A91 /* ChartAxisGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B82181F92C936AD600478A91 /* ChartAxisGenerator.swift */; };
+		B851FFC52C37221800D738C1 /* LiveActivityManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B851FFC42C37221800D738C1 /* LiveActivityManagementView.swift */; };
+		B851FFCA2C3731DE00D738C1 /* LiveActivitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B851FFC62C37271E00D738C1 /* LiveActivitySettings.swift */; };
+		B851FFCB2C3731DE00D738C1 /* LiveActivitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B851FFC62C37271E00D738C1 /* LiveActivitySettings.swift */; };
+		B87539C92C2B06CE0085A975 /* LocalizedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87539C82C2B06CE0085A975 /* LocalizedString.swift */; };
+		B87539CB2C2B08430085A975 /* Bootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87539CA2C2B08430085A975 /* Bootstrap.swift */; };
+		B87539CD2C2B46950085A975 /* ChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87539CC2C2B46950085A975 /* ChartView.swift */; };
+		B87539CF2C2DCB770085A975 /* BasalViewActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87539CE2C2DCB770085A975 /* BasalViewActivity.swift */; };
+		B87D411D2C28A69600120877 /* GlucoseLiveActivityConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B87D411C2C28A69600120877 /* GlucoseLiveActivityConfiguration.swift */; };
+		B87D411F2C28A85F00120877 /* ActivityKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B87D411E2C28A85F00120877 /* ActivityKit.framework */; };
+		B8A937B82C29BA5900E38645 /* LiveActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A937B72C29BA5900E38645 /* LiveActivityManager.swift */; };
+		B8A937C32C29C3B400E38645 /* GlucoseActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A937C02C29C29300E38645 /* GlucoseActivityAttributes.swift */; };
+		B8A937C42C29C43B00E38645 /* GlucoseActivityAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8A937C02C29C29300E38645 /* GlucoseActivityAttributes.swift */; };
+		B8FD0B522C39CA3E003FB72B /* LiveActivityBottomRowManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8FD0B512C39CA3E003FB72B /* LiveActivityBottomRowManagerView.swift */; };
 		C1004DF22981F5B700B8CF94 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1004DF02981F5B700B8CF94 /* InfoPlist.strings */; };
 		C1004DF52981F5B700B8CF94 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1004DF32981F5B700B8CF94 /* Localizable.strings */; };
 		C1004DF82981F5B700B8CF94 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C1004DF62981F5B700B8CF94 /* InfoPlist.strings */; };
@@ -729,6 +746,16 @@
 			name = "Embed App Extensions";
 			runOnlyForDeploymentPostprocessing = 0;
 		};
+		B82181FF2C9370F800478A91 /* Embed Frameworks */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
 		C1E3DC4828595FAA00CA19FF /* Embed Frameworks */ = {
 			isa = PBXCopyFilesBuildPhase;
 			buildActionMask = 2147483647;
@@ -1325,6 +1352,21 @@
 		B4E96D5C248A82A2002DABAD /* StatusBarHUDView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = StatusBarHUDView.xib; sourceTree = "<group>"; };
 		B4F3D25024AF890C0095CE44 /* BluetoothStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BluetoothStateManager.swift; sourceTree = "<group>"; };
 		B4FEEF7C24B8A71F00A8DF9B /* DeviceDataManager+DeviceStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DeviceDataManager+DeviceStatus.swift"; sourceTree = "<group>"; };
+		B813C5672C9C30B400306DAF /* QuickStatsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickStatsViewModel.swift; sourceTree = "<group>"; };
+		B813C56B2C9C30DC00306DAF /* QuickStatsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickStatsView.swift; sourceTree = "<group>"; };
+		B82181F52C93628300478A91 /* LiveActivityManagementViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManagementViewModel.swift; sourceTree = "<group>"; };
+		B82181F92C936AD600478A91 /* ChartAxisGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartAxisGenerator.swift; sourceTree = "<group>"; };
+		B851FFC42C37221800D738C1 /* LiveActivityManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManagementView.swift; sourceTree = "<group>"; };
+		B851FFC62C37271E00D738C1 /* LiveActivitySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivitySettings.swift; sourceTree = "<group>"; };
+		B87539C82C2B06CE0085A975 /* LocalizedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedString.swift; sourceTree = "<group>"; };
+		B87539CA2C2B08430085A975 /* Bootstrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bootstrap.swift; sourceTree = "<group>"; };
+		B87539CC2C2B46950085A975 /* ChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartView.swift; sourceTree = "<group>"; };
+		B87539CE2C2DCB770085A975 /* BasalViewActivity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalViewActivity.swift; sourceTree = "<group>"; };
+		B87D411C2C28A69600120877 /* GlucoseLiveActivityConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlucoseLiveActivityConfiguration.swift; sourceTree = "<group>"; };
+		B87D411E2C28A85F00120877 /* ActivityKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ActivityKit.framework; path = System/Library/Frameworks/ActivityKit.framework; sourceTree = SDKROOT; };
+		B8A937B72C29BA5900E38645 /* LiveActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityManager.swift; sourceTree = "<group>"; };
+		B8A937C02C29C29300E38645 /* GlucoseActivityAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GlucoseActivityAttributes.swift; sourceTree = "<group>"; };
+		B8FD0B512C39CA3E003FB72B /* LiveActivityBottomRowManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityBottomRowManagerView.swift; sourceTree = "<group>"; };
 		C1004DEF2981F5B700B8CF94 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		C1004DF12981F5B700B8CF94 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		C1004DF42981F5B700B8CF94 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -1727,6 +1769,7 @@
 				1419606928D9554E00BA86E0 /* LoopKitUI.framework in Frameworks */,
 				1419606A28D955BC00BA86E0 /* MockKitUI.framework in Frameworks */,
 				1481F9BB28DA26F4004C5AEB /* LoopUI.framework in Frameworks */,
+				B87D411F2C28A85F00120877 /* ActivityKit.framework in Frameworks */,
 				1419606428D9550400BA86E0 /* LoopKitUI.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -1836,6 +1879,7 @@
 				84AA81D12A4A2778000B658B /* Components */,
 				84AA81D92A4A2966000B658B /* Helpers */,
 				84AA81DE2A4A2B3D000B658B /* Timeline */,
+				B87D41192C28A61900120877 /* Live Activity */,
 				84AA81DF2A4A2B7A000B658B /* Widgets */,
 				84AA81D22A4A27A3000B658B /* LoopWidgets.swift */,
 			);
@@ -2163,6 +2207,7 @@
 				43DE92581C5479E4001FFDE1 /* PotentialCarbEntryUserInfo.swift */,
 				43C05CB721EBEA54006FB252 /* HKUnit.swift */,
 				434FF1E91CF26C29000DB779 /* IdentifiableClass.swift */,
+				E9C00EEF24C620EF00628F35 /* LoopSettings.swift */,
 				C19E96DD23D2733F003F79B0 /* LoopCompletionFreshness.swift */,
 				430B29892041F54A00BA9F93 /* NSUserDefaults.swift */,
 				431E73471FF95A900069B5F7 /* PersistenceController.swift */,
@@ -2170,10 +2215,10 @@
 				43D9FFD121EAE05D00AF44BF /* LoopCore.h */,
 				43D9FFD221EAE05D00AF44BF /* Info.plist */,
 				4B60626A287E286000BF8BBB /* Localizable.strings */,
-				E9C00EEF24C620EF00628F35 /* LoopSettings.swift */,
 				C16575742539FD60004AE16E /* LoopCoreConstants.swift */,
 				E9B3551B292844010076AB04 /* MissedMealNotification.swift */,
 				C1D0B62F2986D4D90098D215 /* LocalizedString.swift */,
+				B851FFC62C37271E00D738C1 /* LiveActivitySettings.swift */,
 			);
 			path = LoopCore;
 			sourceTree = "<group>";
@@ -2276,6 +2321,8 @@
 				C1AF062229426300002C1B19 /* ManualGlucoseEntryRow.swift */,
 				DDC389FD2A2C4C830066E2E8 /* GlucoseBasedApplicationFactorSelectionView.swift */,
 				DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */,
+				B851FFC42C37221800D738C1 /* LiveActivityManagementView.swift */,
+				B8FD0B512C39CA3E003FB72B /* LiveActivityBottomRowManagerView.swift */,
 			);
 			path = Views;
 			sourceTree = "<group>";
@@ -2315,6 +2362,7 @@
 				1DA6499D2441266400F61E75 /* Alerts */,
 				E95D37FF24EADE68005E2F50 /* Store Protocols */,
 				E9B355232935906B0076AB04 /* Missed Meal Detection */,
+				B8A937C52C29C44600E38645 /* Live Activity */,
 				C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */,
 				A96DAC2B2838F31200D94E38 /* SharedLogging.swift */,
 				7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */,
@@ -2525,6 +2573,7 @@
 				C11613472983096D00777E7C /* InfoPlist.strings */,
 				14B1736D28AEDA63006CCD7C /* LoopWidgetExtension.entitlements */,
 				14B1736628AED9EE006CCD7C /* Info.plist */,
+				B87539CA2C2B08430085A975 /* Bootstrap.swift */,
 			);
 			path = Bootstrap;
 			sourceTree = "<group>";
@@ -2535,6 +2584,7 @@
 				84AA81DA2A4A2973000B658B /* Date.swift */,
 				84AA81D52A4A28AF000B658B /* WidgetBackground.swift */,
 				84D2879E2AC756C8007ED283 /* ContentMargin.swift */,
+				B87539C82C2B06CE0085A975 /* LocalizedString.swift */,
 			);
 			path = Helpers;
 			sourceTree = "<group>";
@@ -2628,6 +2678,7 @@
 				1D49795724E7289700948F05 /* ServicesViewModel.swift */,
 				C174233B259BEB0F00399C9D /* ManualEntryDoseViewModel.swift */,
 				1DB619AB270BAD3D006C9D07 /* VersionUpdateViewModel.swift */,
+				B82181F52C93628300478A91 /* LiveActivityManagementViewModel.swift */,
 			);
 			path = "View Models";
 			sourceTree = "<group>";
@@ -2663,6 +2714,7 @@
 		968DCD53F724DE56FFE51920 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				B87D411E2C28A85F00120877 /* ActivityKit.framework */,
 				C159C82E286787EF00A86EC0 /* LoopKit.framework */,
 				C159C8212867859800A86EC0 /* MockKitUI.framework */,
 				C159C8192867857000A86EC0 /* LoopKitUI.framework */,
@@ -2760,6 +2812,26 @@
 			path = LoopCore;
 			sourceTree = "<group>";
 		};
+		B87D41192C28A61900120877 /* Live Activity */ = {
+			isa = PBXGroup;
+			children = (
+				B87D411C2C28A69600120877 /* GlucoseLiveActivityConfiguration.swift */,
+				B87539CC2C2B46950085A975 /* ChartView.swift */,
+				B87539CE2C2DCB770085A975 /* BasalViewActivity.swift */,
+			);
+			path = "Live Activity";
+			sourceTree = "<group>";
+		};
+		B8A937C52C29C44600E38645 /* Live Activity */ = {
+			isa = PBXGroup;
+			children = (
+				B8A937B72C29BA5900E38645 /* LiveActivityManager.swift */,
+				B8A937C02C29C29300E38645 /* GlucoseActivityAttributes.swift */,
+				B82181F92C936AD600478A91 /* ChartAxisGenerator.swift */,
+			);
+			path = "Live Activity";
+			sourceTree = "<group>";
+		};
 		C13072B82A76AF0A009A7C58 /* live_capture */ = {
 			isa = PBXGroup;
 			children = (
@@ -2982,6 +3054,7 @@
 				14B1735828AED9EC006CCD7C /* Sources */,
 				14B1735928AED9EC006CCD7C /* Frameworks */,
 				14B1735A28AED9EC006CCD7C /* Resources */,
+				B82181FF2C9370F800478A91 /* Embed Frameworks */,
 			);
 			buildRules = (
 			);
@@ -2989,6 +3062,8 @@
 				1481F9BE28DA26F4004C5AEB /* PBXTargetDependency */,
 			);
 			name = "Loop Widget Extension";
+			packageProductDependencies = (
+			);
 			productName = SmallStatusWidgetExtension;
 			productReference = 14B1735C28AED9EC006CCD7C /* Loop Widget Extension.appex */;
 			productType = "com.apple.product-type.app-extension";
@@ -3623,12 +3698,15 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				B87539CB2C2B08430085A975 /* Bootstrap.swift in Sources */,
 				14B1738128AEDC70006CCD7C /* StatusExtensionContext.swift in Sources */,
 				14B1737628AEDC6C006CCD7C /* HKUnit.swift in Sources */,
 				14B1737728AEDC6C006CCD7C /* NSBundle.swift in Sources */,
 				84AA81D62A4A28AF000B658B /* WidgetBackground.swift in Sources */,
 				84AA81D82A4A2910000B658B /* StatusWidgetTimelimeEntry.swift in Sources */,
 				84AA81D32A4A27A3000B658B /* LoopWidgets.swift in Sources */,
+				B87539CF2C2DCB770085A975 /* BasalViewActivity.swift in Sources */,
+				B87539CD2C2B46950085A975 /* ChartView.swift in Sources */,
 				84AA81E32A4A36FB000B658B /* SystemActionLink.swift in Sources */,
 				14B1737828AEDC6C006CCD7C /* NSTimeInterval.swift in Sources */,
 				14B1737928AEDC6C006CCD7C /* NSUserDefaults+StatusExtension.swift in Sources */,
@@ -3639,7 +3717,10 @@
 				14B1737E28AEDC6C006CCD7C /* FeatureFlags.swift in Sources */,
 				84AA81E72A4A4DEF000B658B /* PumpView.swift in Sources */,
 				14B1737F28AEDC6C006CCD7C /* PluginManager.swift in Sources */,
+				B87D411D2C28A69600120877 /* GlucoseLiveActivityConfiguration.swift in Sources */,
+				B87539C92C2B06CE0085A975 /* LocalizedString.swift in Sources */,
 				14B1738028AEDC6C006CCD7C /* Debug.swift in Sources */,
+				B8A937C42C29C43B00E38645 /* GlucoseActivityAttributes.swift in Sources */,
 				84AA81DB2A4A2973000B658B /* Date.swift in Sources */,
 				14B1737228AEDBF6006CCD7C /* BasalView.swift in Sources */,
 				14B1737428AEDBF6006CCD7C /* GlucoseView.swift in Sources */,
@@ -3725,6 +3806,7 @@
 				B4E202302661063E009421B5 /* AutomaticDosingStatus.swift in Sources */,
 				C191D2A125B3ACAA00C26C0B /* DosingStrategySelectionView.swift in Sources */,
 				A977A2F424ACFECF0059C207 /* CriticalEventLogExportManager.swift in Sources */,
+				B8FD0B522C39CA3E003FB72B /* LiveActivityBottomRowManagerView.swift in Sources */,
 				89CA2B32226C18B8004D9350 /* TestingScenariosTableViewController.swift in Sources */,
 				43E93FB71E469A5100EAB8DB /* HKUnit.swift in Sources */,
 				43C05CAF21EB2C24006FB252 /* NSBundle.swift in Sources */,
@@ -3732,6 +3814,7 @@
 				A967D94C24F99B9300CDDF8A /* OutputStream.swift in Sources */,
 				1DB1065124467E18005542BD /* AlertManager.swift in Sources */,
 				1D9650C82523FBA100A1370B /* DeviceDataManager+BolusEntryViewModelDelegate.swift in Sources */,
+				B851FFC52C37221800D738C1 /* LiveActivityManagementView.swift in Sources */,
 				43C0944A1CACCC73001F6403 /* NotificationManager.swift in Sources */,
 				149A28E42A8A63A700052EDF /* FavoriteFoodDetailView.swift in Sources */,
 				1DDE274024AEA4F200796622 /* NotificationsCriticalAlertPermissionsView.swift in Sources */,
@@ -3786,6 +3869,7 @@
 				C1AF062329426300002C1B19 /* ManualGlucoseEntryRow.swift in Sources */,
 				C148CEE724FD91BD00711B3B /* DeliveryUncertaintyAlertManager.swift in Sources */,
 				DDC389FC2A2BC6670066E2E8 /* SettingsView+algorithmExperimentsSection.swift in Sources */,
+				B82181F62C93628300478A91 /* LiveActivityManagementViewModel.swift in Sources */,
 				1D12D3B92548EFDD00B53E8B /* main.swift in Sources */,
 				435400341C9F878D00D5819C /* SetBolusUserInfo.swift in Sources */,
 				A9DCF32A25B0FABF00C89088 /* LoopUIColorPalette+Default.swift in Sources */,
@@ -3818,11 +3902,13 @@
 				A97F250825E056D500F0EE19 /* OnboardingManager.swift in Sources */,
 				438D42F91D7C88BC003244B0 /* PredictionInputEffect.swift in Sources */,
 				892A5D692230C41D008961AB /* RangeReplaceableCollection.swift in Sources */,
+				B8A937B82C29BA5900E38645 /* LiveActivityManager.swift in Sources */,
 				DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */,
 				4F70C2101DE8FAC5006380B7 /* ExtensionDataManager.swift in Sources */,
 				43DFB62320D4CAE7008A7BAE /* PumpManager.swift in Sources */,
 				A9FB75F1252BE320004C7D3F /* BolusDosingDecision.swift in Sources */,
 				892A5D59222F0A27008961AB /* Debug.swift in Sources */,
+				B82182002C93716A00478A91 /* ChartAxisGenerator.swift in Sources */,
 				431A8C401EC6E8AB00823B9C /* CircleMaskView.swift in Sources */,
 				1D05219D2469F1F5000EBBDE /* AlertStore.swift in Sources */,
 				439897371CD2F80600223065 /* AnalyticsServicesManager.swift in Sources */,
@@ -3835,6 +3921,7 @@
 				439706E622D2E84900C81566 /* PredictionSettingTableViewCell.swift in Sources */,
 				430D85891F44037000AF2D4F /* HUDViewTableViewCell.swift in Sources */,
 				43A51E211EB6DBDD000736CC /* LoopChartsTableViewController.swift in Sources */,
+				B8A937C32C29C3B400E38645 /* GlucoseActivityAttributes.swift in Sources */,
 				8968B1122408B3520074BB48 /* UIFont.swift in Sources */,
 				1452F4A92A851C9400F8B9E4 /* AddEditFavoriteFoodViewModel.swift in Sources */,
 				438D42FB1D7D11A4003244B0 /* PredictionInputEffectTableViewCell.swift in Sources */,
@@ -3956,6 +4043,7 @@
 				C17DDC9D28AC33A1005FBF4C /* PersistedProperty.swift in Sources */,
 				43C05CA921EB2B26006FB252 /* PersistenceController.swift in Sources */,
 				A9CE912224CA032E00302A40 /* NSUserDefaults.swift in Sources */,
+				B851FFCB2C3731DE00D738C1 /* LiveActivitySettings.swift in Sources */,
 				43C05CAB21EB2B4A006FB252 /* NSBundle.swift in Sources */,
 				43C05CC721EC2ABC006FB252 /* IdentifiableClass.swift in Sources */,
 				E9B3552B293591E70076AB04 /* MissedMealNotification.swift in Sources */,
@@ -3977,6 +4065,7 @@
 				C17DDC9C28AC339E005FBF4C /* PersistedProperty.swift in Sources */,
 				43C05CA821EB2B26006FB252 /* PersistenceController.swift in Sources */,
 				43C05CAA21EB2B49006FB252 /* NSBundle.swift in Sources */,
+				B851FFCA2C3731DE00D738C1 /* LiveActivitySettings.swift in Sources */,
 				43C05CC821EC2ABC006FB252 /* IdentifiableClass.swift in Sources */,
 				43C05CAD21EB2BBF006FB252 /* NSUserDefaults.swift in Sources */,
 				E9B3552A293591E70076AB04 /* MissedMealNotification.swift in Sources */,
@@ -4993,7 +5082,6 @@
 				GCC_WARN_UNUSED_LABEL = YES;
 				GCC_WARN_UNUSED_PARAMETER = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 15.1;
 				LOCALIZED_STRING_MACRO_NAMES = (
 					NSLocalizedString,
 					CFLocalizedString,
@@ -5103,7 +5191,6 @@
 				GCC_WARN_UNUSED_LABEL = YES;
 				GCC_WARN_UNUSED_PARAMETER = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 15.1;
 				LOCALIZED_STRING_MACRO_NAMES = (
 					NSLocalizedString,
 					CFLocalizedString,
diff --git a/Loop/Info.plist b/Loop/Info.plist
index ddad5426ac..f9f4ca84a9 100644
--- a/Loop/Info.plist
+++ b/Loop/Info.plist
@@ -71,6 +71,10 @@
 	<string>Carbohydrate meal data entered in the app and on the watch is stored in the Health database. Glucose data retrieved from the CGM is stored securely in HealthKit.</string>
 	<key>NSSiriUsageDescription</key>
 	<string>Loop uses Siri to allow you to enact presets with your voice.</string>
+	<key>NSSupportsLiveActivities</key>
+	<true/>
+	<key>NSSupportsLiveActivitiesFrequentUpdates</key>
+	<true/>
 	<key>NSUserActivityTypes</key>
 	<array>
 		<string>EnableOverridePresetIntent</string>
diff --git a/Loop/Loop.entitlements b/Loop/Loop.entitlements
index 50ba55d9e5..e6a2f9b9f0 100644
--- a/Loop/Loop.entitlements
+++ b/Loop/Loop.entitlements
@@ -8,6 +8,8 @@
 	<true/>
 	<key>com.apple.developer.healthkit.access</key>
 	<array/>
+	<key>com.apple.developer.healthkit.background-delivery</key>
+	<true/>
 	<key>com.apple.developer.nfc.readersession.formats</key>
 	<array>
 		<string>TAG</string>
diff --git a/Loop/Managers/Live Activity/ChartAxisGenerator.swift b/Loop/Managers/Live Activity/ChartAxisGenerator.swift
new file mode 100644
index 0000000000..0fcc3ca80d
--- /dev/null
+++ b/Loop/Managers/Live Activity/ChartAxisGenerator.swift	
@@ -0,0 +1,125 @@
+//
+//  ChartAxisGenerator.swift
+//  Loop
+//
+//  Created by Bastiaan Verhaar on 12/09/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import Foundation
+import HealthKit
+import SwiftCharts
+import UIKit
+
+struct ChartAxisGenerator {
+    private static let yAxisStepSizeMGDLOverride: Double? = FeatureFlags.predictedGlucoseChartClampEnabled ? 40 : nil
+    private static let range = FeatureFlags.predictedGlucoseChartClampEnabled ? LoopConstants.glucoseChartDefaultDisplayBoundClamped : LoopConstants.glucoseChartDefaultDisplayBound
+    private static let predictedGlucoseSoftBoundsMinimum = FeatureFlags.predictedGlucoseChartClampEnabled ? HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 40) : nil
+    
+    private static let minSegmentCount: Double = 2
+    private static let addPaddingSegmentIfEdge = false
+    private static let axisLabelSettings = ChartLabelSettings(font: .systemFont(ofSize: 14), fontColor: UIColor.secondaryLabel)
+    
+    // This logic is copied/ported from generateYAxisValuesUsingLinearSegmentStep
+    static func getYAxis(points: [Double], isMmol: Bool) -> [Double] {
+        let unit: HKUnit = isMmol ? .millimolesPerLiter : .milligramsPerDeciliter
+
+        let glucoseDisplayRange = [
+            range.lowerBound.doubleValue(for: unit),
+            range.upperBound.doubleValue(for: unit)
+        ]
+        
+        let actualPoints = points + glucoseDisplayRange
+        let sortedChartPoints = actualPoints.sorted {(obj1, obj2) in
+            return obj1 < obj2
+        }
+        
+        guard let first = sortedChartPoints.first, let lastPar = sortedChartPoints.last else {
+            print("Trying to generate Y axis without datapoints, returning empty array")
+            return []
+        }
+        
+        let maxSegmentCount: Double = glucoseValueBelowSoftBoundsMinimum(first, unit) ? 5 : 4
+        
+        guard lastPar >=~ first else {fatalError("Invalid range generating axis values")}
+        let multiple: Double = !isMmol ? (yAxisStepSizeMGDLOverride ?? 25) : 1
+        
+        let last = needsToAddOne(lastPar, first) ? lastPar + 1 : lastPar
+        
+        /// The first axis value will be less than or equal to the first scalar value, aligned with the desired multiple
+        var firstValue = first - (first.truncatingRemainder(dividingBy: multiple))
+        /// The last axis value will be greater than or equal to the last scalar value, aligned with the desired multiple
+        let remainder = last.truncatingRemainder(dividingBy: multiple)
+        var lastValue = remainder == 0 ? last : last + (multiple - remainder)
+        var segmentSize = multiple
+        
+        /// If there should be a padding segment added when a scalar value falls on the first or last axis value, adjust the first and last axis values
+        if firstValue =~ first && addPaddingSegmentIfEdge {
+           firstValue = firstValue - segmentSize
+        }
+        
+        // do not allow the first label to be displayed as -0
+        while firstValue < 0 && firstValue.rounded() == -0 {
+            firstValue = firstValue - segmentSize
+        }
+        
+        if lastValue =~ last && addPaddingSegmentIfEdge {
+            lastValue = lastValue + segmentSize
+        }
+        
+        let distance = lastValue - firstValue
+        var currentMultiple = multiple
+        var segmentCount = distance / currentMultiple
+        var potentialSegmentValues = stride(from: firstValue, to: lastValue, by: currentMultiple)
+
+        /// Find the optimal number of segments and segment width
+        /// If the number of segments is greater than desired, make each segment wider
+        /// ensure no label of -0 will be displayed on the axis
+        while segmentCount > maxSegmentCount ||
+            !potentialSegmentValues.filter({ $0 < 0 && $0.rounded() == -0 }).isEmpty
+        {
+            currentMultiple += multiple
+            segmentCount = distance / currentMultiple
+            potentialSegmentValues = stride(from: firstValue, to: lastValue, by: currentMultiple)
+        }
+        segmentCount = ceil(segmentCount)
+        
+        /// Increase the number of segments until there are enough as desired
+        while segmentCount < minSegmentCount {
+            segmentCount += 1
+        }
+        segmentSize = currentMultiple
+        
+        /// Generate axis values from the first value, segment size and number of segments
+        let offset = firstValue
+        return (0...Int(segmentCount)).map {segment in
+            var scalar = offset + (Double(segment) * segmentSize)
+            // a value that could be displayed as 0 should truly be 0 to have the zero-line drawn correctly.
+            if scalar != 0,
+                scalar.rounded() == 0
+            {
+                scalar = 0
+            }
+            return ChartAxisValueDouble(scalar, labelSettings: axisLabelSettings).scalar
+        }
+    }
+    
+    private static func needsToAddOne(_ a: Double, _ b: Double) -> Bool {
+        return fabs(a - b) < Double.ulpOfOne
+    }
+    
+    private static func glucoseValueBelowSoftBoundsMinimum(_ glucoseMinimum: Double, _ unit: HKUnit) -> Bool {
+        guard let predictedGlucoseSoftBoundsMinimum = predictedGlucoseSoftBoundsMinimum else
+        {
+            return false
+        }
+            
+        return HKQuantity(unit: unit, doubleValue: glucoseMinimum) < predictedGlucoseSoftBoundsMinimum
+    }
+}
+
+fileprivate extension Double {
+    static func >=~ (a: Double, b: Double) -> Bool {
+        return a =~ b || a > b
+    }
+}
diff --git a/Loop/Managers/Live Activity/GlucoseActivityAttributes.swift b/Loop/Managers/Live Activity/GlucoseActivityAttributes.swift
new file mode 100644
index 0000000000..936de751c5
--- /dev/null
+++ b/Loop/Managers/Live Activity/GlucoseActivityAttributes.swift	
@@ -0,0 +1,151 @@
+//
+//  LiveActivityAttributes.swift
+//  LoopUI
+//
+//  Created by Bastiaan Verhaar on 23/06/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import ActivityKit
+import Foundation
+import LoopKit
+import LoopCore
+
+public struct GlucoseActivityAttributes: ActivityAttributes {
+    public struct ContentState: Codable, Hashable {
+        // Meta data
+        public let date: Date
+        public let ended: Bool
+        public let preset: Preset?
+        public let glucoseRanges: [GlucoseRangeValue]
+        
+        // Dynamic island data
+        public let currentGlucose: Double
+        public let trendType: GlucoseTrend?
+        public let delta: String
+        public let isMmol: Bool
+        
+        // Loop circle
+        public let isCloseLoop: Bool
+        public let lastCompleted: Date?
+        
+        // Bottom row
+        public let bottomRow: [BottomRowItem]
+        
+        // Chart view
+        public let glucoseSamples: [GlucoseSampleAttributes]
+        public let predicatedGlucose: [Double]
+        public let predicatedStartDate: Date?
+        public let predicatedInterval: TimeInterval?
+        public let yAxisMarks: [Double]
+    }
+    
+    public let mode: LiveActivityMode
+    public let addPredictiveLine: Bool
+    public let useLimits: Bool
+    public let upperLimitChartMmol: Double
+    public let lowerLimitChartMmol: Double
+    public let upperLimitChartMg: Double
+    public let lowerLimitChartMg: Double
+}
+
+public struct Preset: Codable, Hashable {
+    public let title: String
+    public let startDate: Date
+    public let endDate: Date
+    public let minValue: Double
+    public let maxValue: Double
+}
+
+public struct GlucoseRangeValue: Identifiable, Codable, Hashable {
+    public let id: UUID
+    public let minValue: Double
+    public let maxValue: Double
+    public let startDate: Date
+    public let endDate: Date
+}
+
+public struct BottomRowItem: Codable, Hashable {
+    public enum BottomRowType: Codable, Hashable {
+        case generic
+        case basal
+        case currentBg
+        case loopCircle
+    }
+    
+    public let type: BottomRowType
+    
+    // Generic properties
+    public let label: String
+    public let value: String
+    public let unit: String
+    
+    public let trend: GlucoseTrend?
+
+    // Basal properties
+    public let rate: Double
+    public let percentage: Double
+    
+    private init(type: BottomRowType, label: String?, value: String?, unit: String?, trend: GlucoseTrend?, rate: Double?, percentage: Double?) {
+        self.type = type
+        self.label = label ?? ""
+        self.value = value ?? ""
+        self.trend = trend
+        self.unit = unit ?? ""
+        self.rate = rate ?? 0
+        self.percentage = percentage ?? 0
+    }
+    
+    static func generic(label: String, value: String, unit: String) -> BottomRowItem  {
+        return BottomRowItem(
+            type: .generic,
+            label: label,
+            value: value,
+            unit: unit,
+            trend: nil,
+            rate: nil,
+            percentage: nil
+        )
+    }
+    
+    static func basal(rate: Double, percentage: Double) -> BottomRowItem {
+        return BottomRowItem(
+            type: .basal,
+            label: nil,
+            value: nil,
+            unit: nil,
+            trend: nil,
+            rate: rate,
+            percentage: percentage
+        )
+    }
+    
+    static func currentBg(label: String, value: String, trend: GlucoseTrend?) -> BottomRowItem {
+        return BottomRowItem(
+            type: .currentBg,
+            label: label,
+            value: value,
+            unit: nil,
+            trend: trend,
+            rate: nil,
+            percentage: nil
+        )
+    }
+    
+    static func loopIcon() -> BottomRowItem {
+        return BottomRowItem(
+            type: .loopCircle,
+            label: nil,
+            value: nil,
+            unit: nil,
+            trend: nil,
+            rate: nil,
+            percentage: nil
+        )
+    }
+}
+
+public struct GlucoseSampleAttributes: Codable, Hashable {
+    public let x: Date
+    public let y: Double
+}
diff --git a/Loop/Managers/Live Activity/LiveActivityManager.swift b/Loop/Managers/Live Activity/LiveActivityManager.swift
new file mode 100644
index 0000000000..30c6920fae
--- /dev/null
+++ b/Loop/Managers/Live Activity/LiveActivityManager.swift	
@@ -0,0 +1,516 @@
+//
+//  LiveActivityManaer.swift
+//  Loop
+//
+//  Created by Bastiaan Verhaar on 24/06/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import LoopKitUI
+import LoopKit
+import LoopCore
+import Foundation
+import HealthKit
+import ActivityKit
+
+extension Notification.Name {
+    static let LiveActivitySettingsChanged = Notification.Name(rawValue: "com.loopKit.notification.LiveActivitySettingsChanged")
+}
+
+@available(iOS 16.2, *)
+class LiveActivityManager {
+    private let activityInfo = ActivityAuthorizationInfo()
+    private var activity: Activity<GlucoseActivityAttributes>?
+    private let healthStore = HKHealthStore()
+    
+    private let glucoseStore: GlucoseStoreProtocol
+    private let doseStore: DoseStoreProtocol
+    private var loopSettings: LoopSettings
+    
+    private var startDate: Date = Date.now
+    private var settings: LiveActivitySettings = UserDefaults.standard.liveActivity ?? LiveActivitySettings()
+    
+    private let cobFormatter: NumberFormatter =  {
+        let numberFormatter = NumberFormatter()
+        numberFormatter.numberStyle = .none
+        return numberFormatter
+    }()
+    private let iobFormatter: NumberFormatter =  {
+        let numberFormatter = NumberFormatter()
+        numberFormatter.numberStyle = .none
+        numberFormatter.maximumFractionDigits = 1
+        numberFormatter.minimumFractionDigits = 1
+        return numberFormatter
+    }()
+    private let timeFormatter: DateFormatter = {
+        let dateFormatter = DateFormatter()
+        dateFormatter.dateStyle = .none
+        dateFormatter.timeStyle = .short
+        
+        return dateFormatter
+    }()
+    
+    init?(glucoseStore: GlucoseStoreProtocol, doseStore: DoseStoreProtocol, loopSettings: LoopSettings) {
+        guard self.activityInfo.areActivitiesEnabled else {
+            print("ERROR: Live Activities are not enabled...")
+            return nil
+        }
+        
+        self.glucoseStore = glucoseStore
+        self.doseStore = doseStore
+        self.loopSettings = loopSettings
+        
+        // Ensure settings exist
+        if UserDefaults.standard.liveActivity == nil {
+            self.settings = LiveActivitySettings()
+        }
+        
+        let nc = NotificationCenter.default
+        nc.addObserver(self, selector: #selector(self.appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
+        nc.addObserver(self, selector: #selector(self.settingsChanged), name: .LiveActivitySettingsChanged, object: nil)
+        guard self.settings.enabled else {
+            return
+        }
+        
+        initEmptyActivity(settings: self.settings)
+        update()
+        
+        Task {
+            await self.endUnknownActivities()
+        }
+    }
+    
+    public func update(loopSettings: LoopSettings) {
+        self.loopSettings = loopSettings
+        update()
+    }
+    
+    private func update() {
+        Task {
+            if self.needsRecreation(), await UIApplication.shared.applicationState == .active {
+                // activity is no longer visible or old. End it and try to push the update again
+                await endActivity()
+            }
+            
+            guard let unit = await self.healthStore.cachedPreferredUnits(for: .bloodGlucose) else {
+                print("ERROR: No unit found...")
+                return
+            }
+            
+            let isMmol = unit == HKUnit.millimolesPerLiter
+            await self.endUnknownActivities()
+
+            let statusContext = UserDefaults.appGroup?.statusExtensionContext
+            let glucoseFormatter = NumberFormatter.glucoseFormatter(for: unit)
+            
+            let glucoseSamples = self.getGlucoseSample(unit: unit)
+            guard let currentGlucose = glucoseSamples.last else {
+                print("ERROR: No glucose sample found...")
+                return
+            }
+
+            let current = currentGlucose.quantity.doubleValue(for: unit)
+            
+            var delta: String = "+\(glucoseFormatter.string(from: Double(0)) ?? "")"
+            if glucoseSamples.count > 1 {
+                let prevSample = glucoseSamples[glucoseSamples.count - 2]
+                let deltaValue = current - (prevSample.quantity.doubleValue(for: unit))
+                delta = "\(deltaValue < 0 ? "-" : "+")\(glucoseFormatter.string(from: abs(deltaValue)) ?? "??")"
+            }
+            
+            let bottomRow = self.getBottomRow(
+                currentGlucose: current,
+                delta: delta,
+                statusContext: statusContext,
+                glucoseFormatter: glucoseFormatter
+            )
+
+            var predicatedGlucose: [Double] = []
+            if let samples = statusContext?.predictedGlucose?.values, settings.addPredictiveLine {
+                predicatedGlucose = samples
+            }
+            
+            var endDateChart: Date? = nil
+            if predicatedGlucose.count == 0 {
+                endDateChart = glucoseSamples.last?.startDate
+            } else if let predictedGlucose = statusContext?.predictedGlucose {
+                endDateChart = predictedGlucose.startDate.addingTimeInterval(.hours(4))
+            }
+            
+            guard let endDateChart = endDateChart else {
+                return
+            }
+            
+            var presetContext: Preset? = nil
+            if let override = self.loopSettings.preMealOverride ?? self.loopSettings.scheduleOverride, let start = glucoseSamples.first?.startDate {
+                presetContext = Preset(
+                    title: override.getTitle(),
+                    startDate: max(override.startDate, start),
+                    endDate: override.duration.isInfinite ? endDateChart : min(override.actualEndDate, endDateChart),
+                    minValue: override.settings.targetRange?.lowerBound.doubleValue(for: unit) ?? 0,
+                    maxValue: override.settings.targetRange?.upperBound.doubleValue(for: unit) ?? 0
+                )
+            }
+            
+            var glucoseRanges: [GlucoseRangeValue] = []
+            if let glucoseRangeSchedule = self.loopSettings.glucoseTargetRangeSchedule, let start = glucoseSamples.first?.startDate {
+                glucoseRanges = getGlucoseRanges(
+                    glucoseRangeSchedule: glucoseRangeSchedule,
+                    presetContext: presetContext,
+                    start: start,
+                    end: endDateChart,
+                    unit: unit
+                )
+            }
+            
+            let yAxisPoints = glucoseSamples.map{ item in item.quantity.doubleValue(for: unit) } + predicatedGlucose
+            let chartYAxis = ChartAxisGenerator.getYAxis(
+                points: yAxisPoints,
+                isMmol: unit == HKUnit.millimolesPerLiter
+            )
+
+            let state = GlucoseActivityAttributes.ContentState(
+                date: currentGlucose.startDate,
+                ended: false,
+                preset: presetContext,
+                glucoseRanges: glucoseRanges,
+                currentGlucose: current,
+                trendType: statusContext?.glucoseDisplay?.trendType,
+                delta: delta,
+                isMmol: isMmol,
+                isCloseLoop: statusContext?.isClosedLoop ?? false,
+                lastCompleted: statusContext?.lastLoopCompleted,
+                bottomRow: bottomRow,
+                // In order to prevent maxSize errors, only allow the last 100 samples to be sent
+                // Will most likely not be an issue, might be an issue for debugging/CGM simulator with 5sec interval
+                glucoseSamples: glucoseSamples.suffix(100).map { item in
+                    return GlucoseSampleAttributes(x: item.startDate, y: item.quantity.doubleValue(for: unit))
+                },
+                predicatedGlucose: predicatedGlucose,
+                predicatedStartDate: statusContext?.predictedGlucose?.startDate,
+                predicatedInterval: statusContext?.predictedGlucose?.interval,
+                yAxisMarks: chartYAxis
+            )
+            
+            await self.activity?.update(ActivityContent(
+                state: state,
+                staleDate: Date.now.addingTimeInterval(.hours(1))
+            ))
+        }
+    }
+    
+    @objc private func settingsChanged() {
+        Task {
+            let newSettings = UserDefaults.standard.liveActivity ?? LiveActivitySettings()
+            
+            // Update live activity if needed
+            if !newSettings.enabled, let activity = self.activity {
+                await activity.end(nil, dismissalPolicy: .immediate)
+                self.activity = nil
+                
+                return
+            } else if newSettings.enabled && self.activity == nil {
+                initEmptyActivity(settings: newSettings)
+                
+            } else if newSettings != self.settings {
+                await self.activity?.end(nil, dismissalPolicy: .immediate)
+                self.activity = nil
+                
+                initEmptyActivity(settings: newSettings)
+            }
+            
+            self.settings = newSettings
+            update()
+        }
+    }
+    
+    @objc private func appMovedToForeground() {
+        guard self.settings.enabled else {
+            return
+        }
+        
+        guard let activity = self.activity else {
+            initEmptyActivity(settings: self.settings)
+            update()
+            return
+        }
+        
+        Task {
+            await activity.end(nil, dismissalPolicy: .immediate)
+            await self.endUnknownActivities()
+            self.activity = nil
+            
+            initEmptyActivity(settings: self.settings)
+            update()
+        }
+    }
+    
+    private func endUnknownActivities() async {
+        for unknownActivity in Activity<GlucoseActivityAttributes>.activities
+            .filter({ self.activity?.id != $0.id })
+        {
+            await unknownActivity.end(nil, dismissalPolicy: .immediate)
+        }
+    }
+    
+    private func endActivity() async {
+        let dynamicState = self.activity?.content.state
+        await self.activity?.end(nil, dismissalPolicy: .immediate)
+        for unknownActivity in Activity<GlucoseActivityAttributes>.activities {
+            await unknownActivity.end(nil, dismissalPolicy: .immediate)
+        }
+        
+        do {
+            if let dynamicState = dynamicState {
+                self.activity = try Activity.request(
+                    attributes: GlucoseActivityAttributes(
+                        mode: self.settings.mode,
+                        addPredictiveLine: self.settings.addPredictiveLine,
+                        useLimits: self.settings.useLimits,
+                        upperLimitChartMmol: self.settings.upperLimitChartMmol,
+                        lowerLimitChartMmol: self.settings.lowerLimitChartMmol,
+                        upperLimitChartMg: self.settings.upperLimitChartMg,
+                        lowerLimitChartMg: self.settings.lowerLimitChartMg
+                    ),
+                    content: .init(state: dynamicState, staleDate: nil),
+                    pushType: .token
+                )
+            }
+            self.startDate = Date.now
+        } catch {
+            print("ERROR: Error while ending live activity: \(error.localizedDescription)")
+        }
+    }
+    
+    private func needsRecreation() -> Bool {
+        if !self.settings.enabled {
+            return false
+        }
+        
+        switch activity?.activityState {
+        case .dismissed,
+             .ended,
+             .stale:
+            return true
+        case .active:
+            return -startDate.timeIntervalSinceNow > .hours(1)
+        default:
+            return true
+        }
+    }
+    
+    private func getInsulinOnBoard() -> String {
+        let updateGroup = DispatchGroup()
+        var iob = "??"
+        
+        updateGroup.enter()
+        self.doseStore.insulinOnBoard(at: Date.now) { result in
+            switch (result) {
+            case .failure:
+                break
+            case .success(let iobValue):
+                iob = self.iobFormatter.string(from: iobValue.value) ?? "??"
+                break
+            }
+            
+            updateGroup.leave()
+        }
+        
+        _ = updateGroup.wait(timeout: .distantFuture)
+        return iob
+    }
+    
+    private func getGlucoseSample(unit: HKUnit) -> [StoredGlucoseSample] {
+        let updateGroup = DispatchGroup()
+        var samples: [StoredGlucoseSample] = []
+        
+        updateGroup.enter()
+        
+        // When in spacious mode, we want to show the predictive line
+        // In compact mode, we only want to show the history
+        let timeInterval: TimeInterval = self.settings.addPredictiveLine ? .hours(-2) : .hours(-6)
+        self.glucoseStore.getGlucoseSamples(
+            start: Date.now.addingTimeInterval(timeInterval),
+            end: Date.now
+        ) { result in
+            switch (result) {
+            case .failure:
+                break
+            case .success(let data):
+                samples = data
+                break
+            }
+            
+            updateGroup.leave()
+        }
+        
+        _ = updateGroup.wait(timeout: .distantFuture)
+        return samples
+    }
+    
+    private func getGlucoseRanges(glucoseRangeSchedule: GlucoseRangeSchedule, presetContext: Preset?, start: Date, end: Date, unit: HKUnit) -> [GlucoseRangeValue] {
+        var glucoseRanges: [GlucoseRangeValue] = []
+        for item in glucoseRangeSchedule.quantityBetween(start: start, end: end) {
+            let minValue = item.value.lowerBound.doubleValue(for: unit)
+            let maxValue = item.value.upperBound.doubleValue(for: unit)
+            let startDate = max(item.startDate, start)
+            let endDate = min(item.endDate, end)
+            
+            if let presetContext = presetContext {
+                if presetContext.startDate > startDate, presetContext.endDate < endDate {
+                    // A preset is active during this schedule
+                    glucoseRanges.append(GlucoseRangeValue(
+                        id: UUID(),
+                        minValue: minValue,
+                        maxValue: maxValue,
+                        startDate: startDate,
+                        endDate: presetContext.startDate
+                    ))
+                    glucoseRanges.append(GlucoseRangeValue(
+                        id: UUID(),
+                        minValue: minValue,
+                        maxValue: maxValue,
+                        startDate: presetContext.endDate,
+                        endDate: endDate
+                    ))
+                } else if presetContext.endDate > startDate, presetContext.endDate < endDate {
+                    // Cut off the start of the glucose target
+                    glucoseRanges.append(GlucoseRangeValue(
+                        id: UUID(),
+                        minValue: minValue,
+                        maxValue: maxValue,
+                        startDate: presetContext.endDate,
+                        endDate: endDate
+                    ))
+                } else if presetContext.startDate < endDate, presetContext.startDate > startDate {
+                    // Cut off the end of the glucose target
+                    glucoseRanges.append(GlucoseRangeValue(
+                        id: UUID(),
+                        minValue: minValue,
+                        maxValue: maxValue,
+                        startDate: startDate,
+                        endDate: presetContext.startDate
+                    ))
+                    if presetContext.endDate == end {
+                        break
+                    }
+                } else {
+                    // No overlap with target and override
+                    glucoseRanges.append(GlucoseRangeValue(
+                        id: UUID(),
+                        minValue: minValue,
+                        maxValue: maxValue,
+                        startDate: startDate,
+                        endDate: endDate
+                    ))
+                }
+            } else {
+                glucoseRanges.append(GlucoseRangeValue(
+                    id: UUID(),
+                    minValue: minValue,
+                    maxValue: maxValue,
+                    startDate: startDate,
+                    endDate: endDate
+                ))
+            }
+        }
+        
+        return glucoseRanges
+    }
+    
+    private func getBottomRow(currentGlucose: Double, delta: String, statusContext: StatusExtensionContext?, glucoseFormatter: NumberFormatter) -> [BottomRowItem] {
+        return self.settings.bottomRowConfiguration.map { type in
+            switch(type) {
+            case .iob:
+                return BottomRowItem.generic(label: type.name(), value: getInsulinOnBoard(), unit: "U")
+                
+            case .cob:
+                var cob: String = "0"
+                if let cobValue = statusContext?.carbsOnBoard {
+                    cob = self.cobFormatter.string(from: cobValue) ?? "??"
+                }
+                return BottomRowItem.generic(label: type.name(), value: cob, unit: "g")
+                
+            case .basal:
+                guard let netBasalContext = statusContext?.netBasal else {
+                    return BottomRowItem.basal(rate: 0, percentage: 0)
+                }
+
+                return BottomRowItem.basal(rate: netBasalContext.rate, percentage: netBasalContext.percentage)
+                
+            case .currentBg:
+                return BottomRowItem.currentBg(label: type.name(), value: "\(glucoseFormatter.string(from: currentGlucose) ?? "??")", trend: statusContext?.glucoseDisplay?.trendType)
+                
+            case .eventualBg:
+                guard let eventual = statusContext?.predictedGlucose?.values.last else {
+                    return BottomRowItem.generic(label: type.name(), value: "??", unit: "")
+                }
+                
+                return BottomRowItem.generic(label: type.name(), value: glucoseFormatter.string(from: eventual) ?? "??", unit: "")
+                
+            case .deltaBg:
+                return BottomRowItem.generic(label: type.name(), value: delta, unit: "")
+                
+            case .loopCircle:
+                return BottomRowItem.loopIcon()
+                
+            case .updatedAt:
+                return BottomRowItem.generic(label: type.name(), value: timeFormatter.string(from: Date.now), unit: "")
+            }
+       }
+    }
+    
+    private func initEmptyActivity(settings: LiveActivitySettings) {
+        do {
+            let dynamicState = GlucoseActivityAttributes.ContentState(
+                date: Date.now,
+                ended: true,
+                preset: nil,
+                glucoseRanges: [],
+                currentGlucose: 0,
+                trendType: nil,
+                delta: "",
+                isMmol: true,
+                isCloseLoop: false,
+                lastCompleted: nil,
+                bottomRow: [],
+                glucoseSamples: [],
+                predicatedGlucose: [],
+                predicatedStartDate: nil,
+                predicatedInterval: nil,
+                yAxisMarks: []
+            )
+            
+            self.activity = try Activity.request(
+                attributes: GlucoseActivityAttributes(
+                    mode: settings.mode,
+                    addPredictiveLine: settings.addPredictiveLine,
+                    useLimits: settings.useLimits,
+                    upperLimitChartMmol: settings.upperLimitChartMmol,
+                    lowerLimitChartMmol: settings.lowerLimitChartMmol,
+                    upperLimitChartMg: settings.upperLimitChartMg,
+                    lowerLimitChartMg: settings.lowerLimitChartMg
+                ),
+                content: .init(state: dynamicState, staleDate: nil),
+                pushType: .token
+            )
+        } catch {
+            print("ERROR: Error while creating empty live activity: \(error.localizedDescription)")
+        }
+    }
+}
+
+extension TemporaryScheduleOverride {
+    func getTitle() -> String {
+        switch (self.context) {
+        case .preset(let preset):
+            return "\(preset.symbol) \(preset.name)"
+        case .custom:
+            return NSLocalizedString("Custom preset", comment: "The title of the cell indicating a generic custom preset is enabled")
+        case .preMeal:
+            return NSLocalizedString(" Pre-meal Preset", comment: "Status row title for premeal override enabled (leading space is to separate from symbol)")
+        case .legacyWorkout:
+            return ""
+        }
+    }
+}
diff --git a/Loop/Managers/LoopDataManager.swift b/Loop/Managers/LoopDataManager.swift
index 2319f4eceb..36c228c738 100644
--- a/Loop/Managers/LoopDataManager.swift
+++ b/Loop/Managers/LoopDataManager.swift
@@ -68,6 +68,8 @@ final class LoopDataManager {
     private var timeBasedDoseApplicationFactor: Double = 1.0
 
     private var insulinOnBoard: InsulinValue?
+    
+    private var liveActivityManager: LiveActivityManager?
 
     deinit {
         for observer in notificationObservers {
@@ -124,6 +126,12 @@ final class LoopDataManager {
         self.automaticDosingStatus = automaticDosingStatus
 
         self.trustedTimeOffset = trustedTimeOffset
+        
+        self.liveActivityManager = LiveActivityManager(
+            glucoseStore: self.glucoseStore,
+            doseStore: self.doseStore,
+            loopSettings: self.settings
+        )
 
         overrideIntentObserver = UserDefaults.appGroup?.observe(\.intentExtensionOverrideToSet, options: [.new], changeHandler: {[weak self] (defaults, change) in
             guard let name = change.newValue??.lowercased(), let appGroup = UserDefaults.appGroup else {
@@ -144,12 +152,14 @@ final class LoopDataManager {
                         }
                     }
                 }
+                
                 settings.scheduleOverride = preset.createOverride(enactTrigger: .remote("Siri"))
                 if let observers = self?.presetActivationObservers {
                     for observer in observers {
                         observer.presetActivated(context: .preset(preset), duration: preset.duration)
                     }
                 }
+                self?.liveActivityManager?.update(loopSettings: settings)
             }
             // Remove the override from UserDefaults so we don't set it multiple times
             appGroup.intentExtensionOverrideToSet = nil
@@ -167,6 +177,7 @@ final class LoopDataManager {
             ) { (note) -> Void in
                 self.dataAccessQueue.async {
                     self.logger.default("Received notification of carb entries changing")
+                    self.liveActivityManager?.update(loopSettings: self.settings)
 
                     self.carbEffect = nil
                     self.carbsOnBoard = nil
@@ -182,7 +193,8 @@ final class LoopDataManager {
             ) { (note) in
                 self.dataAccessQueue.async {
                     self.logger.default("Received notification of glucose samples changing")
-
+                    self.liveActivityManager?.update(loopSettings: self.settings)
+                    
                     self.glucoseMomentumEffect = nil
                     self.remoteRecommendationNeedsUpdating = true
 
@@ -196,6 +208,7 @@ final class LoopDataManager {
             ) { (note) in
                 self.dataAccessQueue.async {
                     self.logger.default("Received notification of dosing changing")
+                    self.liveActivityManager?.update(loopSettings: self.settings)
 
                     self.clearCachedInsulinEffects()
                     self.remoteRecommendationNeedsUpdating = true
@@ -247,6 +260,8 @@ final class LoopDataManager {
         if newValue.preMealOverride != oldValue.preMealOverride {
             // The prediction isn't actually invalid, but a target range change requires recomputing recommended doses
             predictedGlucose = nil
+            
+            self.liveActivityManager?.update(loopSettings: newValue)
         }
 
         if newValue.scheduleOverride != oldValue.scheduleOverride {
@@ -256,12 +271,14 @@ final class LoopDataManager {
                 for observer in self.presetActivationObservers {
                     observer.presetDeactivated(context: oldPreset.context)
                 }
-
+                self.liveActivityManager?.update(loopSettings: newValue)
             }
             if let newPreset = newValue.scheduleOverride {
                 for observer in self.presetActivationObservers {
                     observer.presetActivated(context: newPreset.context, duration: newPreset.duration)
                 }
+                
+                self.liveActivityManager?.update(loopSettings: newValue)
             }
 
             // Invalidate cached effects affected by the override
diff --git a/Loop/View Models/LiveActivityManagementViewModel.swift b/Loop/View Models/LiveActivityManagementViewModel.swift
new file mode 100644
index 0000000000..46fc560d6c
--- /dev/null
+++ b/Loop/View Models/LiveActivityManagementViewModel.swift	
@@ -0,0 +1,35 @@
+//
+//  LiveActivityManagementViewModel.swift
+//  Loop
+//
+//  Created by Bastiaan Verhaar on 12/09/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import Foundation
+import LoopCore
+
+class LiveActivityManagementViewModel : ObservableObject {
+    @Published var enabled: Bool
+    @Published var mode: LiveActivityMode
+    @Published var isEditingMode: Bool = false
+    @Published var addPredictiveLine: Bool
+    @Published var useLimits: Bool
+    @Published var upperLimitChartMmol: Double
+    @Published var lowerLimitChartMmol: Double
+    @Published var upperLimitChartMg: Double
+    @Published var lowerLimitChartMg: Double
+    
+    init() {
+        let liveActivitySettings = UserDefaults.standard.liveActivity ?? LiveActivitySettings()
+        
+        self.enabled = liveActivitySettings.enabled
+        self.mode = liveActivitySettings.mode
+        self.addPredictiveLine = liveActivitySettings.addPredictiveLine
+        self.useLimits = liveActivitySettings.useLimits
+        self.upperLimitChartMmol = liveActivitySettings.upperLimitChartMmol
+        self.lowerLimitChartMmol = liveActivitySettings.lowerLimitChartMmol
+        self.upperLimitChartMg = liveActivitySettings.upperLimitChartMg
+        self.lowerLimitChartMg = liveActivitySettings.lowerLimitChartMg
+    }
+}
diff --git a/Loop/Views/AlertManagementView.swift b/Loop/Views/AlertManagementView.swift
index e9a38e72a0..94e542a6ab 100644
--- a/Loop/Views/AlertManagementView.swift
+++ b/Loop/Views/AlertManagementView.swift
@@ -7,8 +7,10 @@
 //
 
 import SwiftUI
+import LoopCore
 import LoopKit
 import LoopKitUI
+import HealthKit
 
 struct AlertManagementView: View {
     @Environment(\.appName) private var appName
@@ -157,6 +159,11 @@ struct AlertManagementView: View {
                     }
                 }
             }
+            
+            NavigationLink(destination: LiveActivityManagementView())
+            {
+                    Text(NSLocalizedString("Live activity", comment: "Alert Permissions live activity"))
+            }
         }
     }
 
diff --git a/Loop/Views/LiveActivityBottomRowManagerView.swift b/Loop/Views/LiveActivityBottomRowManagerView.swift
new file mode 100644
index 0000000000..85a45b017f
--- /dev/null
+++ b/Loop/Views/LiveActivityBottomRowManagerView.swift
@@ -0,0 +1,133 @@
+//
+//  LiveActivityBottomRowManagerView.swift
+//  Loop
+//
+//  Created by Bastiaan Verhaar on 06/07/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import LoopKitUI
+import LoopCore
+import SwiftUI
+
+struct LiveActivityBottomRowManagerView: View {
+    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
+    
+    // The maximum items in the bottom row
+    private let maxSize = 4
+    
+    @State var showAdd: Bool = false
+    @State var configuration: [BottomRowConfiguration]
+    @State private var previousConfiguration: [BottomRowConfiguration]
+    @State private var isDirty = false
+    
+    init() {
+        configuration = (UserDefaults.standard.liveActivity ?? LiveActivitySettings()).bottomRowConfiguration
+        previousConfiguration = (UserDefaults.standard.liveActivity ?? LiveActivitySettings()).bottomRowConfiguration
+    }
+    
+    var addItem: ActionSheet {
+        var buttons: [ActionSheet.Button] = BottomRowConfiguration.all.map { item in
+            ActionSheet.Button.default(Text(item.description())) {
+                configuration.append(item)
+                
+                isDirty = configuration != previousConfiguration
+            }
+        }
+        buttons.append(.cancel(Text(NSLocalizedString("Cancel", comment: "Button text to cancel"))))
+        
+        return ActionSheet(title: Text(NSLocalizedString("Add item to bottom row", comment: "Title for Add item")), buttons: buttons)
+    }
+    
+    var body: some View {
+        List {
+            ForEach($configuration, id: \.self) { item in
+                HStack {
+                    deleteButton
+                        .onTapGesture {
+                            onDelete(item.wrappedValue)
+                            isDirty = configuration != previousConfiguration
+                        }
+                    Text(item.wrappedValue.description())
+                    
+                    Spacer()
+                    editBars
+                }
+            }
+            .onMove(perform: onReorder)
+            .deleteDisabled(true)
+            
+            Section {
+                Button(action: onSave) {
+                    Text(NSLocalizedString("Save", comment: ""))
+                }
+                .disabled(!isDirty)
+                .buttonStyle(ActionButtonStyle())
+                .listRowInsets(EdgeInsets())
+            }
+        }
+        .onAppear {
+            configuration = (UserDefaults.standard.liveActivity ?? LiveActivitySettings()).bottomRowConfiguration
+            previousConfiguration = (UserDefaults.standard.liveActivity ?? LiveActivitySettings()).bottomRowConfiguration
+        }
+        .toolbar {
+            ToolbarItem(placement: .navigationBarTrailing) {
+                Button(
+                    action: { showAdd = true },
+                    label: { Image(systemName: "plus") }
+                )
+                .disabled(configuration.count >= self.maxSize)
+            }
+        }
+        .actionSheet(isPresented: $showAdd, content: { addItem })
+        .insetGroupedListStyle()
+        .navigationBarTitle(Text(NSLocalizedString("Bottom row", comment: "Live activity Bottom row configuration title")))
+    }
+    
+    @ViewBuilder
+    private var deleteButton: some View {
+        ZStack {
+            Color.red
+                .clipShape(RoundedRectangle(cornerRadius: 12.5))
+                .frame(width: 20, height: 20)
+            
+            Image(systemName: "minus")
+                .foregroundColor(.white)
+        }
+        .contentShape(Rectangle())
+    }
+    
+    @ViewBuilder
+    private var editBars: some View {
+        Image(systemName: "line.3.horizontal")
+            .foregroundColor(Color(UIColor.tertiaryLabel))
+            .font(.title2)
+    }
+    
+    private func onSave() {
+        var settings = UserDefaults.standard.liveActivity ?? LiveActivitySettings()
+        settings.bottomRowConfiguration = configuration
+        
+        UserDefaults.standard.liveActivity = settings
+        NotificationCenter.default.post(name: .LiveActivitySettingsChanged, object: settings)
+        
+        self.presentationMode.wrappedValue.dismiss()
+    }
+    
+    func onReorder(from: IndexSet, to: Int) {
+        withAnimation {
+            configuration.move(fromOffsets: from, toOffset: to)
+            isDirty = configuration != previousConfiguration
+        }
+    }
+    
+    func onDelete(_ item: BottomRowConfiguration) {
+        withAnimation {
+            _ = configuration.remove(item)
+        }
+    }
+}
+
+#Preview {
+    LiveActivityBottomRowManagerView()
+}
diff --git a/Loop/Views/LiveActivityManagementView.swift b/Loop/Views/LiveActivityManagementView.swift
new file mode 100644
index 0000000000..bdf87dc553
--- /dev/null
+++ b/Loop/Views/LiveActivityManagementView.swift
@@ -0,0 +1,140 @@
+//
+//  LiveActivityManagementView.swift
+//  Loop
+//
+//  Created by Bastiaan Verhaar on 04/07/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import SwiftUI
+import LoopKitUI
+import LoopCore
+import HealthKit
+
+struct LiveActivityManagementView: View {
+    @EnvironmentObject private var displayGlucosePreference: DisplayGlucosePreference
+    @StateObject private var viewModel = LiveActivityManagementViewModel()
+    @State private var previousViewModel = LiveActivityManagementViewModel()
+    
+    @State private var isDirty = false
+   
+    var body: some View {
+        VStack {
+            List {
+                Section {
+                    Toggle(NSLocalizedString("Enabled", comment: "Title for enable live activity toggle"), isOn: $viewModel.enabled)
+                        .onChange(of: viewModel.enabled) { _ in
+                            self.isDirty = previousViewModel.enabled != viewModel.enabled
+                        }
+                    
+                    ExpandableSetting(
+                        isEditing: $viewModel.isEditingMode,
+                        leadingValueContent: {
+                            Text(NSLocalizedString("Mode", comment: "Title for mode live activity toggle"))
+                                .foregroundStyle(viewModel.isEditingMode ? .blue : .primary)
+                        },
+                        trailingValueContent: {
+                            Text(viewModel.mode.name())
+                                .foregroundStyle(viewModel.isEditingMode ? .blue : .primary)
+                        },
+                        expandedContent: {
+                            ResizeablePicker(selection: self.$viewModel.mode.animation(),
+                                             data: LiveActivityMode.all,
+                                             formatter: { $0.name() })
+                        }
+                    )
+                    .onChange(of: viewModel.mode) { _ in
+                        self.isDirty = previousViewModel.mode != viewModel.mode
+                    }
+                }
+                
+                Section {
+                    if viewModel.mode == .large {
+                        Toggle(NSLocalizedString("Add predictive line", comment: "Title for predictive line toggle"), isOn: $viewModel.addPredictiveLine)
+                            .transition(.move(edge: viewModel.mode == .large ? .top : .bottom))
+                            .onChange(of: viewModel.addPredictiveLine) { _ in
+                                self.isDirty = previousViewModel.addPredictiveLine != viewModel.addPredictiveLine
+                            }
+                    }
+                    
+                    Toggle(NSLocalizedString("Use BG coloring", comment: "Title for BG coloring"), isOn: $viewModel.useLimits)
+                        .transition(.move(edge: viewModel.mode == .large ? .top : .bottom))
+                        .onChange(of: viewModel.useLimits) { _ in
+                            self.isDirty = previousViewModel.useLimits != viewModel.useLimits
+                        }
+                    
+                    if self.displayGlucosePreference.unit == .millimolesPerLiter {
+                        TextInput(label: "Upper limit", value: $viewModel.upperLimitChartMmol)
+                            .transition(.move(edge: viewModel.useLimits ? .top : .bottom))
+                            .onChange(of: viewModel.upperLimitChartMmol) { _ in
+                                self.isDirty = previousViewModel.upperLimitChartMmol != viewModel.upperLimitChartMmol
+                            }
+                        TextInput(label: "Lower limit", value: $viewModel.lowerLimitChartMmol)
+                            .transition(.move(edge: viewModel.useLimits ? .top : .bottom))
+                            .onChange(of: viewModel.lowerLimitChartMmol) { _ in
+                                self.isDirty = previousViewModel.lowerLimitChartMmol != viewModel.lowerLimitChartMmol
+                            }
+                    } else {
+                        TextInput(label: "Upper limit", value: $viewModel.upperLimitChartMg)
+                            .transition(.move(edge: viewModel.useLimits ? .top : .bottom))
+                            .onChange(of: viewModel.upperLimitChartMg) { _ in
+                                self.isDirty = previousViewModel.upperLimitChartMg != viewModel.upperLimitChartMg
+                            }
+                        TextInput(label: "Lower limit", value: $viewModel.lowerLimitChartMg)
+                            .transition(.move(edge: viewModel.useLimits ? .top : .bottom))
+                            .onChange(of: viewModel.lowerLimitChartMg) { _ in
+                                self.isDirty = previousViewModel.lowerLimitChartMg != viewModel.lowerLimitChartMg
+                            }
+                    }
+                }
+                
+                Section {
+                    NavigationLink(
+                        destination: LiveActivityBottomRowManagerView(),
+                        label: { Text(NSLocalizedString("Bottom row configuration", comment: "Title for Bottom row configuration")) }
+                    )
+                }
+            }
+            .animation(.easeInOut, value: UUID())
+            .insetGroupedListStyle()
+            
+            Spacer()
+            Button(action: save) {
+                Text(NSLocalizedString("Save", comment: ""))
+            }
+            .buttonStyle(ActionButtonStyle())
+            .disabled(!isDirty)
+            .padding([.bottom, .horizontal])
+        }
+            .navigationBarTitle(Text(NSLocalizedString("Live activity", comment: "Live activity screen title")))
+    }
+    
+    @ViewBuilder
+    private func TextInput(label: String, value: Binding<Double>) -> some View {
+        HStack {
+            Text(NSLocalizedString(label, comment: "no comment"))
+            Spacer()
+            TextField("", value: value, format: .number)
+                .multilineTextAlignment(.trailing)
+            Text(self.displayGlucosePreference.unit.localizedShortUnitString)
+        }
+    }
+    
+    private func save() {
+        var settings = UserDefaults.standard.liveActivity ?? LiveActivitySettings()
+        settings.enabled = viewModel.enabled
+        settings.mode = viewModel.mode
+        settings.addPredictiveLine = viewModel.addPredictiveLine
+        settings.useLimits = viewModel.useLimits
+        settings.upperLimitChartMmol = viewModel.upperLimitChartMmol
+        settings.lowerLimitChartMmol = viewModel.lowerLimitChartMmol
+        settings.upperLimitChartMg = viewModel.upperLimitChartMg
+        settings.lowerLimitChartMg = viewModel.lowerLimitChartMg
+        
+        UserDefaults.standard.liveActivity = settings
+        NotificationCenter.default.post(name: .LiveActivitySettingsChanged, object: settings)
+        
+        self.isDirty = false
+        previousViewModel = LiveActivityManagementViewModel()
+    }
+}
diff --git a/LoopCore/LiveActivitySettings.swift b/LoopCore/LiveActivitySettings.swift
new file mode 100644
index 0000000000..d0f892f7e8
--- /dev/null
+++ b/LoopCore/LiveActivitySettings.swift
@@ -0,0 +1,159 @@
+//
+//  LiveActivitySettings.swift
+//  LoopCore
+//
+//  Created by Bastiaan Verhaar on 04/07/2024.
+//  Copyright © 2024 LoopKit Authors. All rights reserved.
+//
+
+import Foundation
+
+public enum BottomRowConfiguration: Codable {
+    case iob
+    case cob
+    case basal
+    case currentBg
+    case eventualBg
+    case deltaBg
+    case loopCircle
+    case updatedAt
+    
+    static let defaults: [BottomRowConfiguration] =  [.currentBg, .iob, .cob, .updatedAt]
+    public static let all: [BottomRowConfiguration] = [.iob, .cob, .basal, .currentBg, .eventualBg, .deltaBg, .loopCircle, .updatedAt]
+    
+    public func name() -> String {
+        switch self {
+        case .iob:
+            return NSLocalizedString("IOB", comment: "")
+        case .cob:
+            return NSLocalizedString("COB", comment: "")
+        case .basal:
+            return NSLocalizedString("Basal", comment: "")
+        case .currentBg:
+            return NSLocalizedString("Current BG", comment: "")
+        case .eventualBg:
+            return NSLocalizedString("Event", comment: "")
+        case .deltaBg:
+            return NSLocalizedString("Delta", comment: "")
+        case .loopCircle:
+            return NSLocalizedString("Loop", comment: "")
+        case .updatedAt:
+            return NSLocalizedString("Updated", comment: "")
+        }
+    }
+    
+    public func description() -> String {
+        switch self {
+        case .iob:
+            return NSLocalizedString("Active Insulin", comment: "")
+        case .cob:
+            return NSLocalizedString("Active Carbohydrates", comment: "")
+        case .basal:
+            return NSLocalizedString("Basal", comment: "")
+        case .currentBg:
+            return NSLocalizedString("Current Glucose", comment: "")
+        case .eventualBg:
+            return NSLocalizedString("Eventually", comment: "")
+        case .deltaBg:
+            return NSLocalizedString("Delta", comment: "")
+        case .loopCircle:
+            return NSLocalizedString("Loop circle", comment: "")
+        case .updatedAt:
+            return NSLocalizedString("Updated at", comment: "")
+        }
+    }
+}
+
+public enum LiveActivityMode: Codable, CustomStringConvertible {
+    case large
+    case small
+    
+    public static let all: [LiveActivityMode] = [.large, .small]
+    public var description: String {
+        NSLocalizedString("In which mode do you want to render the Live Activity", comment: "")
+    }
+    
+    public func name() -> String {
+        switch self {
+        case .large:
+            return NSLocalizedString("Large", comment: "")
+        case .small:
+            return NSLocalizedString("Small", comment: "")
+        }
+    }
+}
+
+public struct LiveActivitySettings: Codable, Equatable {
+    public var enabled: Bool
+    public var mode: LiveActivityMode
+    public var addPredictiveLine: Bool
+    public var useLimits: Bool
+    public var upperLimitChartMmol: Double
+    public var lowerLimitChartMmol: Double
+    public var upperLimitChartMg: Double
+    public var lowerLimitChartMg: Double
+    public var bottomRowConfiguration: [BottomRowConfiguration]
+    
+    private enum CodingKeys: String, CodingKey {
+        case enabled
+        case mode
+        case addPredictiveLine
+        case bottomRowConfiguration
+        case useLimits
+        case upperLimitChartMmol
+        case lowerLimitChartMmol
+        case upperLimitChartMg
+        case lowerLimitChartMg
+    }
+    
+    private static let defaultUpperLimitMmol = Double(10)
+    private static let defaultLowerLimitMmol = Double(4)
+    private static let defaultUpperLimitMg = Double(180)
+    private static let defaultLowerLimitMg = Double(72)
+    
+    public init(from decoder:Decoder) throws {
+        let values = try decoder.container(keyedBy: CodingKeys.self)
+
+        self.enabled = try values.decode(Bool.self, forKey: .enabled)
+        self.mode = try values.decodeIfPresent(LiveActivityMode.self, forKey: .mode) ?? .large
+        self.addPredictiveLine = try values.decode(Bool.self, forKey: .addPredictiveLine)
+        self.useLimits = try values.decode(Bool.self, forKey: .useLimits)
+        self.upperLimitChartMmol = try values.decode(Double?.self, forKey: .upperLimitChartMmol) ?? LiveActivitySettings.defaultUpperLimitMmol
+        self.lowerLimitChartMmol = try values.decode(Double?.self, forKey: .lowerLimitChartMmol) ?? LiveActivitySettings.defaultLowerLimitMmol
+        self.upperLimitChartMg = try values.decode(Double?.self, forKey: .upperLimitChartMg) ?? LiveActivitySettings.defaultUpperLimitMg
+        self.lowerLimitChartMg = try values.decode(Double?.self, forKey: .lowerLimitChartMg) ?? LiveActivitySettings.defaultLowerLimitMg
+        self.bottomRowConfiguration = try values.decode([BottomRowConfiguration].self, forKey: .bottomRowConfiguration)
+    }
+    
+    public init() {
+        self.enabled = true
+        self.mode = .large
+        self.addPredictiveLine = true
+        self.useLimits = true
+        self.upperLimitChartMmol = LiveActivitySettings.defaultUpperLimitMmol
+        self.lowerLimitChartMmol = LiveActivitySettings.defaultLowerLimitMmol
+        self.upperLimitChartMg = LiveActivitySettings.defaultUpperLimitMg
+        self.lowerLimitChartMg = LiveActivitySettings.defaultLowerLimitMg
+        self.bottomRowConfiguration = BottomRowConfiguration.defaults
+    }
+    
+    public static func == (lhs: LiveActivitySettings, rhs: LiveActivitySettings) -> Bool {
+        return lhs.addPredictiveLine == rhs.addPredictiveLine &&
+            lhs.mode == rhs.mode &&
+            lhs.useLimits == rhs.useLimits &&
+            lhs.lowerLimitChartMmol == rhs.lowerLimitChartMmol &&
+            lhs.upperLimitChartMmol == rhs.upperLimitChartMmol &&
+            lhs.lowerLimitChartMg == rhs.lowerLimitChartMg &&
+            lhs.upperLimitChartMg == rhs.upperLimitChartMg
+    }
+    
+    public static func != (lhs: LiveActivitySettings, rhs: LiveActivitySettings) -> Bool {
+        return lhs.addPredictiveLine != rhs.addPredictiveLine ||
+            lhs.mode != rhs.mode ||
+            lhs.useLimits != rhs.useLimits ||
+            lhs.lowerLimitChartMmol != rhs.lowerLimitChartMmol ||
+            lhs.upperLimitChartMmol != rhs.upperLimitChartMmol ||
+            lhs.lowerLimitChartMg != rhs.lowerLimitChartMg ||
+            lhs.upperLimitChartMg != rhs.upperLimitChartMg
+    }
+}
diff --git a/LoopCore/NSUserDefaults.swift b/LoopCore/NSUserDefaults.swift
index 93fa7e17d6..dacf2ecdc7 100644
--- a/LoopCore/NSUserDefaults.swift
+++ b/LoopCore/NSUserDefaults.swift
@@ -23,6 +23,7 @@ extension UserDefaults {
         case allowSimulators = "com.loopkit.Loop.allowSimulators"
         case LastMissedMealNotification = "com.loopkit.Loop.lastMissedMealNotification"
         case userRequestedLoopReset = "com.loopkit.Loop.userRequestedLoopReset"
+        case liveActivity = "com.loopkit.Loop.liveActivity"
     }
 
     public static let appGroup = UserDefaults(suiteName: Bundle.main.appGroupSuiteName)
@@ -165,6 +166,29 @@ extension UserDefaults {
             setValue(newValue, forKey: Key.userRequestedLoopReset.rawValue)
         }
     }
+    
+    public var liveActivity: LiveActivitySettings? {
+        get {
+            let decoder = JSONDecoder()
+            guard let data = object(forKey: Key.liveActivity.rawValue) as? Data else {
+                return nil
+            }
+            return try? decoder.decode(LiveActivitySettings.self, from: data)
+        }
+        set {
+            do {
+                if let newValue = newValue {
+                    let encoder = JSONEncoder()
+                    let data = try encoder.encode(newValue)
+                    set(data, forKey: Key.liveActivity.rawValue)
+                } else {
+                    set(nil, forKey: Key.liveActivity.rawValue)
+                }
+            } catch {
+                assertionFailure("Unable to encode MissedMealNotification")
+            }
+        }
+    }
 
     public func removeLegacyLoopSettings() {
         removeObject(forKey: "com.loudnate.Naterade.BasalRateSchedule")