Skip to content

Commit

Permalink
Improve notification card UI
Browse files Browse the repository at this point in the history
Expandable text, bold title, tighter padding.
  • Loading branch information
nriedman committed May 31, 2024
1 parent 1a8e9a1 commit a64a7ab
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 26 deletions.
34 changes: 14 additions & 20 deletions ENGAGEHF/Dashboard/Notifications/Views/NotificationRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,35 @@ import SwiftUI


struct NotificationRow: View {
@Binding var notification: Notification
@State private var isExpanded = false
let notification: Notification

Check failure on line 14 in ENGAGEHF/Dashboard/Notifications/Views/NotificationRow.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Type Contents Order Violation: An 'instance_property' should not be placed amongst the type content(s) 'subtype' (type_contents_order)

@ScaledMetric private var spacing: CGFloat = 5

Check failure on line 16 in ENGAGEHF/Dashboard/Notifications/Views/NotificationRow.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Type Contents Order Violation: An 'instance_property' should not be placed amongst the type content(s) 'subtype' (type_contents_order)


var body: some View {

Check failure on line 19 in ENGAGEHF/Dashboard/Notifications/Views/NotificationRow.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Type Contents Order Violation: An 'instance_property' should not be placed amongst the type content(s) 'subtype' (type_contents_order)
VStack(alignment: .leading) {
HStack(alignment: .center) {
Text(notification.type)
.font(.subheadline)
Text(notification.type.localizedUppercase)
.font(.footnote)
.foregroundStyle(.secondary)
Spacer()
XButton(notification: $notification)
XButton(notification: notification)
}
Divider()
Text(notification.title)
.font(.subheadline)
.bold()
.multilineTextAlignment(.leading)
.padding(.bottom, 10)
if isExpanded {
Text(notification.description)
.multilineTextAlignment(.leading)
.font(.footnote)
} else {
LearnMoreButton(isExpanded: $isExpanded)
}
.padding(.bottom, spacing)
ExpandableText(text: notification.description, lineLimit: 1, spacing: spacing)
.font(.footnote)
}
.onDisappear {
isExpanded = false
}
}


private struct XButton: View {
@Environment(NotificationManager.self) private var notificationManager
@Binding var notification: Notification
let notification: Notification


var body: some View {
Expand All @@ -60,6 +55,7 @@ struct NotificationRow: View {
Image(systemName: "xmark")
.foregroundStyle(.accent)
.imageScale(.small)
.accessibilityLabel("XButton")
}
)
}
Expand All @@ -73,12 +69,10 @@ struct NotificationRow: View {


var body: some View {
@Bindable var notificationManager = notificationManager

List {
Section(
content: {
ForEach($notificationManager.notifications, id: \.id) { notification in
ForEach(notificationManager.notifications, id: \.id) { notification in
StudyApplicationListCard {
NotificationRow(notification: notification)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ struct NotificationSection: View {


var body: some View {
@Bindable var notificationManager = notificationManager

Section(
content: {
if !notificationManager.notifications.isEmpty {
ForEach($notificationManager.notifications, id: \.id) { notification in
ForEach(notificationManager.notifications, id: \.id) { notification in
StudyApplicationListCard {
NotificationRow(notification: notification)
}
Expand Down
3 changes: 3 additions & 0 deletions ENGAGEHF/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,9 @@
}
}
}
},
"XButton" : {

}
},
"version" : "1.0"
Expand Down
67 changes: 67 additions & 0 deletions ENGAGEHF/ReusableElements/ExpandableText.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// 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
//
// Based on:
// https://stackoverflow.com/questions/59485532/swiftui-how-know-number-of-lines-in-text/75827200#75827200
//

import SwiftUI


struct ExpandableText: View {
let text: String
let lineLimit: Int
@ScaledMetric var spacing: CGFloat

@State private var isExpanded = false
@State private var isTruncated: Bool? = nil

Check failure on line 21 in ENGAGEHF/ReusableElements/ExpandableText.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Discouraged Optional Boolean Violation: Prefer non-optional booleans over optional booleans (discouraged_optional_boolean)

Check failure on line 21 in ENGAGEHF/ReusableElements/ExpandableText.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Redundant Optional Initialization Violation: Initializing an optional variable with nil is redundant (redundant_optional_initialization)


var body: some View {
VStack(alignment: .leading, spacing: spacing) {
Text(text)
.lineLimit(isExpanded ? nil : lineLimit)
.background(calculateTruncation(text: text))

if isTruncated == true {
ShowMoreButton(isExpanded: $isExpanded)
}
}
.multilineTextAlignment(.leading)
// Re-calculate isTruncated for the new text
.onChange(of: text) { isTruncated = nil }
.onDisappear { isExpanded = false }
}


private func calculateTruncation(text: String) -> some View {
// Select the view that fits in the background of the line-limited text.
ViewThatFits(in: .vertical) {
Text(text)
.hidden()
.onAppear {
// If the whole text fits, then isTruncated is set to false and no button is shown.
guard isTruncated == nil else { return }

Check failure on line 48 in ENGAGEHF/ReusableElements/ExpandableText.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Conditional Returns on Newline Violation: Conditional statements should always return on the next line (conditional_returns_on_newline)
isTruncated = false
}
Color(.clear)
.hidden()
.onAppear {
// If the whole text does not fit, Color.clear is selected,
// isTruncated is set to true and button is shown.
guard isTruncated == nil else { return }

Check failure on line 56 in ENGAGEHF/ReusableElements/ExpandableText.swift

View workflow job for this annotation

GitHub Actions / SwiftLint / SwiftLint

Conditional Returns on Newline Violation: Conditional statements should always return on the next line (conditional_returns_on_newline)
isTruncated = true
}
}
}
}


#Preview {
let sampleText = "Your dose of XXX was changed. You can review medication information in the Education Page."
return ExpandableText(text: sampleText, lineLimit: 1, spacing: 5)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import SwiftUI


struct LearnMoreButton: View {
struct ShowMoreButton: View {
private var labelText: String {
isExpanded ? "Done" : "Learn more..."
isExpanded ? "Show Less" : "Show more"
}

@Binding var isExpanded: Bool
Expand All @@ -37,7 +37,7 @@ struct LearnMoreButton: View {


var body: some View {
LearnMoreButton(isExpanded: $isExpanded)
ShowMoreButton(isExpanded: $isExpanded)
}
}

Expand Down

0 comments on commit a64a7ab

Please sign in to comment.