Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI/notifications #10

Merged
merged 48 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e462cf8
Update build-and-test.yml
nriedman Apr 12, 2024
61ced93
Basic Bluetooth infrastructure
nriedman Apr 15, 2024
0df9088
WeightScaleService Characteristics
nriedman Apr 17, 2024
c4dd341
Create FindDevices view
nriedman Apr 17, 2024
658058f
Update Package.resolved
nriedman Apr 22, 2024
ea6390c
Auto Connect Weight Scale
nriedman May 2, 2024
eb11635
Undo temporary entitlement changes
nriedman May 2, 2024
56c673d
Finish LoadMeasurement
nriedman May 3, 2024
7e0f4d5
Save and display recorded weight measurements
nriedman May 12, 2024
cc41782
Delete ENGAGEHFDebug.entitlements
nriedman May 12, 2024
3f7f107
Deleted BPCuffDevice.swift
nriedman May 12, 2024
6689d4e
Center title in measurement recorded veiw
nriedman May 13, 2024
2625528
Create basic notification UI elements
nriedman May 13, 2024
b0946a5
Basic UI Infrastructure
nriedman May 14, 2024
5ff379b
Update Package.resolved
nriedman May 20, 2024
83dfee1
Update ENGAGEHFDelegate.swift
nriedman May 20, 2024
3dc03c2
Implement NotificationManager
nriedman May 21, 2024
19f23d5
Change emulator host to localhost
nriedman May 21, 2024
ab13039
Remove SpeziFHIR as a dependency
nriedman May 22, 2024
098ce67
Update Package.resolved
nriedman May 22, 2024
6d5a422
Merge branch 'main' into UI/notifications
nriedman May 22, 2024
8584fff
Smooth quirks from merge w main
nriedman May 22, 2024
8bb5d71
Update Package.resolved
nriedman May 22, 2024
a62e8fe
Finish UI for notitifications
nriedman May 31, 2024
189c9f9
Credit StudyApplicationListCard
nriedman May 31, 2024
007e019
linting
nriedman May 31, 2024
db368d8
Merge branch 'main' into UI/notifications
PSchmiedmayer May 31, 2024
22fb6d1
Addressing PR comments
nriedman May 31, 2024
6faeb9c
Merge branch 'UI/notifications' of https://github.com/StanfordBDHG/EN…
nriedman May 31, 2024
1a8e9a1
Dev code is ENGAGETEST1 not ENGAGEHFTEST1
nriedman May 31, 2024
a64a7ab
Improve notification card UI
nriedman May 31, 2024
a04ab1e
Further update the notification UI Cards
nriedman May 31, 2024
797fac3
Change notification indexing to id-based, testing
nriedman Jun 11, 2024
e045231
Merge branch 'main' into UI/notifications
nriedman Jun 11, 2024
d9fb0bd
cleanup after merging main
nriedman Jun 11, 2024
1f657e2
more post merge cleanup
nriedman Jun 11, 2024
0080342
UI Testing
nriedman Jun 11, 2024
ae10660
undo development team changes
nriedman Jun 11, 2024
9a71223
Add created and completed to Notification class
nriedman Jun 11, 2024
fe1e2ed
Add "completed" and "created" fields to Notifs
nriedman Jun 11, 2024
9386f95
UI Test debugging
nriedman Jun 13, 2024
b9077ec
UI Tests done
nriedman Jun 13, 2024
7cac1eb
Remove To-dos
nriedman Jun 13, 2024
933f8b0
Remove To-dos
nriedman Jun 13, 2024
83d9fd9
remove non-functional elements
nriedman Jun 13, 2024
3bf9e00
add PI contact to education page
nriedman Jun 13, 2024
671d61b
Merge branch 'main' into UI/notifications
PSchmiedmayer Jun 14, 2024
cbcae79
Fix minor issues
PSchmiedmayer Jun 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 156 additions & 12 deletions ENGAGEHF.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@
{
"identity" : "spezifoundation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/StanfordSpezi/SpeziFoundation",
"location" : "https://github.com/StanfordSpezi/SpeziFoundation.git",
"state" : {
"revision" : "01af5b91a54f30ddd121258e81aff2ddc2a99ff9",
"version" : "1.0.4"
Expand Down
2 changes: 1 addition & 1 deletion ENGAGEHF/Bluetooth/MeasurementManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class MeasurementManager: Module, EnvironmentAccessible {
var deviceName: String?

var newMeasurement: HKQuantitySample?


init() {
MeasurementManager._manager = self
Expand Down
15 changes: 9 additions & 6 deletions ENGAGEHF/Bluetooth/Views/ViewElements.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ struct CloseButtonLayer: View {

var body: some View {
HStack {
Button(action: {
dismiss()
}) {
Text(NSLocalizedString("Close", comment: "For closing sheets."))
.foregroundStyle(Color.accentColor)
}
Button(
action: {
dismiss()
},
label: {
Text(NSLocalizedString("Close", comment: "For closing sheets."))
.foregroundStyle(Color.accentColor)
}
)
.buttonStyle(PlainButtonStyle())
.disabled(viewState != .idle)

Expand Down
36 changes: 28 additions & 8 deletions ENGAGEHF/Dashboard/Dashboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,45 @@ import SwiftUI

struct Dashboard: View {
@Binding var presentingAccount: Bool
@State var showSurvey = false


var body: some View {
NavigationStack {
VStack {
Greeting()
Spacer()
List {
// Notifications
NotificationSection()

// To-do
ToDoSection()

// Most recent vitals
RecentVitalsSection()

// Survey, if available
SurveySection(showFullSurvey: $showSurvey)
}
.navigationTitle("Home")
.toolbar {
if AccountButton.shouldDisplay {
AccountButton(isPresented: $presentingAccount)
}

.studyApplicationList()

.sheet(isPresented: $showSurvey, content: {
FullSurveyView()
})

.navigationTitle("Home")
.toolbar {
if AccountButton.shouldDisplay {
AccountButton(isPresented: $presentingAccount)
}
}
}
}
}


#Preview {
Dashboard(presentingAccount: .constant(false))
.previewWith(standard: ENGAGEHFStandard()) {
NotificationManager()
}
}
24 changes: 24 additions & 0 deletions ENGAGEHF/Dashboard/FetchingError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import Foundation


enum FetchingError: LocalizedError {
case invalidTimestamp
case userNotAuthenticated

var errorDescription: String? {
switch self {
case .invalidTimestamp:
String(localized: "Unable to get notification timestamp.", comment: "Invalid Timestamp")
case .userNotAuthenticated:
String(localized: "User not authenticated. Please sign in an try again.", comment: "User Not Authenticated")
}
}
}
29 changes: 0 additions & 29 deletions ENGAGEHF/Dashboard/Greeting.swift

This file was deleted.

32 changes: 32 additions & 0 deletions ENGAGEHF/Dashboard/Notifications/Notification.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import Foundation


// A notification
//
// Mirrors the representation of a notification in firestore
// When assigned to a patient, the title will be displayed
// and the description will be displayed in a drop-down field
//
// Title and Descprition may be markdown text
struct Notification: Identifiable, Equatable {
var id: String

var type: String
var title: String
var description: String

init(type: String, title: String, description: String, id: String) {
self.id = id
self.type = type
self.title = title
self.description = description
}
}
158 changes: 158 additions & 0 deletions ENGAGEHF/Dashboard/Notifications/NotificationManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//
// This source file is part of the ENGAGE-HF project based on the Stanford Spezi Template Application project
//
// SPDX-FileCopyrightText: 2023 Stanford University
//
// SPDX-License-Identifier: MIT
//

import FirebaseAuth
import FirebaseFirestore
import Foundation
import OSLog
import Spezi
import SpeziFirebaseConfiguration


//
// Notification manager
//
// Maintains a list of Notifications associated with the current user in firebase
//
// Includes functionality for adding notifications and marking them complete
// On configuration of the app, adds a snapshot listener to the user's notification collection
//
@Observable
class NotificationManager: Module, EnvironmentAccessible {
@ObservationIgnored @Dependency private var configureFirebaseApp: ConfigureFirebaseApp
@ObservationIgnored @StandardActor var standard: ENGAGEHFStandard

private var authStateDidChangeListenerHandle: AuthStateDidChangeListenerHandle?
private var snapshotListener: ListenerRegistration?

private let logger = Logger(subsystem: "ENGAGEHF", category: "NotificationManager")

private let expirationDate = 10

var notifications: [Notification] = []


func configure() {
if ProcessInfo.processInfo.isPreviewSimulator {
let dummyNotification = Notification(
type: "Mock Notification",
title: "Weight Recorded",
description: "A weight measurement has been recorded.",
id: String(describing: UUID())
)
notifications.append(dummyNotification)
return
}

authStateDidChangeListenerHandle = Auth.auth().addStateDidChangeListener { [weak self] _, user in
self?.registerSnapshotListener(user: user)
}
self.registerSnapshotListener(user: Auth.auth().currentUser)
}

// Call on initialization
//
// Creates a snapshot listener to save new notifications to the manager
// as they are added to the user's directory in Firebase
func registerSnapshotListener(user: User?) {
logger.info("Initializing notifiation snapshot listener...")

// Remove previous snapshot listener for the user before creating new one
snapshotListener?.remove()
guard let uid = user?.uid else {
return
}

let db = Firestore.firestore()

Check failure on line 71 in ENGAGEHF/Dashboard/Notifications/NotificationManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Identifier Name Violation: Variable name 'db' should be between 3 and 40 characters long (identifier_name)

// Ignore notifications older than expirationDate
guard let thresholdDate = Calendar.current.date(byAdding: .day, value: -expirationDate, to: .now) else {
logger.error("Unable to get threshold date: \(FetchingError.invalidTimestamp)")
return
}

let thesholdTimeStamp = Timestamp(date: thresholdDate)

// Set a snapshot listener on the query for valid notifications
db.collection("users").document(uid).collection("notifications")

Check failure on line 82 in ENGAGEHF/Dashboard/Notifications/NotificationManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Multiline Function Chains Violation: Chained function calls should be either on the same line, or one per line (multiline_function_chains)

Check failure on line 82 in ENGAGEHF/Dashboard/Notifications/NotificationManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Multiline Function Chains Violation: Chained function calls should be either on the same line, or one per line (multiline_function_chains)
.whereField("created", isGreaterThan: thesholdTimeStamp)
.addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
self.logger.error("Error fetching documents: \(error!)")

Check failure on line 86 in ENGAGEHF/Dashboard/Notifications/NotificationManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Force Unwrapping Violation: Force unwrapping should be avoided (force_unwrapping)
return
}

self.notifications = documents.compactMap {
if $0.get("completed") == nil {
return Notification(
type: String(describing: $0["type"] ?? "Unknown"),
title: String(describing: $0["title"] ?? "Unknown"),
description: String(describing: $0["description"] ?? "Unknown"),
id: $0.documentID
)
}
return nil
}

self.logger.debug("Notifications updated")
}
}

func add(_ notification: Notification) async {
await standard.add(notification: notification)
}

func markComplete(at offsets: IndexSet) async {
if ProcessInfo.processInfo.isPreviewSimulator {
notifications.remove(atOffsets: offsets)
return
}

logger.debug("Marking notifications complete at the following offsets: \(offsets)")

let db = Firestore.firestore()

Check failure on line 118 in ENGAGEHF/Dashboard/Notifications/NotificationManager.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Identifier Name Violation: Variable name 'db' should be between 3 and 40 characters long (identifier_name)

guard let user = Auth.auth().currentUser else {
logger.error("Unable to mark notitificaitons complete: \(FetchingError.userNotAuthenticated)")
return
}

// Mark the notifications as completed in the Firestore
let timestamp = Timestamp(date: .now)
for offset in offsets {
let docID = notifications[offset].id

let docRef = db.collection("users").document(user.uid).collection("notifications")
.document(docID)

do {
try await docRef.updateData([
"completed": timestamp
])
} catch {
logger.error("Unable to update notification at offset \(offset): \(error)")
}
}

logger.info("Successfully marked notifications complete!")
}
}


extension NotificationManager {
// Function for adding a mock notification for the preview simulator
func addMock() {
let dummyNotification = Notification(
type: "Medication Change",
title: "Your dose of XXX was changed.",
description: "Your dose of XXX was changed. You can review medication information in the Education Page.",
id: String(describing: UUID())
)
notifications.append(dummyNotification)
}
}
Loading
Loading