mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-28 19:11:30 +00:00
Account relationship and identity proofs
This commit is contained in:
parent
b302388a9b
commit
ad4b238883
19 changed files with 359 additions and 21 deletions
|
@ -54,6 +54,8 @@ extension AccountRecord {
|
|||
|
||||
extension AccountRecord {
|
||||
static let moved = belongsTo(AccountRecord.self)
|
||||
static let relationship = hasOne(Relationship.self)
|
||||
static let identityProofs = hasMany(IdentityProofRecord.self)
|
||||
static let pinnedStatusJoins = hasMany(AccountPinnedStatusJoin.self)
|
||||
.order(AccountPinnedStatusJoin.Columns.index)
|
||||
static let pinnedStatuses = hasMany(
|
||||
|
|
|
@ -30,6 +30,33 @@ extension ContentDatabase {
|
|||
t.column("movedId", .text).references("accountRecord")
|
||||
}
|
||||
|
||||
try db.create(table: "relationship") { t in
|
||||
t.column("id", .text).primaryKey(onConflict: .replace)
|
||||
.references("accountRecord", onDelete: .cascade)
|
||||
t.column("following", .boolean).notNull()
|
||||
t.column("requested", .boolean).notNull()
|
||||
t.column("endorsed", .boolean).notNull()
|
||||
t.column("followedBy", .boolean).notNull()
|
||||
t.column("muting", .boolean).notNull()
|
||||
t.column("mutingNotifications", .boolean).notNull()
|
||||
t.column("showingReblogs", .boolean).notNull()
|
||||
t.column("blocking", .boolean).notNull()
|
||||
t.column("domainBlocking", .boolean).notNull()
|
||||
t.column("blockedBy", .boolean).notNull()
|
||||
t.column("note", .text).notNull()
|
||||
}
|
||||
|
||||
try db.create(table: "identityProofRecord") { t in
|
||||
t.column("accountId", .text).notNull().references("accountRecord", onDelete: .cascade)
|
||||
t.column("provider", .text).notNull()
|
||||
t.column("providerUsername", .text).notNull()
|
||||
t.column("profileUrl", .text).notNull()
|
||||
t.column("proofUrl", .text).notNull()
|
||||
t.column("updatedAt", .date).notNull()
|
||||
|
||||
t.primaryKey(["accountId", "provider"], onConflict: .replace)
|
||||
}
|
||||
|
||||
try db.create(table: "statusRecord") { t in
|
||||
t.column("id", .text).primaryKey(onConflict: .replace)
|
||||
t.column("uri", .text).notNull()
|
||||
|
|
|
@ -233,6 +233,39 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func insert(account: Account) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher(updates: account.save)
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func insert(identityProofs: [IdentityProof], id: Account.Id) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
for identityProof in identityProofs {
|
||||
try IdentityProofRecord(
|
||||
accountId: id,
|
||||
provider: identityProof.provider,
|
||||
providerUsername: identityProof.providerUsername,
|
||||
profileUrl: identityProof.profileUrl,
|
||||
proofUrl: identityProof.proofUrl,
|
||||
updatedAt: identityProof.updatedAt)
|
||||
.save($0)
|
||||
}
|
||||
}
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func insert(relationships: [Relationship]) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
for relationship in relationships {
|
||||
try relationship.save($0)
|
||||
}
|
||||
}
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func setLists(_ lists: [List]) -> AnyPublisher<Never, Error> {
|
||||
databaseWriter.writePublisher {
|
||||
for list in lists {
|
||||
|
@ -347,12 +380,20 @@ public extension ContentDatabase {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func accountPublisher(id: Account.Id) -> AnyPublisher<Account, Error> {
|
||||
ValueObservation.tracking(AccountInfo.request(AccountRecord.filter(AccountRecord.Columns.id == id)).fetchOne)
|
||||
func profilePublisher(id: Account.Id) -> AnyPublisher<Profile, Error> {
|
||||
ValueObservation.tracking(ProfileInfo.request(AccountRecord.filter(AccountRecord.Columns.id == id)).fetchOne)
|
||||
.removeDuplicates()
|
||||
.publisher(in: databaseWriter)
|
||||
.compactMap { $0 }
|
||||
.map(Profile.init(info:))
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func relationshipPublisher(id: Account.Id) -> AnyPublisher<Relationship, Error> {
|
||||
ValueObservation.tracking(Relationship.filter(Relationship.Columns.id == id).fetchOne)
|
||||
.removeDuplicates()
|
||||
.publisher(in: databaseWriter)
|
||||
.compactMap { $0 }
|
||||
.map(Account.init(info:))
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
|
25
DB/Sources/DB/Content/IdentityProofRecord.swift
Normal file
25
DB/Sources/DB/Content/IdentityProofRecord.swift
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
struct IdentityProofRecord: ContentDatabaseRecord, Hashable {
|
||||
let accountId: Account.Id
|
||||
let provider: String
|
||||
let providerUsername: String
|
||||
let profileUrl: URL
|
||||
let proofUrl: URL
|
||||
let updatedAt: Date
|
||||
}
|
||||
|
||||
extension IdentityProofRecord {
|
||||
enum Columns {
|
||||
static let accountId = Column(IdentityProofRecord.CodingKeys.accountId)
|
||||
static let provider = Column(IdentityProofRecord.CodingKeys.provider)
|
||||
static let providerUsername = Column(IdentityProofRecord.CodingKeys.providerUsername)
|
||||
static let profileUrl = Column(IdentityProofRecord.CodingKeys.profileUrl)
|
||||
static let proofUrl = Column(IdentityProofRecord.CodingKeys.proofUrl)
|
||||
static let updatedAt = Column(IdentityProofRecord.CodingKeys.updatedAt)
|
||||
}
|
||||
}
|
23
DB/Sources/DB/Content/ProfileInfo.swift
Normal file
23
DB/Sources/DB/Content/ProfileInfo.swift
Normal file
|
@ -0,0 +1,23 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
struct ProfileInfo: Codable, Hashable, FetchableRecord {
|
||||
let accountInfo: AccountInfo
|
||||
let relationship: Relationship?
|
||||
let identityProofRecords: [IdentityProofRecord]
|
||||
}
|
||||
|
||||
extension ProfileInfo {
|
||||
static func addingIncludes<T: DerivableRequest>(_ request: T) -> T where T.RowDecoder == AccountRecord {
|
||||
AccountInfo.addingIncludes(request)
|
||||
.including(optional: AccountRecord.relationship.forKey(CodingKeys.relationship))
|
||||
.including(all: AccountRecord.identityProofs.forKey(CodingKeys.identityProofRecords))
|
||||
}
|
||||
|
||||
static func request(_ request: QueryInterfaceRequest<AccountRecord>) -> QueryInterfaceRequest<Self> {
|
||||
addingIncludes(request).asRequest(of: self)
|
||||
}
|
||||
}
|
24
DB/Sources/DB/Entities/Profile.swift
Normal file
24
DB/Sources/DB/Entities/Profile.swift
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Mastodon
|
||||
|
||||
public struct Profile: Codable, Hashable {
|
||||
public let account: Account
|
||||
public let relationship: Relationship?
|
||||
public let identityProofs: [IdentityProof]
|
||||
|
||||
public init(account: Account) {
|
||||
self.account = account
|
||||
self.relationship = nil
|
||||
self.identityProofs = []
|
||||
}
|
||||
}
|
||||
|
||||
extension Profile {
|
||||
init(info: ProfileInfo) {
|
||||
account = Account(info: info.accountInfo)
|
||||
relationship = info.relationship
|
||||
identityProofs = info.identityProofRecords.map(IdentityProof.init(record:))
|
||||
}
|
||||
}
|
16
DB/Sources/DB/Extensions/IdentityProof+Extensions.swift
Normal file
16
DB/Sources/DB/Extensions/IdentityProof+Extensions.swift
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
extension IdentityProof {
|
||||
init(record: IdentityProofRecord) {
|
||||
self.init(
|
||||
provider: record.provider,
|
||||
providerUsername: record.providerUsername,
|
||||
profileUrl: record.profileUrl,
|
||||
proofUrl: record.proofUrl,
|
||||
updatedAt: record.updatedAt)
|
||||
}
|
||||
}
|
24
DB/Sources/DB/Extensions/Relationship+Extensions.swift
Normal file
24
DB/Sources/DB/Extensions/Relationship+Extensions.swift
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Mastodon
|
||||
|
||||
extension Relationship: ContentDatabaseRecord {}
|
||||
|
||||
extension Relationship {
|
||||
enum Columns: String, ColumnExpression {
|
||||
case id
|
||||
case following
|
||||
case requested
|
||||
case endorsed
|
||||
case followedBy
|
||||
case muting
|
||||
case mutingNotifications
|
||||
case showingReblogs
|
||||
case blocking
|
||||
case domainBlocking
|
||||
case blockedBy
|
||||
case note
|
||||
}
|
||||
}
|
19
Mastodon/Sources/Mastodon/Entities/IdentityProof.swift
Normal file
19
Mastodon/Sources/Mastodon/Entities/IdentityProof.swift
Normal file
|
@ -0,0 +1,19 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct IdentityProof: Codable, Hashable {
|
||||
public let provider: String
|
||||
public let providerUsername: String
|
||||
public let profileUrl: URL
|
||||
public let proofUrl: URL
|
||||
public let updatedAt: Date
|
||||
|
||||
public init(provider: String, providerUsername: String, profileUrl: URL, proofUrl: URL, updatedAt: Date) {
|
||||
self.provider = provider
|
||||
self.providerUsername = providerUsername
|
||||
self.profileUrl = profileUrl
|
||||
self.proofUrl = proofUrl
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
18
Mastodon/Sources/Mastodon/Entities/Relationship.swift
Normal file
18
Mastodon/Sources/Mastodon/Entities/Relationship.swift
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct Relationship: Codable, Hashable {
|
||||
public let id: Account.Id
|
||||
public let following: Bool
|
||||
public let requested: Bool
|
||||
@DecodableDefault.False public private(set) var endorsed: Bool
|
||||
public let followedBy: Bool
|
||||
public let muting: Bool
|
||||
@DecodableDefault.False public private(set) var mutingNotifications: Bool
|
||||
@DecodableDefault.False public private(set) var showingReblogs: Bool
|
||||
public let blocking: Bool
|
||||
public let domainBlocking: Bool
|
||||
@DecodableDefault.False public private(set) var blockedBy: Bool
|
||||
@DecodableDefault.EmptyString public private(set) var note: String
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
import Mastodon
|
||||
|
||||
public enum IdentityProofsEndpoint {
|
||||
case identityProofs(id: Account.Id)
|
||||
}
|
||||
|
||||
extension IdentityProofsEndpoint: Endpoint {
|
||||
public typealias ResultType = [IdentityProof]
|
||||
|
||||
public var context: [String] {
|
||||
defaultContext + ["accounts"]
|
||||
}
|
||||
|
||||
public var pathComponentsInContext: [String] {
|
||||
switch self {
|
||||
case let .identityProofs(id):
|
||||
return [id, "identity_proofs"]
|
||||
}
|
||||
}
|
||||
|
||||
public var method: HTTPMethod {
|
||||
switch self {
|
||||
case .identityProofs:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import HTTP
|
||||
import Mastodon
|
||||
|
||||
public enum RelationshipsEndpoint {
|
||||
case relationships(ids: [Account.Id])
|
||||
}
|
||||
|
||||
extension RelationshipsEndpoint: Endpoint {
|
||||
public typealias ResultType = [Relationship]
|
||||
|
||||
public var pathComponentsInContext: [String] {
|
||||
["accounts", "relationships"]
|
||||
}
|
||||
|
||||
public var queryParameters: [URLQueryItem] {
|
||||
switch self {
|
||||
case let .relationships(ids):
|
||||
return ids.map { URLQueryItem(name: "id[]", value: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
public var method: HTTPMethod {
|
||||
switch self {
|
||||
case .relationships:
|
||||
return .get
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,13 +8,21 @@ import MastodonAPI
|
|||
|
||||
public struct AccountService {
|
||||
public let account: Account
|
||||
public let relationship: Relationship?
|
||||
public let identityProofs: [IdentityProof]
|
||||
public let navigationService: NavigationService
|
||||
|
||||
private let mastodonAPIClient: MastodonAPIClient
|
||||
private let contentDatabase: ContentDatabase
|
||||
|
||||
init(account: Account, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
||||
init(account: Account,
|
||||
relationship: Relationship? = nil,
|
||||
identityProofs: [IdentityProof] = [],
|
||||
mastodonAPIClient: MastodonAPIClient,
|
||||
contentDatabase: ContentDatabase) {
|
||||
self.account = account
|
||||
self.relationship = relationship
|
||||
self.identityProofs = identityProofs
|
||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||
self.mastodonAPIClient = mastodonAPIClient
|
||||
self.contentDatabase = contentDatabase
|
||||
|
|
|
@ -34,17 +34,24 @@ public struct ProfileService {
|
|||
self.mastodonAPIClient = mastodonAPIClient
|
||||
self.contentDatabase = contentDatabase
|
||||
|
||||
var accountPublisher = contentDatabase.accountPublisher(id: id)
|
||||
var accountPublisher = contentDatabase.profilePublisher(id: id)
|
||||
|
||||
if let account = account {
|
||||
accountPublisher = accountPublisher
|
||||
.merge(with: Just(account).setFailureType(to: Error.self))
|
||||
.merge(with: Just(Profile(account: account)).setFailureType(to: Error.self))
|
||||
.removeDuplicates()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
accountServicePublisher = accountPublisher
|
||||
.map { AccountService(account: $0, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase) }
|
||||
.map {
|
||||
AccountService(
|
||||
account: $0.account,
|
||||
relationship: $0.relationship,
|
||||
identityProofs: $0.identityProofs,
|
||||
mastodonAPIClient: mastodonAPIClient,
|
||||
contentDatabase: contentDatabase)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +64,18 @@ public extension ProfileService {
|
|||
contentDatabase: contentDatabase)
|
||||
}
|
||||
|
||||
func fetchProfile() -> AnyPublisher<Never, Error> {
|
||||
Publishers.Merge3(
|
||||
mastodonAPIClient.request(AccountEndpoint.accounts(id: id))
|
||||
.flatMap { contentDatabase.insert(account: $0) },
|
||||
mastodonAPIClient.request(RelationshipsEndpoint.relationships(ids: [id]))
|
||||
.flatMap { contentDatabase.insert(relationships: $0) },
|
||||
mastodonAPIClient.request(IdentityProofsEndpoint.identityProofs(id: id))
|
||||
.catch { _ in Empty() }
|
||||
.flatMap { contentDatabase.insert(identityProofs: $0, id: id) })
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func fetchPinnedStatuses() -> AnyPublisher<Never, Error> {
|
||||
mastodonAPIClient.request(
|
||||
StatusesEndpoint.accountsStatuses(
|
||||
|
|
|
@ -45,4 +45,12 @@ final class ProfileViewController: TableViewController {
|
|||
|
||||
tableView.tableHeaderView = accountHeaderView
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
viewModel.fetchProfile()
|
||||
.sink { _ in }
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,8 @@ public extension AccountViewModel {
|
|||
|
||||
var isLocked: Bool { accountService.account.locked }
|
||||
|
||||
var identityProofs: [IdentityProof] { accountService.identityProofs }
|
||||
|
||||
var fields: [Account.Field] { accountService.account.fields }
|
||||
|
||||
var note: NSAttributedString { accountService.account.note.attributed }
|
||||
|
|
|
@ -55,6 +55,10 @@ public extension ProfileViewModel {
|
|||
|
||||
imagePresentationsSubject.send(accountViewModel.avatarURL(profile: true))
|
||||
}
|
||||
|
||||
func fetchProfile() -> AnyPublisher<Never, Never> {
|
||||
profileService.fetchProfile().assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||
}
|
||||
}
|
||||
|
||||
extension ProfileViewModel: CollectionViewModel {
|
||||
|
|
|
@ -8,11 +8,9 @@ final class AccountFieldView: UIView {
|
|||
let valueTextView = TouchFallthroughTextView()
|
||||
|
||||
// swiftlint:disable:next function_body_length
|
||||
init(field: Account.Field, emoji: [Emoji]) {
|
||||
init(name: String, value: NSAttributedString, verifiedAt: Date?, emoji: [Emoji]) {
|
||||
super.init(frame: .zero)
|
||||
|
||||
let verified = field.verifiedAt != nil
|
||||
|
||||
backgroundColor = .systemBackground
|
||||
|
||||
let nameBackgroundView = UIView()
|
||||
|
@ -25,9 +23,9 @@ final class AccountFieldView: UIView {
|
|||
|
||||
addSubview(valueBackgroundView)
|
||||
valueBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||
valueBackgroundView.backgroundColor = verified
|
||||
? UIColor.systemGreen.withAlphaComponent(0.25)
|
||||
: .systemBackground
|
||||
valueBackgroundView.backgroundColor = verifiedAt == nil
|
||||
? .systemBackground
|
||||
: UIColor.systemGreen.withAlphaComponent(0.25)
|
||||
|
||||
addSubview(nameLabel)
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -36,7 +34,7 @@ final class AccountFieldView: UIView {
|
|||
nameLabel.textAlignment = .center
|
||||
nameLabel.textColor = .secondaryLabel
|
||||
|
||||
let mutableName = NSMutableAttributedString(string: field.name)
|
||||
let mutableName = NSMutableAttributedString(string: name)
|
||||
|
||||
mutableName.insert(emoji: emoji, view: nameLabel)
|
||||
mutableName.resizeAttachments(toLineHeight: nameLabel.font.lineHeight)
|
||||
|
@ -53,14 +51,14 @@ final class AccountFieldView: UIView {
|
|||
valueTextView.isScrollEnabled = false
|
||||
valueTextView.backgroundColor = .clear
|
||||
|
||||
if verified {
|
||||
if verifiedAt != nil {
|
||||
valueTextView.linkTextAttributes = [
|
||||
.foregroundColor: UIColor.systemGreen as Any,
|
||||
.underlineColor: UIColor.clear]
|
||||
}
|
||||
|
||||
let valueFont = UIFont.preferredFont(forTextStyle: verified ? .headline : .body)
|
||||
let mutableValue = NSMutableAttributedString(attributedString: field.value.attributed)
|
||||
let valueFont = UIFont.preferredFont(forTextStyle: verifiedAt == nil ? .body : .headline)
|
||||
let mutableValue = NSMutableAttributedString(attributedString: value)
|
||||
let valueRange = NSRange(location: 0, length: mutableValue.length)
|
||||
|
||||
mutableValue.removeAttribute(.font, range: valueRange)
|
||||
|
@ -85,10 +83,10 @@ final class AccountFieldView: UIView {
|
|||
addSubview(checkButton)
|
||||
checkButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
checkButton.tintColor = .systemGreen
|
||||
checkButton.isHidden = !verified
|
||||
checkButton.isHidden = verifiedAt == nil
|
||||
checkButton.showsMenuAsPrimaryAction = true
|
||||
|
||||
if let verifiedAt = field.verifiedAt {
|
||||
if let verifiedAt = verifiedAt {
|
||||
checkButton.menu = UIMenu(
|
||||
title: String.localizedStringWithFormat(
|
||||
NSLocalizedString("account.field.verified", comment: ""),
|
||||
|
@ -118,7 +116,7 @@ final class AccountFieldView: UIView {
|
|||
dividerView.widthAnchor.constraint(equalToConstant: .hairline),
|
||||
checkButton.leadingAnchor.constraint(equalTo: dividerView.trailingAnchor, constant: .defaultSpacing),
|
||||
valueTextView.leadingAnchor.constraint(
|
||||
equalTo: verified ? checkButton.trailingAnchor : dividerView.trailingAnchor,
|
||||
equalTo: verifiedAt == nil ? dividerView.trailingAnchor : checkButton.trailingAnchor,
|
||||
constant: .defaultSpacing),
|
||||
valueTextView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: .defaultSpacing),
|
||||
valueTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -.defaultSpacing),
|
||||
|
|
|
@ -43,8 +43,26 @@ final class AccountHeaderView: UIView {
|
|||
view.removeFromSuperview()
|
||||
}
|
||||
|
||||
for identityProof in accountViewModel.identityProofs {
|
||||
let fieldView = AccountFieldView(
|
||||
name: identityProof.provider,
|
||||
value: NSAttributedString(
|
||||
string: identityProof.providerUsername,
|
||||
attributes: [.link: identityProof.profileUrl]),
|
||||
verifiedAt: identityProof.updatedAt,
|
||||
emoji: [])
|
||||
|
||||
fieldView.valueTextView.delegate = self
|
||||
|
||||
fieldsStackView.addArrangedSubview(fieldView)
|
||||
}
|
||||
|
||||
for field in accountViewModel.fields {
|
||||
let fieldView = AccountFieldView(field: field, emoji: accountViewModel.emoji)
|
||||
let fieldView = AccountFieldView(
|
||||
name: field.name,
|
||||
value: field.value.attributed,
|
||||
verifiedAt: field.verifiedAt,
|
||||
emoji: accountViewModel.emoji)
|
||||
|
||||
fieldView.valueTextView.delegate = self
|
||||
|
||||
|
|
Loading…
Reference in a new issue