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 #130

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 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
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
171 changes: 108 additions & 63 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,46 +61,87 @@ struct MessageRow: View {
.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 = message.processingState {
switch processingState.type {
case .healthMeasurement(let count):
return "Processing \(count) measurement\(count == 1 ? "" : "s")..."
case .questionnaire:
return "Processing questionnaire..."
}
}
return "Processing..."
Comment on lines +81 to +89
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please ensure that these strings are localized. I would suggest to move this string generation in the processing state type and localize them there, we already have some similar code there.

}

private var titleRow: some View {
HStack(alignment: .top) {
HStack(alignment: .center, spacing: 8) {
Text(message.title)
.bold()
}
.font(.subheadline)
Spacer()
if message.isDismissible && !message.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 message.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 {
if message.action != .unknown && !message.isProcessing {
Task {
let didPerformAction = await navigationManager.execute(message.action)
if message.isDismissible, didPerformAction {
Expand All @@ -108,6 +150,7 @@ struct MessageRow: View {
}
}
}
.disabled(message.isProcessing)
}
}

Expand All @@ -117,49 +160,51 @@ struct MessageRow: View {
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
18 changes: 15 additions & 3 deletions ENGAGEHF/ENGAGEHFStandard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import SwiftUI

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 @@ actor ENGAGEHFStandard: Standard, EnvironmentAccessible {
}
}


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,17 @@ actor ENGAGEHFStandard: Standard, EnvironmentAccessible {


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

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

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)
} catch {
throw FirestoreError(error)
}
Expand Down
20 changes: 19 additions & 1 deletion ENGAGEHF/Managers/MessageManager/Message.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,22 @@
let dueDate: Date?
// periphery:ignore - Currently not used in the app but a complete representation of the model.
let completionDate: Date?
var processingState: ProcessingState?

var isProcessing: Bool {
processingState.isStillProcessing ?? false

Check failure on line 29 in ENGAGEHF/Managers/MessageManager/Message.swift

View workflow job for this annotation

GitHub Actions / Build and Test / Test using xcodebuild or run fastlane

value of optional type 'ProcessingState?' must be unwrapped to refer to member 'isStillProcessing' of wrapped base type 'ProcessingState'

Check failure on line 29 in ENGAGEHF/Managers/MessageManager/Message.swift

View workflow job for this annotation

GitHub Actions / CodeQL / Test using xcodebuild or run fastlane

value of optional type 'ProcessingState?' must be unwrapped to refer to member 'isStillProcessing' of wrapped base type 'ProcessingState'
}

func isRelatedTo(_ state: ProcessingState) -> Bool {

Check failure on line 32 in ENGAGEHF/Managers/MessageManager/Message.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Type Contents Order Violation: An 'other_method' should not be placed amongst the type content(s) 'initializer' (type_contents_order)
switch (self.action, state.type) {
case (.showHeartHealth, .healthMeasurement):
return true
case (.completeQuestionnaire(let questionnaireId), .questionnaire(let id)):

Check warning on line 36 in ENGAGEHF/Managers/MessageManager/Message.swift

View workflow job for this annotation

GitHub Actions / Build and Test / Test using xcodebuild or run fastlane

Pattern Matching Keywords Violation: Combine multiple pattern matching bindings by moving keywords out of tuples (pattern_matching_keywords)

Check warning on line 36 in ENGAGEHF/Managers/MessageManager/Message.swift

View workflow job for this annotation

GitHub Actions / CodeQL / Test using xcodebuild or run fastlane

Pattern Matching Keywords Violation: Combine multiple pattern matching bindings by moving keywords out of tuples (pattern_matching_keywords)

Check failure on line 36 in ENGAGEHF/Managers/MessageManager/Message.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Pattern Matching Keywords Violation: Combine multiple pattern matching bindings by moving keywords out of tuples (pattern_matching_keywords)

Check failure on line 36 in ENGAGEHF/Managers/MessageManager/Message.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Pattern Matching Keywords Violation: Combine multiple pattern matching bindings by moving keywords out of tuples (pattern_matching_keywords)
return questionnaireId == id
default:
return false
}
}

init(
title: String,
Expand All @@ -32,7 +47,8 @@
isDismissible: Bool,
dueDate: Date?,
completionDate: Date?,
id: String? = UUID().uuidString
id: String? = UUID().uuidString,
processingState: ProcessingState? = nil
) {
self.id = id
self.title = title
Expand All @@ -41,6 +57,7 @@
self.isDismissible = isDismissible
self.dueDate = dueDate
self.completionDate = completionDate
self.processingState = processingState
}
}

Expand All @@ -67,5 +84,6 @@
self.isDismissible = try container.decode(Bool.self, forKey: .isDismissible)
self.dueDate = try container.decodeISO8601DateIfPresent(forKey: .dueDate)
self.completionDate = try container.decodeISO8601DateIfPresent(forKey: .completionDate)
self.processingState = nil // Initialize as nil since it's not stored in Firebase
}
}
Loading
Loading