Skip to content

Commit

Permalink
Merge branch 'trunk' into test-docc-2
Browse files Browse the repository at this point in the history
  • Loading branch information
etoledom committed Apr 23, 2024
2 parents 5ba6898 + 4984c0c commit ecadceb
Show file tree
Hide file tree
Showing 22 changed files with 528 additions and 4 deletions.
39 changes: 36 additions & 3 deletions Sources/GravatarUI/DesignSystem/Palette.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public struct Palette {
public let foreground: ForegroundColors
public let background: BackgroundColors
public let avatarBorder: UIColor
public let placeholder: PlaceholderColors
}

public struct PlaceholderColors {
var backgroundColor: UIColor
var loadingAnimationColors: [UIColor]
}

public enum PaletteType {
Expand Down Expand Up @@ -64,7 +70,14 @@ extension Palette {
light: light.background.primary,
dark: dark.background.primary
)),
avatarBorder: .porpoiseGray
avatarBorder: .porpoiseGray,
placeholder: PlaceholderColors(
backgroundColor: UIColor(
light: light.placeholder.backgroundColor,
dark: dark.placeholder.backgroundColor
),
loadingAnimationColors: systemPlaceholderAnimationColors()
)
)
}

Expand All @@ -77,7 +90,11 @@ extension Palette {
secondary: .dugongGray
),
background: .init(primary: .white),
avatarBorder: .porpoiseGray
avatarBorder: .porpoiseGray,
placeholder: PlaceholderColors(
backgroundColor: .smokeWhite,
loadingAnimationColors: [.smokeWhite, .bleachedSilkWhite]
)
)
}

Expand All @@ -90,7 +107,23 @@ extension Palette {
secondary: .snowflakeWhite60
),
background: .init(primary: .gravatarBlack),
avatarBorder: .porpoiseGray
avatarBorder: .porpoiseGray,
placeholder: PlaceholderColors(
backgroundColor: .boatAnchorGray,
loadingAnimationColors: [.boatAnchorGray, .spanishGray]
)
)
}

private static func systemPlaceholderAnimationColors() -> [UIColor] {
var colors: [UIColor] = []
let count = min(light.placeholder.loadingAnimationColors.count, dark.placeholder.loadingAnimationColors.count)
for i in 0 ..< count {
colors.append(UIColor(
light: light.placeholder.loadingAnimationColors[i],
dark: dark.placeholder.loadingAnimationColors[i]
))
}
return colors
}
}
4 changes: 4 additions & 0 deletions Sources/GravatarUI/DesignSystem/UIColor+DesignSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ extension UIColor {
static let gravatarBlack: UIColor = .rgba(16, 21, 23)
static let snowflakeWhite: UIColor = .rgba(240, 240, 240)
static let porpoiseGray: UIColor = .rgba(218, 218, 218)
static let bleachedSilkWhite: UIColor = .rgba(242, 242, 242)
static let smokeWhite: UIColor = .rgba(229, 231, 233)
static let snowflakeWhite60: UIColor = snowflakeWhite.withAlphaComponent(0.6)
static let boatAnchorGray: UIColor = .rgba(107, 107, 107)
static let spanishGray: UIColor = .rgba(151, 151, 151)
static let dugongGray: UIColor = .rgba(112, 112, 112)

static func rgba(_ red: CGFloat, _ green: CGFloat, _ blue: CGFloat, alpha: CGFloat = 1.0) -> UIColor {
Expand Down
40 changes: 39 additions & 1 deletion Sources/GravatarUI/ProfileView/BaseProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ import Gravatar
import UIKit

open class BaseProfileView: UIView, UIContentView {
private enum Constants {
enum Constants {
static let avatarLength: CGFloat = 72
static let maximumAccountsDisplay = 4
static let accountIconLength: CGFloat = 32
static let defaultDisplayNamePlaceholderHeight: CGFloat = 24
}

open var avatarLength: CGFloat {
Constants.avatarLength
}

public enum PlaceholderColorPolicy {
/// Gets the placeholder colors from the current palette.
case currentPalette
/// Custom colors. You can also pass predefined colors from any ``Palette``. Example: `Palette.light.placeholder`.
case custom(PlaceholderColors)
}

/// Placeholder color policy to use in the placeholder state (which basically means when all fields are empty).
public var placeholderColorPolicy: PlaceholderColorPolicy = .currentPalette

public var profileButtonStyle: ProfileButtonStyle = .view {
didSet {
Configure(profileButton).asProfileButton().style(profileButtonStyle)
Expand Down Expand Up @@ -71,6 +82,7 @@ open class BaseProfileView: UIView, UIContentView {
imageView.heightAnchor.constraint(equalToConstant: avatarLength).isActive = true
imageView.layer.cornerRadius = avatarLength / 2
imageView.clipsToBounds = true
imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return imageView
}()

Expand All @@ -81,6 +93,16 @@ open class BaseProfileView: UIView, UIContentView {
return label
}()

/// The placeholder state of "about me" label consists of 2 separate lines in some designs. This label's only purpose is to serve as the 2nd line of that
/// placeholder.
lazy var aboutMePlaceholderLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.adjustsFontForContentSizeCategory = true
label.isHidden = true
return label
}()

public private(set) lazy var displayNameLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
Expand Down Expand Up @@ -109,6 +131,7 @@ open class BaseProfileView: UIView, UIContentView {
stack.translatesAutoresizingMaskIntoConstraints = false
stack.axis = .horizontal
stack.spacing = .DS.Padding.half
stack.setContentHuggingPriority(.defaultHigh, for: .horizontal)
return stack
}()

Expand Down Expand Up @@ -247,6 +270,21 @@ open class BaseProfileView: UIView, UIContentView {
profileURL: url
)
}

// MARK: - Placeholder handling

var placeholderColors: PlaceholderColors {
switch placeholderColorPolicy {
case .currentPalette:
paletteType.palette.placeholder
case .custom(let placeholderColors):
placeholderColors
}
}

open var displayNamePlaceholderHeight: CGFloat {
Constants.defaultDisplayNamePlaceholderHeight
}
}

public protocol ProfileViewDelegate: NSObjectProtocol {
Expand Down
5 changes: 5 additions & 0 deletions Sources/GravatarUI/ProfileView/LargeProfileSummaryView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import UIKit
public class LargeProfileSummaryView: BaseProfileView {
private enum Constants {
static let avatarLength: CGFloat = 132.0
static let displayNamePlaceholderHeight: CGFloat = 32
}

public static var personalInfoLines: [PersonalInfoLine] {
Expand Down Expand Up @@ -44,4 +45,8 @@ public class LargeProfileSummaryView: BaseProfileView {
guard let model = config.summaryModel else { return }
update(with: model)
}

override public var displayNamePlaceholderHeight: CGFloat {
Constants.displayNamePlaceholderHeight
}
}
5 changes: 5 additions & 0 deletions Sources/GravatarUI/ProfileView/LargeProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import UIKit
public class LargeProfileView: BaseProfileView {
private enum Constants {
static let avatarLength: CGFloat = 132.0
static let displayNamePlaceholderHeight: CGFloat = 32
}

override public var avatarLength: CGFloat {
Expand Down Expand Up @@ -57,4 +58,8 @@ public class LargeProfileView: BaseProfileView {
guard let model = config.model else { return }
update(with: model)
}

override public var displayNamePlaceholderHeight: CGFloat {
Constants.displayNamePlaceholderHeight
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import UIKit

/// This ``PlaceholderDisplaying`` implementation is tailored for account buttons. It shows 4 shadow account buttons in the given color.
@MainActor
class AccountButtonsPlaceholderDisplayer: PlaceholderDisplaying {
var placeholderColor: UIColor
private let containerStackView: UIStackView
let isTemporary: Bool
init(containerStackView: UIStackView, color: UIColor, isTemporary: Bool = false) {
self.placeholderColor = color
self.isTemporary = isTemporary
self.containerStackView = containerStackView
}

func showPlaceholder() {
removeAllArrangedSubviews()
[placeholderView(), placeholderView(), placeholderView(), placeholderView()].forEach(containerStackView.addArrangedSubview)
if isTemporary {
containerStackView.isHidden = false
}
}

func hidePlaceholder() {
removeAllArrangedSubviews()
if isTemporary {
containerStackView.isHidden = true
}
}

private func removeAllArrangedSubviews() {
for view in containerStackView.arrangedSubviews {
containerStackView.removeArrangedSubview(view)
view.removeFromSuperview()
}
}

private func placeholderView() -> UIView {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = placeholderColor
NSLayoutConstraint.activate([
view.heightAnchor.constraint(equalToConstant: BaseProfileView.Constants.accountIconLength),
view.widthAnchor.constraint(equalToConstant: BaseProfileView.Constants.accountIconLength),
])
view.layer.cornerRadius = BaseProfileView.Constants.accountIconLength / 2
view.clipsToBounds = true
return view
}

func set(viewColor newColor: UIColor?) {
for arrangedSubview in containerStackView.arrangedSubviews {
arrangedSubview.backgroundColor = newColor
}
}

func set(layerColor newColor: UIColor?) {
for arrangedSubview in containerStackView.arrangedSubviews {
arrangedSubview.layer.backgroundColor = newColor?.cgColor
}
}

func prepareForAnimation() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import UIKit

/// This ``PlaceholderDisplaying`` implementation updates the background color when `showPlaceholder()` is called.
@MainActor
class BackgroundColorPlaceholderDisplayer<T: UIView>: PlaceholderDisplaying {
var placeholderColor: UIColor
let baseView: T
let isTemporary: Bool
let originalBackgroundColor: UIColor

init(baseView: T, color: UIColor, originalBackgroundColor: UIColor, isTemporary: Bool = false) {
self.placeholderColor = color
self.baseView = baseView
self.isTemporary = isTemporary
self.originalBackgroundColor = originalBackgroundColor
}

func showPlaceholder() {
if isTemporary {
baseView.isHidden = false
}
set(viewColor: placeholderColor)
}

func hidePlaceholder() {
set(layerColor: .clear)
set(viewColor: originalBackgroundColor)
if isTemporary {
baseView.isHidden = true
}
}

func set(viewColor newColor: UIColor?) {
// UIColor can automatically adjust according to `UIUserInterfaceStyle`, but CGColor can't.
// That's why we can't just rely on `layer.backgroundColor`. We need to set this.
baseView.backgroundColor = newColor
}

func set(layerColor newColor: UIColor?) {
// backgroundColor is not animatable for some UIView subclasses. For example: UILabel. So we need to animate over `layer.backgroundColor`.
baseView.layer.backgroundColor = newColor?.cgColor
}

func prepareForAnimation() {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import UIKit

/// A ``PlaceholderDisplaying`` implementation for a UILabel.
@MainActor
class LabelPlaceholderDisplayer: RectangularPlaceholderDisplayer<UILabel> {
override func prepareForAnimation() {
// If UILabel's backgroundColor is set, the animation won't be visible. So we need to clear it. This is only needed for UILabel so far.
set(viewColor: .clear)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import UIKit

/// A ``PlaceholderDisplaying`` implementation for the "Edit/View profile" button.
@MainActor
class ProfileButtonPlaceholderDisplayer: RectangularPlaceholderDisplayer<UIButton> {
override func showPlaceholder() {
super.showPlaceholder()
baseView.imageView?.isHidden = true
baseView.titleLabel?.isHidden = true
}

override func hidePlaceholder() {
super.hidePlaceholder()
baseView.imageView?.isHidden = false
baseView.titleLabel?.isHidden = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import UIKit

/// A ``PlaceholderDisplaying`` implementation that Inherits ``BackgroundColorPlaceholderDisplayer`.
/// In addition to ``BackgroundColorPlaceholderDisplayer``, this gives a size to the ui element and rounds its corners a bit when `showPlaceholder()` is
/// called.
@MainActor
class RectangularPlaceholderDisplayer<T: UIView>: BackgroundColorPlaceholderDisplayer<T> {
private let cornerRadius: CGFloat
private let height: CGFloat
private let widthRatioToParent: CGFloat
private var layoutConstraints: [NSLayoutConstraint] = []
private var isShowing: Bool = false
private var originalCornerRadius: CGFloat

init(
baseView: T,
color: UIColor,
originalBackgroundColor: UIColor = .clear,
cornerRadius: CGFloat,
height: CGFloat,
widthRatioToParent: CGFloat,
isTemporary: Bool = false
) {
self.cornerRadius = cornerRadius
self.height = height
self.widthRatioToParent = widthRatioToParent
self.originalCornerRadius = baseView.layer.cornerRadius
super.init(baseView: baseView, color: color, originalBackgroundColor: originalBackgroundColor, isTemporary: isTemporary)
}

override func showPlaceholder() {
super.showPlaceholder()
guard !isShowing else { return }
// Deactivate existing ones
NSLayoutConstraint.deactivate(layoutConstraints)
layoutConstraints = baseView.turnIntoPlaceholder(cornerRadius: cornerRadius, height: height, widthRatioToParent: widthRatioToParent)
NSLayoutConstraint.activate(layoutConstraints)
isShowing = true
}

override func hidePlaceholder() {
super.hidePlaceholder()
baseView.resetPlaceholder(cornerRadius: originalCornerRadius)
NSLayoutConstraint.deactivate(layoutConstraints)
layoutConstraints = []
isShowing = false
}
}

0 comments on commit ecadceb

Please sign in to comment.