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

feat: 🎸 [JIRA:HCPSDKFIORIUIKIT-2707] SwiftUI RatingControl Enhancement #794

Merged
merged 4 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -42,38 +42,92 @@ struct RatingControlExample: View {

@State var rating20: Int = 4

@State var rating21: Int = 1

@State var rating22: Int = 2

@State var rating23: Int = 3

@State var rating24: Int = 4

@State var showsValueLabel = false
@State var showsReviewCountLabel = false
@State var setsAverageRating = false
@State var setsReviewCount = false
@State var setsReviewCountCeiling = false

var body: some View {
List {
Text("RatingControl Default Example")
RatingControl(rating: self.$rating1, ratingControlStyle: .editable)
RatingControl(rating: self.$rating2, ratingControlStyle: .editableDisabled)
RatingControl(rating: self.$rating3, ratingControlStyle: .standard)
RatingControl(rating: self.$rating4, ratingControlStyle: .accented)
Text("RatingControl Examples")
Toggle("Shows Value Label", isOn: self.$showsValueLabel)
.padding(.leading, 16)
.padding(.trailing, 16)
Toggle("Shows Review Count Label", isOn: self.$showsReviewCountLabel)
.padding(.leading, 16)
.padding(.trailing, 16)
Toggle("Sets Average Rating", isOn: self.$setsAverageRating)
.padding(.leading, 16)
.padding(.trailing, 16)
Toggle("Sets Review Count", isOn: self.$setsReviewCount)
.padding(.leading, 16)
.padding(.trailing, 16)
Toggle("Sets Review Count Ceiling", isOn: self.$setsReviewCountCeiling)
.padding(.leading, 16)
.padding(.trailing, 16)

Text("Custom Color Example")
RatingControl(rating: self.$rating5, ratingControlStyle: .editable, onColor: .red, offColor: .yellow)
RatingControl(rating: self.$rating6, ratingControlStyle: .editableDisabled, onColor: .orange, offColor: .yellow)
RatingControl(rating: self.$rating7, ratingControlStyle: .standard, onColor: .brown, offColor: .yellow)
RatingControl(rating: self.$rating8, ratingControlStyle: .accented, onColor: .black, offColor: .yellow)
Text("RatingControl Default Example")
RatingControl(rating: self.$rating1, ratingControlStyle: .editable, showsValueLabel: self.showsValueLabel)
RatingControl(rating: self.$rating2, ratingControlStyle: .editableDisabled, showsValueLabel: self.showsValueLabel)
RatingControl(rating: self.$rating3, ratingControlStyle: .standard, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating4, ratingControlStyle: .standardLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating5, ratingControlStyle: .accented, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating6, ratingControlStyle: .accentedLarge, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)

Text("Custom Color and Font Example")
RatingControl(rating: self.$rating7, ratingControlStyle: .editable, onColor: .red, offColor: .yellow, showsValueLabel: self.showsValueLabel)
RatingControl(rating: self.$rating8, ratingControlStyle: .editableDisabled, onColor: .orange, offColor: .yellow, showsValueLabel: self.showsValueLabel)
RatingControl(rating: self.$rating9, ratingControlStyle: .standard, onColor: .brown, offColor: .yellow, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating10, ratingControlStyle: .standardLarge, onColor: .brown, offColor: .yellow, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating11, ratingControlStyle: .accented, onColor: .black, offColor: .yellow, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating12, ratingControlStyle: .accentedLarge, onColor: .black, offColor: .yellow, showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)

Text("Larger Size Example")
RatingControl(rating: self.$rating9, ratingControlStyle: .editable, itemSize: CGSize(width: 50, height: 50))
RatingControl(rating: self.$rating10, ratingControlStyle: .editableDisabled, itemSize: CGSize(width: 40, height: 40))
RatingControl(rating: self.$rating11, ratingControlStyle: .standard, itemSize: CGSize(width: 10, height: 10))
RatingControl(rating: self.$rating12, ratingControlStyle: .accented, itemSize: CGSize(width: 5, height: 5))
RatingControl(rating: self.$rating13, ratingControlStyle: .editable, itemSize: CGSize(width: 40, height: 40), showsValueLabel: self.showsValueLabel)
RatingControl(rating: self.$rating14, ratingControlStyle: .editableDisabled, itemSize: CGSize(width: 30, height: 30), showsValueLabel: self.showsValueLabel)
RatingControl(rating: self.$rating15, ratingControlStyle: .standard, itemSize: CGSize(width: 10, height: 10), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating16, ratingControlStyle: .standardLarge, itemSize: CGSize(width: 20, height: 20), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating17, ratingControlStyle: .accented, itemSize: CGSize(width: 5, height: 5), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating18, ratingControlStyle: .accentedLarge, itemSize: CGSize(width: 10, height: 10), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)

Text("Custom Image Example")
RatingControl(rating: self.$rating13, ratingControlStyle: .editable, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"))
RatingControl(rating: self.$rating14, ratingControlStyle: .editableDisabled, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"))
RatingControl(rating: self.$rating15, ratingControlStyle: .standard, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"))
RatingControl(rating: self.$rating16, ratingControlStyle: .accented, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"))

Text("Custom Number of Stars Example")
RatingControl(rating: self.$rating17, ratingControlStyle: .editable, ratingBounds: -5 ... 5)
RatingControl(rating: self.$rating18, ratingControlStyle: .editableDisabled, ratingBounds: -5 ... 5)
RatingControl(rating: self.$rating19, ratingControlStyle: .standard, ratingBounds: -5 ... 5)
RatingControl(rating: self.$rating20, ratingControlStyle: .accented, ratingBounds: -5 ... 5)
RatingControl(rating: self.$rating19, ratingControlStyle: .editable, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"), halfImage: Image(systemName: "hand.thumbsup.circle"), showsValueLabel: self.showsValueLabel)
RatingControl(rating: self.$rating20, ratingControlStyle: .editableDisabled, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"), halfImage: Image(systemName: "hand.thumbsup.circle"), showsValueLabel: self.showsValueLabel)
RatingControl(rating: self.$rating21, ratingControlStyle: .standard, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"), halfImage: Image(systemName: "hand.thumbsup.circle"), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating22, ratingControlStyle: .standardLarge, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"), halfImage: Image(systemName: "hand.thumbsup.circle"), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating23, ratingControlStyle: .accented, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"), halfImage: Image(systemName: "hand.thumbsup.circle"), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
RatingControl(rating: self.$rating24, ratingControlStyle: .accentedLarge, onImage: Image(systemName: "hand.thumbsup.fill"), offImage: Image(systemName: "hand.thumbsdown.fill"), halfImage: Image(systemName: "hand.thumbsup.circle"), showsValueLabel: self.showsValueLabel, averageRating: self.getAverageRatingValue(), reviewCount: self.getReviewCount(), reviewCountCeiling: self.getReviewCountCeiling(), showsReviewCountLabel: self.showsReviewCountLabel)
}
}

func getAverageRatingValue() -> CGFloat? {
guard self.setsAverageRating else {
return nil
}
return 2.6
}

func getReviewCount() -> Int? {
guard self.setsReviewCount else {
return nil
}
return 1234
}

func getReviewCountCeiling() -> Int? {
guard self.setsReviewCountCeiling else {
return nil
}
return 1000
}
}

Expand Down
89 changes: 70 additions & 19 deletions Sources/FioriSwiftUICore/DataTypes/RatingControl+DataType.swift
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
import FioriThemeManager
import SwiftUI

public extension RatingControl {
/**
The available styles for the `FUIRatingControl`.
The available styles for the `RatingControl`.
*/
enum Style {
/**
Editable style.

Each rating star is a SF Symbol body light style (large scale) with tint color.
Each rating star is large scale with tint color.
This is the default style.
*/
case editable

/**
Disabled editable style.

Each rating star is the same as `Editable` style but with grey color.
Each rating star is the same as `Editable` style but with grey color. User interaction is disabled.
*/
case editableDisabled

/**
Standard style.

This `FUIRatingControl` is read-only. Each rating star is a SF Symbol body light style (small scale).
This `RatingControl` is read-only. Each rating star is in small scale.
*/
case standard

/**
Standard large style.

This `RatingControl` is read-only. Each rating star is in large scale.
*/
case standardLarge
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to use the Bool variable isLarge with the UIKit FUIRatingControl. By default, it is set to false for the read-only state and always true for the editable and disabled states. This approach is more simple to me when a smaller size is needed for the editable or disabled states, or for any new states that may be introduced in the future. For us, this means that we do not need to add too many styles, and for the App team, it makes it easy to understand that there are two sizes for each state.
I just want to let you know about the difference between your API and mine. However, I am definitely okay with your implementation for these two new styles here for SWiftUI Rating Control.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JunSong-SH I was originally using the isLarge property. However, later I removed it to reduce the number of properties. I think there are too many properties already.


/**
Accented read-only style.

This `FUIRatingControl` is read-only with accented color. Each rating star is the same as in `standard` style.
This `RatingControl` is read-only. Each rating star is in small scale with accented color.
*/
case accented

/**
Accented read-only style.

This `RatingControl` is read-only. Each rating star is in large scale with accented color.
*/
case accentedLarge
}

internal static func getAccessibilityLabelString(_ rating: Int, ratingBounds: ClosedRange<Int>) -> String {
Expand All @@ -45,6 +60,7 @@ extension RatingControlConfiguration {
struct RatingItem: Identifiable {
public let id = UUID()
let isOn: Bool
let isHalf: Bool
}

func ratingItems(_ rating: Int) -> [RatingItem] {
Expand All @@ -53,7 +69,25 @@ extension RatingControlConfiguration {
guard i != self.ratingBounds.upperBound else {
continue
}
items.append(RatingItem(isOn: i < rating))
items.append(RatingItem(isOn: i < rating, isHalf: false))
}
return items
}

func ratingItems(_ averageRating: CGFloat) -> [RatingItem] {
var items: [RatingItem] = []
for i in self.ratingBounds {
guard i != self.ratingBounds.upperBound else {
continue
}
let diff = averageRating - CGFloat(i)
if diff < 0.3 {
items.append(RatingItem(isOn: false, isHalf: false))
} else if diff < 0.7 {
Copy link
Contributor

Choose a reason for hiding this comment

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

See FIORITECHE1-8222, web component use 1.0-1.2 -> 1, 1.3-1.7 -> 1.5 and 1.8-1.9 -> 2. So, here, shouldn't diff <= 0.7 or diff < 0.8?

Copy link
Contributor Author

@janhuachu janhuachu Sep 9, 2024

Choose a reason for hiding this comment

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

Yes, you're right. I'll change it. Actually, I think use half star for 0.25 to 0.75 is more reasonable, since it is a Float. I'll check with the designer. Let's merge this code first and will change later with the changes needed for large text font.

items.append(RatingItem(isOn: false, isHalf: true))
} else {
items.append(RatingItem(isOn: true, isHalf: false))
}
}
return items
}
Expand All @@ -67,10 +101,10 @@ extension RatingControlConfiguration {
return .preferredColor(.tintColor)
case .editableDisabled:
return .preferredColor(.quaternaryLabel)
case .standard:
case .standard, .standardLarge:
return .preferredColor(.tertiaryLabel)
case .accented:
return .preferredColor(.mango3)
case .accented, .accentedLarge:
return .preferredColor(.mango8)
}
}

Expand All @@ -83,10 +117,10 @@ extension RatingControlConfiguration {
return .preferredColor(.tintColor)
case .editableDisabled:
return .preferredColor(.quaternaryLabel)
case .standard:
case .standard, .standardLarge:
return .preferredColor(.tertiaryLabel)
case .accented:
return .preferredColor(.mango4)
case .accented, .accentedLarge:
return .preferredColor(.mango8)
}
}

Expand All @@ -112,21 +146,38 @@ extension RatingControlConfiguration {
.foregroundColor(self.getOffColor())
}

func getHalfImageView() -> some View {
self.getHalfImage()
.resizable()
.scaledToFit()
.frame(width: self.getItemSize().width, height: self.getItemSize().height)
.font(.body)
.fontWeight(.light)
.imageScale(self.getScale())
.foregroundColor(self.getOnColor())
}

func getOnImage() -> Image {
let image: Image = (onImage ?? Image(systemName: "star.fill"))
let image: Image = (onImage ?? FioriIcon.actions.favorite)
.renderingMode(.template)
return image
}

func getOffImage() -> Image {
let image: Image = (offImage ?? Image(systemName: "star"))
let image: Image = (offImage ?? FioriIcon.actions.unfavorite)
.renderingMode(.template)
return image
}

func getHalfImage() -> Image {
let image: Image = (halfImage ?? FioriIcon.actions.halfStar)
.renderingMode(.template)
return image
}

func getScale() -> Image.Scale {
switch self.ratingControlStyle {
case .editable, .editableDisabled:
case .editable, .editableDisabled, .standardLarge, .accentedLarge:
return .large
case .standard, .accented:
return .small
Expand All @@ -138,7 +189,7 @@ extension RatingControlConfiguration {
return itemSize
}
switch self.ratingControlStyle {
case .editable, .editableDisabled:
case .editable, .editableDisabled, .standardLarge, .accentedLarge:
return CGSize(width: 28, height: 28)
case .standard, .accented:
return CGSize(width: 16, height: 16)
Expand All @@ -150,10 +201,10 @@ extension RatingControlConfiguration {
return interItemSpacing
}
switch self.ratingControlStyle {
case .editable, .editableDisabled:
return CGFloat(4)
case .editable, .editableDisabled, .standardLarge, .accentedLarge:
return CGFloat(8)
case .standard, .accented:
return CGFloat(2)
return CGFloat(6)
}
}

Expand Down
Loading
Loading