mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 01:31:02 +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 {
|
extension AccountRecord {
|
||||||
static let moved = belongsTo(AccountRecord.self)
|
static let moved = belongsTo(AccountRecord.self)
|
||||||
|
static let relationship = hasOne(Relationship.self)
|
||||||
|
static let identityProofs = hasMany(IdentityProofRecord.self)
|
||||||
static let pinnedStatusJoins = hasMany(AccountPinnedStatusJoin.self)
|
static let pinnedStatusJoins = hasMany(AccountPinnedStatusJoin.self)
|
||||||
.order(AccountPinnedStatusJoin.Columns.index)
|
.order(AccountPinnedStatusJoin.Columns.index)
|
||||||
static let pinnedStatuses = hasMany(
|
static let pinnedStatuses = hasMany(
|
||||||
|
|
|
@ -30,6 +30,33 @@ extension ContentDatabase {
|
||||||
t.column("movedId", .text).references("accountRecord")
|
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
|
try db.create(table: "statusRecord") { t in
|
||||||
t.column("id", .text).primaryKey(onConflict: .replace)
|
t.column("id", .text).primaryKey(onConflict: .replace)
|
||||||
t.column("uri", .text).notNull()
|
t.column("uri", .text).notNull()
|
||||||
|
|
|
@ -233,6 +233,39 @@ public extension ContentDatabase {
|
||||||
.eraseToAnyPublisher()
|
.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> {
|
func setLists(_ lists: [List]) -> AnyPublisher<Never, Error> {
|
||||||
databaseWriter.writePublisher {
|
databaseWriter.writePublisher {
|
||||||
for list in lists {
|
for list in lists {
|
||||||
|
@ -347,12 +380,20 @@ public extension ContentDatabase {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
func accountPublisher(id: Account.Id) -> AnyPublisher<Account, Error> {
|
func profilePublisher(id: Account.Id) -> AnyPublisher<Profile, Error> {
|
||||||
ValueObservation.tracking(AccountInfo.request(AccountRecord.filter(AccountRecord.Columns.id == id)).fetchOne)
|
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()
|
.removeDuplicates()
|
||||||
.publisher(in: databaseWriter)
|
.publisher(in: databaseWriter)
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.map(Account.init(info:))
|
|
||||||
.eraseToAnyPublisher()
|
.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 struct AccountService {
|
||||||
public let account: Account
|
public let account: Account
|
||||||
|
public let relationship: Relationship?
|
||||||
|
public let identityProofs: [IdentityProof]
|
||||||
public let navigationService: NavigationService
|
public let navigationService: NavigationService
|
||||||
|
|
||||||
private let mastodonAPIClient: MastodonAPIClient
|
private let mastodonAPIClient: MastodonAPIClient
|
||||||
private let contentDatabase: ContentDatabase
|
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.account = account
|
||||||
|
self.relationship = relationship
|
||||||
|
self.identityProofs = identityProofs
|
||||||
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
navigationService = NavigationService(mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
||||||
self.mastodonAPIClient = mastodonAPIClient
|
self.mastodonAPIClient = mastodonAPIClient
|
||||||
self.contentDatabase = contentDatabase
|
self.contentDatabase = contentDatabase
|
||||||
|
|
|
@ -34,17 +34,24 @@ public struct ProfileService {
|
||||||
self.mastodonAPIClient = mastodonAPIClient
|
self.mastodonAPIClient = mastodonAPIClient
|
||||||
self.contentDatabase = contentDatabase
|
self.contentDatabase = contentDatabase
|
||||||
|
|
||||||
var accountPublisher = contentDatabase.accountPublisher(id: id)
|
var accountPublisher = contentDatabase.profilePublisher(id: id)
|
||||||
|
|
||||||
if let account = account {
|
if let account = account {
|
||||||
accountPublisher = accountPublisher
|
accountPublisher = accountPublisher
|
||||||
.merge(with: Just(account).setFailureType(to: Error.self))
|
.merge(with: Just(Profile(account: account)).setFailureType(to: Error.self))
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
accountServicePublisher = accountPublisher
|
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()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +64,18 @@ public extension ProfileService {
|
||||||
contentDatabase: contentDatabase)
|
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> {
|
func fetchPinnedStatuses() -> AnyPublisher<Never, Error> {
|
||||||
mastodonAPIClient.request(
|
mastodonAPIClient.request(
|
||||||
StatusesEndpoint.accountsStatuses(
|
StatusesEndpoint.accountsStatuses(
|
||||||
|
|
|
@ -45,4 +45,12 @@ final class ProfileViewController: TableViewController {
|
||||||
|
|
||||||
tableView.tableHeaderView = accountHeaderView
|
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 isLocked: Bool { accountService.account.locked }
|
||||||
|
|
||||||
|
var identityProofs: [IdentityProof] { accountService.identityProofs }
|
||||||
|
|
||||||
var fields: [Account.Field] { accountService.account.fields }
|
var fields: [Account.Field] { accountService.account.fields }
|
||||||
|
|
||||||
var note: NSAttributedString { accountService.account.note.attributed }
|
var note: NSAttributedString { accountService.account.note.attributed }
|
||||||
|
|
|
@ -55,6 +55,10 @@ public extension ProfileViewModel {
|
||||||
|
|
||||||
imagePresentationsSubject.send(accountViewModel.avatarURL(profile: true))
|
imagePresentationsSubject.send(accountViewModel.avatarURL(profile: true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchProfile() -> AnyPublisher<Never, Never> {
|
||||||
|
profileService.fetchProfile().assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileViewModel: CollectionViewModel {
|
extension ProfileViewModel: CollectionViewModel {
|
||||||
|
|
|
@ -8,11 +8,9 @@ final class AccountFieldView: UIView {
|
||||||
let valueTextView = TouchFallthroughTextView()
|
let valueTextView = TouchFallthroughTextView()
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
// 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)
|
super.init(frame: .zero)
|
||||||
|
|
||||||
let verified = field.verifiedAt != nil
|
|
||||||
|
|
||||||
backgroundColor = .systemBackground
|
backgroundColor = .systemBackground
|
||||||
|
|
||||||
let nameBackgroundView = UIView()
|
let nameBackgroundView = UIView()
|
||||||
|
@ -25,9 +23,9 @@ final class AccountFieldView: UIView {
|
||||||
|
|
||||||
addSubview(valueBackgroundView)
|
addSubview(valueBackgroundView)
|
||||||
valueBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
valueBackgroundView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
valueBackgroundView.backgroundColor = verified
|
valueBackgroundView.backgroundColor = verifiedAt == nil
|
||||||
? UIColor.systemGreen.withAlphaComponent(0.25)
|
? .systemBackground
|
||||||
: .systemBackground
|
: UIColor.systemGreen.withAlphaComponent(0.25)
|
||||||
|
|
||||||
addSubview(nameLabel)
|
addSubview(nameLabel)
|
||||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
|
@ -36,7 +34,7 @@ final class AccountFieldView: UIView {
|
||||||
nameLabel.textAlignment = .center
|
nameLabel.textAlignment = .center
|
||||||
nameLabel.textColor = .secondaryLabel
|
nameLabel.textColor = .secondaryLabel
|
||||||
|
|
||||||
let mutableName = NSMutableAttributedString(string: field.name)
|
let mutableName = NSMutableAttributedString(string: name)
|
||||||
|
|
||||||
mutableName.insert(emoji: emoji, view: nameLabel)
|
mutableName.insert(emoji: emoji, view: nameLabel)
|
||||||
mutableName.resizeAttachments(toLineHeight: nameLabel.font.lineHeight)
|
mutableName.resizeAttachments(toLineHeight: nameLabel.font.lineHeight)
|
||||||
|
@ -53,14 +51,14 @@ final class AccountFieldView: UIView {
|
||||||
valueTextView.isScrollEnabled = false
|
valueTextView.isScrollEnabled = false
|
||||||
valueTextView.backgroundColor = .clear
|
valueTextView.backgroundColor = .clear
|
||||||
|
|
||||||
if verified {
|
if verifiedAt != nil {
|
||||||
valueTextView.linkTextAttributes = [
|
valueTextView.linkTextAttributes = [
|
||||||
.foregroundColor: UIColor.systemGreen as Any,
|
.foregroundColor: UIColor.systemGreen as Any,
|
||||||
.underlineColor: UIColor.clear]
|
.underlineColor: UIColor.clear]
|
||||||
}
|
}
|
||||||
|
|
||||||
let valueFont = UIFont.preferredFont(forTextStyle: verified ? .headline : .body)
|
let valueFont = UIFont.preferredFont(forTextStyle: verifiedAt == nil ? .body : .headline)
|
||||||
let mutableValue = NSMutableAttributedString(attributedString: field.value.attributed)
|
let mutableValue = NSMutableAttributedString(attributedString: value)
|
||||||
let valueRange = NSRange(location: 0, length: mutableValue.length)
|
let valueRange = NSRange(location: 0, length: mutableValue.length)
|
||||||
|
|
||||||
mutableValue.removeAttribute(.font, range: valueRange)
|
mutableValue.removeAttribute(.font, range: valueRange)
|
||||||
|
@ -85,10 +83,10 @@ final class AccountFieldView: UIView {
|
||||||
addSubview(checkButton)
|
addSubview(checkButton)
|
||||||
checkButton.translatesAutoresizingMaskIntoConstraints = false
|
checkButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
checkButton.tintColor = .systemGreen
|
checkButton.tintColor = .systemGreen
|
||||||
checkButton.isHidden = !verified
|
checkButton.isHidden = verifiedAt == nil
|
||||||
checkButton.showsMenuAsPrimaryAction = true
|
checkButton.showsMenuAsPrimaryAction = true
|
||||||
|
|
||||||
if let verifiedAt = field.verifiedAt {
|
if let verifiedAt = verifiedAt {
|
||||||
checkButton.menu = UIMenu(
|
checkButton.menu = UIMenu(
|
||||||
title: String.localizedStringWithFormat(
|
title: String.localizedStringWithFormat(
|
||||||
NSLocalizedString("account.field.verified", comment: ""),
|
NSLocalizedString("account.field.verified", comment: ""),
|
||||||
|
@ -118,7 +116,7 @@ final class AccountFieldView: UIView {
|
||||||
dividerView.widthAnchor.constraint(equalToConstant: .hairline),
|
dividerView.widthAnchor.constraint(equalToConstant: .hairline),
|
||||||
checkButton.leadingAnchor.constraint(equalTo: dividerView.trailingAnchor, constant: .defaultSpacing),
|
checkButton.leadingAnchor.constraint(equalTo: dividerView.trailingAnchor, constant: .defaultSpacing),
|
||||||
valueTextView.leadingAnchor.constraint(
|
valueTextView.leadingAnchor.constraint(
|
||||||
equalTo: verified ? checkButton.trailingAnchor : dividerView.trailingAnchor,
|
equalTo: verifiedAt == nil ? dividerView.trailingAnchor : checkButton.trailingAnchor,
|
||||||
constant: .defaultSpacing),
|
constant: .defaultSpacing),
|
||||||
valueTextView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: .defaultSpacing),
|
valueTextView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: .defaultSpacing),
|
||||||
valueTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -.defaultSpacing),
|
valueTextView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -.defaultSpacing),
|
||||||
|
|
|
@ -43,8 +43,26 @@ final class AccountHeaderView: UIView {
|
||||||
view.removeFromSuperview()
|
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 {
|
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
|
fieldView.valueTextView.delegate = self
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue