feat: add familiar followers UI component for ProfileCard
This commit is contained in:
parent
945f05703b
commit
531f71b77d
|
@ -8,6 +8,7 @@
|
|||
import os.log
|
||||
import UIKit
|
||||
import MastodonUI
|
||||
import MastodonSDK
|
||||
|
||||
enum DiscoverySection: CaseIterable {
|
||||
// case posts
|
||||
|
@ -22,9 +23,14 @@ extension DiscoverySection {
|
|||
|
||||
class Configuration {
|
||||
weak var profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate?
|
||||
let familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher?
|
||||
|
||||
public init(profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? = nil) {
|
||||
public init(
|
||||
profileCardTableViewCellDelegate: ProfileCardTableViewCellDelegate? = nil,
|
||||
familiarFollowers: Published<[Mastodon.Entity.FamiliarFollowers]>.Publisher? = nil
|
||||
) {
|
||||
self.profileCardTableViewCellDelegate = profileCardTableViewCellDelegate
|
||||
self.familiarFollowers = familiarFollowers
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,6 +63,15 @@ extension DiscoverySection {
|
|||
user: user,
|
||||
profileCardTableViewCellDelegate: configuration.profileCardTableViewCellDelegate
|
||||
)
|
||||
// bind familiarFollowers
|
||||
if let familiarFollowers = configuration.familiarFollowers {
|
||||
familiarFollowers
|
||||
.map { array in array.first(where: { $0.id == user.id }) }
|
||||
.assign(to: \.familiarFollowers, on: cell.profileCardView.viewModel)
|
||||
.store(in: &cell.disposeBag)
|
||||
} else {
|
||||
cell.profileCardView.viewModel.familiarFollowers = nil
|
||||
}
|
||||
}
|
||||
context.authenticationService.activeMastodonAuthentication
|
||||
.map { $0?.user }
|
||||
|
|
|
@ -19,7 +19,8 @@ extension DiscoveryForYouViewModel {
|
|||
tableView: tableView,
|
||||
context: context,
|
||||
configuration: DiscoverySection.Configuration(
|
||||
profileCardTableViewCellDelegate: profileCardTableViewCellDelegate
|
||||
profileCardTableViewCellDelegate: profileCardTableViewCellDelegate,
|
||||
familiarFollowers: $familiarFollowers
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -20,6 +20,9 @@ final class DiscoveryForYouViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let userFetchedResultsController: UserFetchedResultsController
|
||||
|
||||
@MainActor
|
||||
@Published var familiarFollowers: [Mastodon.Entity.FamiliarFollowers] = []
|
||||
@Published var isFetching = false
|
||||
|
||||
// output
|
||||
|
@ -48,12 +51,35 @@ final class DiscoveryForYouViewModel {
|
|||
}
|
||||
|
||||
extension DiscoveryForYouViewModel {
|
||||
|
||||
@MainActor
|
||||
func fetch() async throws {
|
||||
guard !isFetching else { return }
|
||||
isFetching = true
|
||||
defer { isFetching = false }
|
||||
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
throw APIService.APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
do {
|
||||
let userIDs = try await fetchSuggestionAccounts()
|
||||
|
||||
let _familiarFollowersResponse = try? await context.apiService.familiarFollowers(
|
||||
query: .init(ids: userIDs),
|
||||
authenticationBox: authenticationBox
|
||||
)
|
||||
familiarFollowers = _familiarFollowersResponse?.value ?? []
|
||||
userFetchedResultsController.userIDs = userIDs
|
||||
} catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private func fetchSuggestionAccounts() async throws -> [Mastodon.Entity.Account.ID] {
|
||||
guard let authenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
|
||||
throw APIService.APIError.implicit(.badRequest)
|
||||
}
|
||||
|
||||
do {
|
||||
let response = try await context.apiService.suggestionAccountV2(
|
||||
|
@ -61,15 +87,15 @@ extension DiscoveryForYouViewModel {
|
|||
authenticationBox: authenticationBox
|
||||
)
|
||||
let userIDs = response.value.map { $0.account.id }
|
||||
userFetchedResultsController.userIDs = userIDs
|
||||
return userIDs
|
||||
} catch {
|
||||
// fallback V1
|
||||
let response2 = try await context.apiService.suggestionAccount(
|
||||
let response = try await context.apiService.suggestionAccount(
|
||||
query: nil,
|
||||
authenticationBox: authenticationBox
|
||||
)
|
||||
let userIDs = response2.value.map { $0.id }
|
||||
userFetchedResultsController.userIDs = userIDs
|
||||
let userIDs = response.value.map { $0.id }
|
||||
return userIDs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ extension APIService {
|
|||
func familiarFollowers(
|
||||
query: Mastodon.API.Account.FamiliarFollowersQuery,
|
||||
authenticationBox: MastodonAuthenticationBox
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
|
||||
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.FamiliarFollowers]> {
|
||||
let response = try await Mastodon.API.Account.familiarFollowers(
|
||||
session: session,
|
||||
domain: authenticationBox.domain,
|
||||
|
@ -88,20 +88,23 @@ extension APIService {
|
|||
authorization: authenticationBox.userAuthorization
|
||||
).singleOutput()
|
||||
|
||||
// let managedObjectContext = backgroundManagedObjectContext
|
||||
// try await managedObjectContext.performChanges {
|
||||
// for entity in response.value {
|
||||
// _ = Persistence.MastodonUser.createOrMerge(
|
||||
// in: managedObjectContext,
|
||||
// context: Persistence.MastodonUser.PersistContext(
|
||||
// domain: authenticationBox.domain,
|
||||
// entity: entity.account,
|
||||
// cache: nil,
|
||||
// networkDate: response.networkDate
|
||||
// )
|
||||
// )
|
||||
// } // end for … in
|
||||
// }
|
||||
let managedObjectContext = backgroundManagedObjectContext
|
||||
try await managedObjectContext.performChanges {
|
||||
for entity in response.value {
|
||||
for account in entity.accounts {
|
||||
_ = Persistence.MastodonUser.createOrMerge(
|
||||
in: managedObjectContext,
|
||||
context: Persistence.MastodonUser.PersistContext(
|
||||
domain: authenticationBox.domain,
|
||||
entity: account,
|
||||
cache: nil,
|
||||
networkDate: response.networkDate
|
||||
)
|
||||
)
|
||||
|
||||
} // end for account in
|
||||
} // end for entity in
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ extension Mastodon.API.Account {
|
|||
domain: String,
|
||||
query: FamiliarFollowersQuery,
|
||||
authorization: Mastodon.API.OAuth.Authorization
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
|
||||
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.FamiliarFollowers]>, Error> {
|
||||
let request = Mastodon.API.get(
|
||||
url: familiarFollowersEndpointURL(domain: domain),
|
||||
query: query,
|
||||
|
@ -44,24 +44,23 @@ extension Mastodon.API.Account {
|
|||
)
|
||||
return session.dataTaskPublisher(for: request)
|
||||
.tryMap { data, response in
|
||||
let value = try Mastodon.API.decode(type: [Mastodon.Entity.Account].self, from: data, response: response)
|
||||
let value = try Mastodon.API.decode(type: [Mastodon.Entity.FamiliarFollowers].self, from: data, response: response)
|
||||
return Mastodon.Response.Content(value: value, response: response)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public struct FamiliarFollowersQuery: GetQuery {
|
||||
public let accounts: [Mastodon.Entity.Account.ID]
|
||||
public let ids: [Mastodon.Entity.Account.ID]
|
||||
|
||||
public init(accounts: [Mastodon.Entity.Account.ID]) {
|
||||
self.accounts = accounts
|
||||
public init(ids: [Mastodon.Entity.Account.ID]) {
|
||||
self.ids = ids
|
||||
}
|
||||
|
||||
var queryItems: [URLQueryItem]? {
|
||||
var items: [URLQueryItem] = []
|
||||
let accountsValue = accounts.joined(separator: ",")
|
||||
if !accountsValue.isEmpty {
|
||||
items.append(URLQueryItem(name: "accounts", value: accountsValue))
|
||||
for id in ids {
|
||||
items.append(URLQueryItem(name: "id[]", value: id))
|
||||
}
|
||||
guard !items.isEmpty else { return nil }
|
||||
return items
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Mastodon+Entity+FamiliarFollowers.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK on 2022-5-16.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Mastodon.Entity {
|
||||
|
||||
/// FamiliarFollowers
|
||||
///
|
||||
/// - Since: 3.5.2
|
||||
/// - Version: 3.5.2
|
||||
/// # Last Update
|
||||
/// 2022/5/16
|
||||
/// # Reference
|
||||
/// [Document](TBD)
|
||||
public class FamiliarFollowers: Codable {
|
||||
public let id: Mastodon.Entity.Account.ID
|
||||
public let accounts: [Mastodon.Entity.Account]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Mastodon+Entity+Account.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK on 2022-5-16.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
public var displayNameWithFallback: String {
|
||||
if displayName.isEmpty {
|
||||
return username
|
||||
} else {
|
||||
return displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Account {
|
||||
public func avatarImageURL() -> URL? {
|
||||
let string = UserDefaults.shared.preferredStaticAvatar ? avatarStatic ?? avatar : avatar
|
||||
return URL(string: string)
|
||||
}
|
||||
|
||||
public func avatarImageURLWithFallback(domain: String) -> URL {
|
||||
return avatarImageURL() ?? URL(string: "https://\(domain)/avatars/original/missing.png")!
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// FamiliarFollowersDashboardView+Configuration.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK on 2022-5-16.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonSDK
|
||||
|
||||
extension FamiliarFollowersDashboardView {
|
||||
public func configure(familiarFollowers: Mastodon.Entity.FamiliarFollowers?) {
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
let accounts = familiarFollowers?.accounts.prefix(4) ?? []
|
||||
|
||||
viewModel.avatarURLs = accounts.map { $0.avatarImageURL() }
|
||||
viewModel.names = accounts.map { $0.displayNameWithFallback }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// FamiliarFollowersDashboardView+ViewModel.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK on 2022-5-16.
|
||||
//
|
||||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
extension FamiliarFollowersDashboardView {
|
||||
public final class ViewModel: ObservableObject {
|
||||
public var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
let logger = Logger(subsystem: "FamiliarFollowersDashboardView", category: "ViewModel")
|
||||
|
||||
@Published var avatarURLs: [URL?] = []
|
||||
@Published var names: [String] = []
|
||||
@Published var backgroundColor: UIColor?
|
||||
}
|
||||
}
|
||||
|
||||
extension FamiliarFollowersDashboardView.ViewModel {
|
||||
func bind(view: FamiliarFollowersDashboardView) {
|
||||
Publishers.CombineLatest(
|
||||
$avatarURLs,
|
||||
$backgroundColor
|
||||
)
|
||||
.sink { avatarURLs, backgroundColor in
|
||||
view.avatarContainerView.subviews.forEach { $0.removeFromSuperview() }
|
||||
for (i, avatarURL) in avatarURLs.enumerated() {
|
||||
let avatarButton = AvatarButton()
|
||||
let origin = CGPoint(x: 20 * i, y: 0)
|
||||
let size = CGSize(width: 32, height: 32)
|
||||
avatarButton.size = size
|
||||
avatarButton.frame = CGRect(origin: origin, size: size)
|
||||
view.avatarContainerView.addSubview(avatarButton)
|
||||
avatarButton.avatarImageView.configure(configuration: .init(url: avatarURL))
|
||||
avatarButton.avatarImageView.configure(
|
||||
cornerConfiguration: .init(
|
||||
corner: .fixed(radius: 7),
|
||||
border: .init(
|
||||
color: backgroundColor ?? .clear,
|
||||
width: 1
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
view.avatarContainerViewWidthLayoutConstraint.constant = CGFloat(12 + 20 * avatarURLs.count)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
$names
|
||||
.sink { names in
|
||||
// TODO: i18n
|
||||
let description = "Followed by" + names.joined(separator: ", ")
|
||||
view.descriptionLabel.text = description
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// FamiliarFollowersDashboardView.swift
|
||||
//
|
||||
//
|
||||
// Created by MainasuK on 2022-5-16.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import MastodonAsset
|
||||
|
||||
public final class FamiliarFollowersDashboardView: UIView {
|
||||
|
||||
let avatarContainerView = UIView()
|
||||
var avatarContainerViewWidthLayoutConstraint: NSLayoutConstraint!
|
||||
|
||||
let descriptionLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: .systemFont(ofSize: 13, weight: .regular))
|
||||
label.text = "Followed by Pixelflowers, Lee’s Food, and 4 other mutuals"
|
||||
label.textColor = Asset.Colors.Label.secondary.color
|
||||
label.numberOfLines = 0
|
||||
return label
|
||||
}()
|
||||
|
||||
public private(set) lazy var viewModel: ViewModel = {
|
||||
let viewModel = ViewModel()
|
||||
viewModel.bind(view: self)
|
||||
return viewModel
|
||||
}()
|
||||
|
||||
public func prepareForReuse() {
|
||||
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
_init()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
_init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension FamiliarFollowersDashboardView {
|
||||
|
||||
private func _init() {
|
||||
let stackView = UIStackView()
|
||||
stackView.axis = .horizontal
|
||||
stackView.spacing = 8
|
||||
|
||||
stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(stackView)
|
||||
NSLayoutConstraint.activate([
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
])
|
||||
|
||||
avatarContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.addArrangedSubview(avatarContainerView)
|
||||
avatarContainerViewWidthLayoutConstraint = avatarContainerView.widthAnchor.constraint(equalToConstant: 32).priority(.required - 1)
|
||||
NSLayoutConstraint.activate([
|
||||
avatarContainerViewWidthLayoutConstraint,
|
||||
avatarContainerView.heightAnchor.constraint(equalToConstant: 32).priority(.required - 1)
|
||||
])
|
||||
stackView.addArrangedSubview(descriptionLabel)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#if DEBUG
|
||||
import SwiftUI
|
||||
struct FamiliarFollowersDashboardView_Preview: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UIViewPreview {
|
||||
FamiliarFollowersDashboardView()
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -18,7 +18,7 @@ extension NotificationView {
|
|||
public final class ViewModel: ObservableObject {
|
||||
public var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
let logger = Logger(subsystem: "StatusView", category: "ViewModel")
|
||||
let logger = Logger(subsystem: "NotificationView", category: "ViewModel")
|
||||
|
||||
@Published public var userIdentifier: UserIdentifier? // me
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import Combine
|
|||
import CoreDataStack
|
||||
import Meta
|
||||
import MastodonMeta
|
||||
import MastodonSDK
|
||||
|
||||
extension ProfileCardView {
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import AlamofireImage
|
|||
import CoreDataStack
|
||||
import MastodonLocalization
|
||||
import MastodonAsset
|
||||
import MastodonSDK
|
||||
|
||||
extension ProfileCardView {
|
||||
public class ViewModel: ObservableObject {
|
||||
|
@ -44,6 +45,8 @@ extension ProfileCardView {
|
|||
|
||||
@Published public var groupedAccessibilityLabel = ""
|
||||
|
||||
@Published public var familiarFollowers: Mastodon.Entity.FamiliarFollowers?
|
||||
|
||||
init() {
|
||||
backgroundColor = ThemeService.shared.currentTheme.value.systemBackgroundColor
|
||||
Publishers.CombineLatest(
|
||||
|
@ -77,6 +80,7 @@ extension ProfileCardView.ViewModel {
|
|||
bindBio(view: view)
|
||||
bindRelationship(view: view)
|
||||
bindDashboard(view: view)
|
||||
bindFamiliarFollowers(view: view)
|
||||
bindAccessibility(view: view)
|
||||
}
|
||||
|
||||
|
@ -189,6 +193,18 @@ extension ProfileCardView.ViewModel {
|
|||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
private func bindFamiliarFollowers(view: ProfileCardView) {
|
||||
$familiarFollowers
|
||||
.sink { familiarFollowers in
|
||||
view.familiarFollowersDashboardViewAdaptiveMarginContainerView.isHidden = familiarFollowers.flatMap { $0.accounts.isEmpty } ?? true
|
||||
view.familiarFollowersDashboardView.configure(familiarFollowers: familiarFollowers)
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
$backgroundColor
|
||||
.assign(to: \.backgroundColor, on: view.familiarFollowersDashboardView.viewModel)
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
private func bindAccessibility(view: ProfileCardView) {
|
||||
let authorAccessibilityLabel = Publishers.CombineLatest(
|
||||
$authorName,
|
||||
|
|
|
@ -94,6 +94,9 @@ public final class ProfileCardView: UIView {
|
|||
return button
|
||||
}()
|
||||
|
||||
let familiarFollowersDashboardViewAdaptiveMarginContainerView = AdaptiveMarginContainerView()
|
||||
let familiarFollowersDashboardView = FamiliarFollowersDashboardView()
|
||||
|
||||
public private(set) lazy var viewModel: ViewModel = {
|
||||
let viewModel = ViewModel()
|
||||
viewModel.bind(view: self)
|
||||
|
@ -126,7 +129,7 @@ extension ProfileCardView {
|
|||
bioMetaText.textView.isUserInteractionEnabled = false
|
||||
statusDashboardView.isUserInteractionEnabled = false
|
||||
|
||||
// container: V - [ bannerContainer | authorContainer | bioMetaText | infoContainer ]
|
||||
// container: V - [ bannerContainer | authorContainer | bioMetaText | infoContainer | familiarFollowersDashboardView ]
|
||||
container.axis = .vertical
|
||||
container.spacing = 8
|
||||
container.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -211,7 +214,7 @@ extension ProfileCardView {
|
|||
container.addArrangedSubview(bioMetaTextAdaptiveMarginContainerView)
|
||||
container.setCustomSpacing(16, after: bioMetaTextAdaptiveMarginContainerView)
|
||||
|
||||
// infoContainer: H - [ statusDashboardView | (spacer) | relationshipActionButton ]
|
||||
// infoContainer: H - [ statusDashboardView | (spacer) | relationshipActionButton]
|
||||
infoContainer.axis = .horizontal
|
||||
infoContainer.spacing = 8
|
||||
infoContainerAdaptiveMarginContainerView.contentView = infoContainer
|
||||
|
@ -237,6 +240,10 @@ extension ProfileCardView {
|
|||
relationshipActionButtonShadowContainer.widthAnchor.constraint(greaterThanOrEqualToConstant: ProfileCardView.friendshipActionButtonSize.width).priority(.required - 1),
|
||||
relationshipActionButtonShadowContainer.heightAnchor.constraint(equalToConstant: ProfileCardView.friendshipActionButtonSize.height).priority(.required - 1),
|
||||
])
|
||||
|
||||
familiarFollowersDashboardViewAdaptiveMarginContainerView.contentView = familiarFollowersDashboardView
|
||||
familiarFollowersDashboardViewAdaptiveMarginContainerView.margin = ProfileCardView.contentMargin
|
||||
container.addArrangedSubview(familiarFollowersDashboardViewAdaptiveMarginContainerView)
|
||||
|
||||
let bottomPadding = UIView()
|
||||
bottomPadding.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
|
|
@ -44,6 +44,11 @@ extension AvatarImageView {
|
|||
}
|
||||
}
|
||||
|
||||
private func setup(border: CornerConfiguration.Border?) {
|
||||
layer.borderColor = border?.color.cgColor
|
||||
layer.borderWidth = border?.width ?? .zero
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension AvatarImageView {
|
||||
|
@ -107,9 +112,14 @@ extension AvatarImageView {
|
|||
extension AvatarImageView {
|
||||
public struct CornerConfiguration {
|
||||
public let corner: Corner
|
||||
public let border: Border?
|
||||
|
||||
public init(corner: Corner = .circle) {
|
||||
public init(
|
||||
corner: Corner = .circle,
|
||||
border: Border? = nil
|
||||
) {
|
||||
self.corner = corner
|
||||
self.border = border
|
||||
}
|
||||
|
||||
public enum Corner {
|
||||
|
@ -117,10 +127,16 @@ extension AvatarImageView {
|
|||
case fixed(radius: CGFloat)
|
||||
case scale(ratio: Int = 4) // width / ratio
|
||||
}
|
||||
|
||||
public struct Border {
|
||||
public let color: UIColor
|
||||
public let width: CGFloat
|
||||
}
|
||||
}
|
||||
|
||||
public func configure(cornerConfiguration: CornerConfiguration) {
|
||||
self.cornerConfiguration = cornerConfiguration
|
||||
setup(corner: cornerConfiguration.corner)
|
||||
setup(border: cornerConfiguration.border)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import UIKit
|
||||
import CoreDataStack
|
||||
import MastodonSDK
|
||||
|
||||
extension ProfileCardTableViewCell {
|
||||
|
||||
|
|
Loading…
Reference in New Issue