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

Implementation of a progress Indicator for Input-Related Messages #138

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bfb3664
Introduction of Progress Indicator, preview of #118 solution
PaulGoldschmidt Jan 5, 2025
aa4f1d9
disable linter body length check
PaulGoldschmidt Jan 12, 2025
cac4735
resolve actor isolation issue
PaulGoldschmidt Jan 12, 2025
9535e0b
removed content order violation
PaulGoldschmidt Jan 12, 2025
cd0086b
refreshContent added, remove "await" unnecessarily
PaulGoldschmidt Jan 12, 2025
9e532e7
1-second delay after submission before dismissal
PaulGoldschmidt Jan 15, 2025
4ea9b21
Merge branch 'main' into main
PSchmiedmayer Jan 18, 2025
23b6725
Merge branch 'main' into main
PSchmiedmayer Jan 18, 2025
1d9870c
Add Preview to Test PR
PSchmiedmayer Jan 18, 2025
d45ce79
Merge branch 'main' into main
PSchmiedmayer Jan 23, 2025
ba66177
Update ENGAGEHF/Questionnaire/QuestionnaireSheetView.swift
PaulGoldschmidt Feb 17, 2025
50ae6dc
using for: .seconds() instead of nanoseconds
PaulGoldschmidt Feb 17, 2025
f3bc342
Update ENGAGEHF/Dashboard/Messages/MessageRow.swift
PaulGoldschmidt Feb 17, 2025
d3e4ebd
Update ENGAGEHF/Managers/MessageManager/Message.swift
PaulGoldschmidt Feb 17, 2025
8e2ab27
Indentation fix
PaulGoldschmidt Feb 17, 2025
18caaff
Update ENGAGEHF/Managers/MessageManager/ProcessingState.swift
PaulGoldschmidt Feb 17, 2025
33abce6
revert styling changes in whitespaces
PaulGoldschmidt Feb 17, 2025
69540f6
Merge branch 'main' of https://github.com/PaulGoldschmidt/ENGAGE-HF-i…
PaulGoldschmidt Feb 17, 2025
d78345a
Indentation fix
PaulGoldschmidt Feb 17, 2025
a251efe
minor code styling
PaulGoldschmidt Feb 17, 2025
af5c332
fix further styling issues
PaulGoldschmidt Feb 17, 2025
74df0a1
safely access isStillProcessing, using Date.now
PaulGoldschmidt Feb 17, 2025
cdbb080
move isRelatedTo function, fix Type Contents Order Violation
PaulGoldschmidt Feb 17, 2025
a79a3f4
remove unnecessary code
PaulGoldschmidt Feb 18, 2025
32cf18b
processing states are now stored in MessageManager
PaulGoldschmidt Feb 18, 2025
1bd232a
add test for UI
PaulGoldschmidt Feb 18, 2025
af5018c
Multilang processing state
PaulGoldschmidt Feb 18, 2025
90e4dcf
Change test plan to english locale
PaulGoldschmidt Feb 19, 2025
2c2cfdb
Indentation fix
PaulGoldschmidt Feb 25, 2025
0670641
indentation fix
PaulGoldschmidt Feb 25, 2025
4178bc8
Removal of the UI test for questionnaire
PaulGoldschmidt Feb 25, 2025
9be82f9
Fix message processing indicator by adding test-aware questionnaire I…
PaulGoldschmidt Feb 27, 2025
4daa447
removed unnecessary code / fix periphery warnings
PaulGoldschmidt Feb 28, 2025
2466f06
Merge branch 'main' into feature/process-indicator
PaulGoldschmidt Feb 28, 2025
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
4 changes: 4 additions & 0 deletions ENGAGEHF.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@
653A255528338800005D4D48 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 653A255428338800005D4D48 /* Assets.xcassets */; };
653A256228338800005D4D48 /* VitalsGraphAggregationUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 653A256128338800005D4D48 /* VitalsGraphAggregationUnitTests.swift */; };
65D39ABD2CE497630040BF71 /* SpeziNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = 65D39ABC2CE497630040BF71 /* SpeziNotifications */; };
76D18D1F2D2B2C8A00053BE1 /* ProcessingState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76D18D1E2D2B2C8600053BE1 /* ProcessingState.swift */; };
9733CFC62A8066DE001B7ABC /* SpeziOnboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 2FE5DC8029EDD91D004B9AB4 /* SpeziOnboarding */; };
9739A0C62AD7B5730084BEA5 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 9739A0C52AD7B5730084BEA5 /* FirebaseStorage */; };
97D73D6A2AD860AD00B47FA0 /* SpeziFirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 97D73D692AD860AD00B47FA0 /* SpeziFirebaseStorage */; };
Expand Down Expand Up @@ -429,6 +430,7 @@
653A256128338800005D4D48 /* VitalsGraphAggregationUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VitalsGraphAggregationUnitTests.swift; sourceTree = "<group>"; };
653A256728338800005D4D48 /* ENGAGEHFUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ENGAGEHFUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
653A258928339462005D4D48 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
76D18D1E2D2B2C8600053BE1 /* ProcessingState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessingState.swift; sourceTree = "<group>"; };
9B1864872C9226E90042AC81 /* AccountDetails+Key.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountDetails+Key.swift"; sourceTree = "<group>"; };
9B18648A2C9227040042AC81 /* AccountNotifications+Extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AccountNotifications+Extras.swift"; sourceTree = "<group>"; };
9B1864932C9276C40042AC81 /* AuthFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthFlow.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1062,6 +1064,7 @@
4DF5061E2C2E1C10003E7EFB /* MessageManager */ = {
isa = PBXGroup;
children = (
76D18D1E2D2B2C8600053BE1 /* ProcessingState.swift */,
4D734AC22C5D7C81002D6D2F /* MessageAction.swift */,
4DDFC73C2BF2C4FA002B07A1 /* Message.swift */,
4DDFC7862BFBFDDD002B07A1 /* MessageManager.swift */,
Expand Down Expand Up @@ -1403,6 +1406,7 @@
4D8402B22C51D62400817495 /* MedicationsList.swift in Sources */,
4DC990612C7D3A13001E86C5 /* ClosedRange+InitFromSequence.swift in Sources */,
4D73A5112C58088E00CCEC46 /* PositionedCurrentLabel.swift in Sources */,
76D18D1F2D2B2C8A00053BE1 /* ProcessingState.swift in Sources */,
4D8AD2C22C49D51E00CB4F3E /* SeriesDictionary.swift in Sources */,
4D4072A42C49A07C007C5621 /* HKSampleGraph+ViewModel.swift in Sources */,
4DA20B642C2A0FF100715AA2 /* VitalsCard.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions ENGAGEHF.xctestplan
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
}
]
},
"language" : "en",
"targetForVariableExpansion" : {
"containerPath" : "container:ENGAGEHF.xcodeproj",
"identifier" : "653A254C283387FE005D4D48",
Expand Down
184 changes: 114 additions & 70 deletions ENGAGEHF/Dashboard/Messages/MessageRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// SPDX-License-Identifier: MIT
//

@_spi(TestingSupport) import SpeziAccount
import SpeziViews
import SwiftUI

Expand Down Expand Up @@ -60,54 +61,95 @@
.accessibilityLabel(message.action.localizedDescription.localizedString() + " Symbol")
}

private var processingStateView: some View {
HStack(spacing: 8) {
ProgressView()
.controlSize(.small)
Text(processingStateText)
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.vertical, 4)
.padding(.horizontal, 8)
.background {
Capsule()
.fill(.secondary.opacity(0.1))
}
}

private var processingStateText: String {
if let processingState = messageManager.processingState(for: message) {
return processingState.type.localizedDescription
}
return String(localized: "Processing...", comment: "Processing state")

Check warning on line 84 in ENGAGEHF/Dashboard/Messages/MessageRow.swift

View check run for this annotation

Codecov / codecov/patch

ENGAGEHF/Dashboard/Messages/MessageRow.swift#L84

Added line #L84 was not covered by tests
}

private var isProcessing: Bool {
messageManager.processingState(for: message)?.isStillProcessing ?? false
}

private var titleRow: some View {
HStack(alignment: .top) {
HStack(alignment: .center, spacing: 8) {
Text(message.title)
.bold()
}
.font(.subheadline)
Spacer()
if message.isDismissible && !isProcessing {
XButton(message: message, labelSize: dismissLabelSize)
}
}
}

private var mainContent: some View {
VStack(alignment: .leading, spacing: spacing) {
titleRow

if let description = message.description {
ExpandableText(text: description, lineLimit: 3)
.font(.footnote)
.accessibilityIdentifier("Message Description")
}

if isProcessing {
processingStateView
.accessibilityIdentifier("Processing State")
} else if message.action != .unknown {
Text(message.action.localizedDescription)
.padding(.vertical, 4)
.padding(.horizontal, 8)
.background {
Capsule()
.fill(.accent.opacity(0.7))
}
.font(.caption.weight(.heavy))
.foregroundStyle(.white)
.accessibilityIdentifier("Message Action")
}
}
}


var body: some View {
HStack(alignment: .top, spacing: 16) {
actionImage
.foregroundStyle(.accent)
.frame(width: 38)
VStack(alignment: .leading, spacing: spacing) {
HStack(alignment: .top) {
HStack(alignment: .center, spacing: 8) {
Text(message.title)
.bold()
}
.font(.subheadline)
Spacer()
if message.isDismissible {
XButton(message: message, labelSize: dismissLabelSize)
}
}
if let description = message.description {
ExpandableText(text: description, lineLimit: 3)
.font(.footnote)
.accessibilityIdentifier("Message Description")
}
if message.action != .unknown {
Text(message.action.localizedDescription)
.padding(.vertical, 4)
.padding(.horizontal, 8)
.background {
Capsule()
.fill(.accent.opacity(0.7))
}
.font(.caption.weight(.heavy))
.foregroundStyle(.white)
.accessibilityIdentifier("Message Action")
}
}
mainContent
}
.padding(2)
.asButton {
if message.action != .unknown {
Task {
let didPerformAction = await navigationManager.execute(message.action)
if message.isDismissible, didPerformAction {
await messageManager.dismiss(message, didPerformAction: didPerformAction)
}
.padding(2)
.asButton {
if message.action != .unknown && !isProcessing {
Task {
let didPerformAction = await navigationManager.execute(message.action)
if message.isDismissible, didPerformAction {
await messageManager.dismiss(message, didPerformAction: didPerformAction)
}
}
}
}
.disabled(isProcessing)
}
}

Expand All @@ -117,49 +159,51 @@
struct MessageRowPreviewWrapper: View {
@Environment(MessageManager.self) private var messageManager


var body: some View {
List {
Section(
content: {
ForEach(messageManager.messages) { message in
StudyApplicationListCard {
MessageRow(message: message)
NavigationStack {
List {
Section(
content: {
ForEach(messageManager.messages) { message in
StudyApplicationListCard {
MessageRow(message: message)
}
}
}
.buttonStyle(.borderless)
},
header: {
Text("Messages")
.studyApplicationHeaderStyle()
}
)
Section(
content: {
StudyApplicationListCard {
Button(
action: {
messageManager.addMockMessage()
},
label: {
Text("Add Mock")
}
)
.buttonStyle(.borderless)
},
header: {
Text("Messages")
.studyApplicationHeaderStyle()
}
},
header: {
Text("")
}
)
}
)
}
.studyApplicationList()
.toolbar {
Button(
action: {
messageManager.addMockMessage()
},
label: {
Text("Add Mock")
}
)
Button(
action: {
messageManager.makeMockMessagesProcessing()
},
label: {
Text("Set Processing")
}
)
}
}
}
}


return MessageRowPreviewWrapper()
.previewWith(standard: ENGAGEHFStandard()) {
AccountConfiguration(service: InMemoryAccountService())
MessageManager()
NavigationManager()
}
Expand Down
32 changes: 29 additions & 3 deletions ENGAGEHF/ENGAGEHFStandard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

actor ENGAGEHFStandard: Standard, EnvironmentAccessible {
@Dependency(Account.self) private var account: Account?
@Dependency(MessageManager.self) private var messageManager: MessageManager?

@Application(\.logger) private var logger

Expand All @@ -37,11 +38,15 @@
}
}


func addMeasurement(samples: [HKSample]) async throws {
guard !samples.isEmpty else {
return
}

await messageManager?.markAsProcessing(
type: .healthMeasurement(samples: samples.count)
)

logger.debug("Saving \(samples.count) samples to firestore ...")
let accountId = try await accountId
Expand Down Expand Up @@ -80,10 +85,31 @@


func add(response: ModelsR4.QuestionnaireResponse) async throws {
var questionnaireId = response.identifier?.value?.value?.string ?? UUID().uuidString

// Use ID "0" in test mode to match test message
#if DEBUG || TEST
if ProcessInfo.processInfo.isPreviewSimulator || FeatureFlags.setupTestMessages {
questionnaireId = "0"
}
#endif

await messageManager?.markAsProcessing(
type: .questionnaire(id: questionnaireId)
)

#if DEBUG || TEST
if ProcessInfo.processInfo.isPreviewSimulator || FeatureFlags.setupTestMessages {
try? await Task.sleep(for: .seconds(2)) // Simulate delay
return
}
#endif

Check warning on line 107 in ENGAGEHF/ENGAGEHFStandard.swift

View check run for this annotation

Codecov / codecov/patch

ENGAGEHF/ENGAGEHFStandard.swift#L107

Added line #L107 was not covered by tests
let accountId = try await accountId
do {
let id = response.identifier?.value?.value?.string ?? UUID().uuidString
try await Firestore.questionnaireResponseCollectionReference(for: accountId).document(id).setData(from: response)
try await Firestore.questionnaireResponseCollectionReference(for: accountId)
.document(questionnaireId)
.setData(from: response)

Check warning on line 112 in ENGAGEHF/ENGAGEHFStandard.swift

View check run for this annotation

Codecov / codecov/patch

ENGAGEHF/ENGAGEHFStandard.swift#L110-L112

Added lines #L110 - L112 were not covered by tests
} catch {
throw FirestoreError(error)
}
Expand Down
Loading