Skip to content

Commit

Permalink
Merge pull request #669 from adevinta/pre-release/component/ratings
Browse files Browse the repository at this point in the history
[RatingsDisplay] Copied RatingsDisplay to make a clean release of this component (from 0.8.0)
  • Loading branch information
LouisBorleeAdevinta authored Dec 4, 2023
2 parents 993eff8 + e6ed7b8 commit 74a63af
Show file tree
Hide file tree
Showing 33 changed files with 2,585 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// RatingDisplayAccessibilityIdentifier.swift
// SparkCore
//
// Created by Michael Zimmermann on 21.11.23.
// Copyright © 2023 Adevinta. All rights reserved.
//

import Foundation

/// The accessibility identifiers of the rating display.
public enum RatingDisplayAccessibilityIdentifier {

// MARK: - Properties

/// The accessibility identifier.
public static let identifier = "spark-rating-display"
}
29 changes: 29 additions & 0 deletions core/Sources/Components/Rating/Cache/CGLayerCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// CGLayerCache.swift
// SparkCore
//
// Created by michael.zimmermann on 08.11.23.
// Copyright © 2023 Adevinta. All rights reserved.
//

import Foundation
import UIKit

// sourcery: AutoMockable
protocol CGLayerCaching {
func object(forKey: NSString) -> CGLayer?
func setObject(_ layer: CGLayer, forKey: NSString)
}

/// A simple facade for a static NSCache
final class CGLayerCache: CGLayerCaching {
private static var cache = NSCache<NSString, CGLayer>()

func object(forKey key: NSString) -> CGLayer? {
return Self.cache.object(forKey: key)
}

func setObject(_ layer: CGLayer, forKey key: NSString) {
Self.cache.setObject(layer, forKey: key)
}
}
15 changes: 15 additions & 0 deletions core/Sources/Components/Rating/Enum/RatingDisplaySize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// RatingDisplaySize.swift
// SparkCore
//
// Created by Michael Zimmermann on 17.11.23.
// Copyright © 2023 Adevinta. All rights reserved.
//

import Foundation

public enum RatingDisplaySize: Int, CaseIterable {
case small = 12
case medium = 16
case input = 40
}
13 changes: 13 additions & 0 deletions core/Sources/Components/Rating/Enum/RatingIntent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// RatingIntent.swift
// SparkCore
//
// Created by michael.zimmermann on 09.11.23.
// Copyright © 2023 Adevinta. All rights reserved.
//

import Foundation

public enum RatingIntent: CaseIterable {
case main
}
14 changes: 14 additions & 0 deletions core/Sources/Components/Rating/Enum/RatingStarsCount.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// RatingStarsCount.swift
// SparkCore
//
// Created by Michael Zimmermann on 17.11.23.
// Copyright © 2023 Adevinta. All rights reserved.
//

import Foundation

public enum RatingStarsCount: Int, CaseIterable {
case one = 1
case five = 5
}
50 changes: 50 additions & 0 deletions core/Sources/Components/Rating/Enum/StarFillMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// StarFillMode.swift
// SparkCore
//
// Created by michael.zimmermann on 07.11.23.
// Copyright © 2023 Adevinta. All rights reserved.
//

import Foundation

/// The star fill mode determins how the star is to be filled
/// - full: the rating will be rounded to the next full number 0/1 and can be only empty or filled.
/// - half: the raing will be rounded to the next half number and can either be empty, half filled and filled.
/// - fraction: The fill rate will be the rating rounded to the neares fraction, e.g. half is fraction 2.
/// - exact: The star will be filled with the exact rating value
@frozen
public enum StarFillMode {
case full
case half
case fraction(_ :CGFloat)
case exact

// MARK: - Public functions
/// function rating
/// Calculate the rounded rating
public func rating(of givenValue: CGFloat) -> CGFloat {
if givenValue > 1 {
return 1.0
} else if givenValue <= 0 {
return 0.0
}

switch self {
case .full: return givenValue.rounded()
case .half: return givenValue.halfRounded()
case let .fraction(fraction): return givenValue.rounded(by: fraction)
case .exact: return givenValue
}
}
}

// MARK: - Private helpers
private extension CGFloat {
func rounded(by fraction: CGFloat) -> CGFloat {
return (self * fraction).rounded() / fraction
}
func halfRounded() -> CGFloat {
return self.rounded(by: 2.0)
}
}
46 changes: 46 additions & 0 deletions core/Sources/Components/Rating/Enum/StarFillModeUnitTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// StarFillModeUnitTests.swift
// SparkCoreUnitTests
//
// Created by michael.zimmermann on 08.11.23.
// Copyright © 2023 Adevinta. All rights reserved.
//

import XCTest

@testable import SparkCore

final class StarFillModeUnitTests: XCTestCase {

func test_full_with_half_rating() {
XCTAssertEqual(StarFillMode.full.rating(of: 0.5), 1.0)
}

func test_full_with_less_than_half_rating() {
XCTAssertEqual(StarFillMode.full.rating(of: 0.49), 0.0)
}

func test_half_with_half_rating() {
XCTAssertEqual(StarFillMode.half.rating(of: 0.6), 0.5)
}

func test_half_with_big_rating() {
XCTAssertEqual(StarFillMode.half.rating(of: 0.75), 1.0)
}

func test_half_with_small_rating() {
XCTAssertEqual(StarFillMode.half.rating(of: 0.2), 0.0)
}

func test_exact_rating() {
XCTAssertEqual(StarFillMode.exact.rating(of: 0.211), 0.211)
}

func test_fraction_rating_round_down() {
XCTAssertEqual(StarFillMode.fraction(10).rating(of: 0.1111), 0.1)
}

func test_fraction_rating_round_up() {
XCTAssertEqual(StarFillMode.fraction(10).rating(of: 0.15), 0.2)
}
}
91 changes: 91 additions & 0 deletions core/Sources/Components/Rating/Graphics/ShapeLayer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//
// ShapeLayer.swift
// SparkCore
//
// Created by michael.zimmermann on 08.11.23.
// Copyright © 2023 Adevinta. All rights reserved.
//

import Foundation
import UIKit

/// The Shape Layer draws a shape onto a layer and returns the CGLayer.
final class ShapeLayer {
// MARK: - Private variables
private let shape: CGPathShape
private let fillColor: CGColor
private let strokeColor: CGColor
private let fillPercentage: CGFloat
private let strokeWidth: CGFloat

// MARK: - Initializer
init(shape: CGPathShape,
fillColor: CGColor,
strokeColor: CGColor,
fillPercentage: CGFloat,
strokeWidth: CGFloat) {
self.shape = shape
self.fillColor = fillColor
self.strokeColor = strokeColor
self.fillPercentage = fillPercentage
self.strokeWidth = strokeWidth
}

/// Create a CGLayer and draw the shape on it.
func layer(graphicsContext: CGContext, size: CGSize) -> CGLayer {
guard let starLayer = CGLayer(graphicsContext, size: size, auxiliaryInfo: nil) else {
fatalError("Couldn't create layer")
}

guard let context = starLayer.context else {
fatalError("Couldn't create layer")
}

self.drawShape(graphicsContext: context, size: size)
return starLayer
}

// MARK: - Private
private func drawShape(graphicsContext: CGContext, size: CGSize) {
let rect = CGRect(origin: .zero, size: size)
let path = self.shape.cgPath(rect: rect)

graphicsContext.saveGState()

graphicsContext.clip(to: rect)
graphicsContext.addPath(path)
graphicsContext.setLineWidth(self.strokeWidth)
graphicsContext.setStrokeColor(self.strokeColor)
graphicsContext.drawPath(using: .stroke)

let insets = self.shape.insets.withHorizontalPadding(self.strokeWidth/2.0)
let maskWidth = CGFloat((insets.right - insets.left) * fillPercentage)

let maskHeight = rect.height

let clipRect = CGRect(
x: insets.left,
y: 0,
width: maskWidth,
height: maskHeight
)
graphicsContext.clip(to: clipRect)
graphicsContext.addPath(path)
graphicsContext.setFillColor(self.fillColor)
graphicsContext.drawPath(using: .fill)

graphicsContext.addPath(path)
graphicsContext.setStrokeColor(self.fillColor)
graphicsContext.setLineWidth(self.strokeWidth)
graphicsContext.drawPath(using: .stroke)

graphicsContext.restoreGState()
}
}

// MARK: Private extensions
private extension UIEdgeInsets {
func withHorizontalPadding(_ padding: CGFloat) -> UIEdgeInsets {
return UIEdgeInsets(top: self.top, left: self.left - padding, bottom: self.bottom, right: self.right + padding)
}
}
Loading

0 comments on commit 74a63af

Please sign in to comment.