mirror of
https://github.com/mastodon/mastodon-ios.git
synced 2025-01-27 07:46:15 +01:00
Add a “Joined” cell to the top of the About tab to match the web version
This commit is contained in:
parent
c2bb14eaab
commit
c6826542f9
@ -449,6 +449,7 @@
|
|||||||
"followers": "followers"
|
"followers": "followers"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"joined": "Joined",
|
||||||
"add_row": "Add Row",
|
"add_row": "Add Row",
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
|
@ -449,6 +449,7 @@
|
|||||||
"followers": "followers"
|
"followers": "followers"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"joined": "Joined",
|
||||||
"add_row": "Add Row",
|
"add_row": "Add Row",
|
||||||
"placeholder": {
|
"placeholder": {
|
||||||
"label": "Label",
|
"label": "Label",
|
||||||
|
@ -11,10 +11,10 @@ import MastodonSDK
|
|||||||
import MastodonMeta
|
import MastodonMeta
|
||||||
|
|
||||||
enum ProfileFieldItem: Hashable {
|
enum ProfileFieldItem: Hashable {
|
||||||
|
case createdAt(date: Date)
|
||||||
case field(field: FieldValue)
|
case field(field: FieldValue)
|
||||||
case editField(field: FieldValue)
|
case editField(field: FieldValue)
|
||||||
case addEntry
|
case addEntry
|
||||||
case noResult
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ProfileFieldItem {
|
extension ProfileFieldItem {
|
||||||
|
@ -33,44 +33,57 @@ extension ProfileFieldSection {
|
|||||||
collectionView.register(ProfileFieldCollectionViewHeaderFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: ProfileFieldCollectionViewHeaderFooterView.footerReuseIdentifer)
|
collectionView.register(ProfileFieldCollectionViewHeaderFooterView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: ProfileFieldCollectionViewHeaderFooterView.footerReuseIdentifer)
|
||||||
|
|
||||||
let fieldCellRegistration = UICollectionView.CellRegistration<ProfileFieldCollectionViewCell, ProfileFieldItem> { cell, indexPath, item in
|
let fieldCellRegistration = UICollectionView.CellRegistration<ProfileFieldCollectionViewCell, ProfileFieldItem> { cell, indexPath, item in
|
||||||
guard case let .field(field) = item else { return }
|
let key, value: String
|
||||||
|
let emojiMeta: MastodonContent.Emojis
|
||||||
|
let verified: Bool
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case .field(field: let field):
|
||||||
|
key = field.name.value
|
||||||
|
value = field.value.value
|
||||||
|
emojiMeta = field.emojiMeta
|
||||||
|
verified = field.verifiedAt.value != nil
|
||||||
|
case .createdAt(date: let date):
|
||||||
|
key = L10n.Scene.Profile.Fields.joined
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .medium
|
||||||
|
formatter.timeStyle = .none
|
||||||
|
value = formatter.string(from: date)
|
||||||
|
emojiMeta = [:]
|
||||||
|
verified = false
|
||||||
|
default: return
|
||||||
|
}
|
||||||
|
|
||||||
// set key
|
// set key
|
||||||
do {
|
do {
|
||||||
let mastodonContent = MastodonContent(content: field.name.value, emojis: field.emojiMeta)
|
let mastodonContent = MastodonContent(content: key, emojis: emojiMeta)
|
||||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||||
cell.keyMetaLabel.configure(content: metaContent)
|
cell.keyMetaLabel.configure(content: metaContent)
|
||||||
} catch {
|
} catch {
|
||||||
let content = PlaintextMetaContent(string: field.name.value)
|
let content = PlaintextMetaContent(string: key)
|
||||||
cell.keyMetaLabel.configure(content: content)
|
cell.keyMetaLabel.configure(content: content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set value
|
// set value
|
||||||
|
let linkColor = verified ? Asset.Scene.Profile.About.bioAboutFieldVerifiedLink.color : Asset.Colors.brand.color
|
||||||
do {
|
do {
|
||||||
let mastodonContent = MastodonContent(content: field.value.value, emojis: field.emojiMeta)
|
let mastodonContent = MastodonContent(content: value, emojis: emojiMeta)
|
||||||
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
let metaContent = try MastodonMetaContent.convert(document: mastodonContent)
|
||||||
cell.valueMetaLabel.linkAttributes[.foregroundColor] = Asset.Colors.brand.color
|
cell.valueMetaLabel.linkAttributes[.foregroundColor] = linkColor
|
||||||
if field.verifiedAt.value != nil {
|
|
||||||
cell.valueMetaLabel.linkAttributes[.foregroundColor] = Asset.Scene.Profile.About.bioAboutFieldVerifiedLink.color
|
|
||||||
}
|
|
||||||
cell.valueMetaLabel.configure(content: metaContent)
|
cell.valueMetaLabel.configure(content: metaContent)
|
||||||
} catch {
|
} catch {
|
||||||
let content = PlaintextMetaContent(string: field.value.value)
|
let content = PlaintextMetaContent(string: value)
|
||||||
|
cell.valueMetaLabel.linkAttributes[.foregroundColor] = linkColor
|
||||||
cell.valueMetaLabel.configure(content: content)
|
cell.valueMetaLabel.configure(content: content)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set background
|
// set background
|
||||||
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
||||||
backgroundConfiguration.backgroundColor = UIColor.secondarySystemBackground
|
backgroundConfiguration.backgroundColor = verified ? Asset.Scene.Profile.About.bioAboutFieldVerifiedBackground.color : UIColor.secondarySystemBackground
|
||||||
if (field.verifiedAt.value != nil) {
|
|
||||||
backgroundConfiguration.backgroundColor = Asset.Scene.Profile.About.bioAboutFieldVerifiedBackground.color
|
|
||||||
}
|
|
||||||
cell.backgroundConfiguration = backgroundConfiguration
|
cell.backgroundConfiguration = backgroundConfiguration
|
||||||
|
|
||||||
// set checkmark and edit menu label
|
// set checkmark and edit menu label
|
||||||
cell.checkmark.isHidden = true
|
if case .field(let field) = item, let verifiedAt = field.verifiedAt.value {
|
||||||
cell.checkmarkPopoverString = nil
|
|
||||||
if let verifiedAt = field.verifiedAt.value {
|
|
||||||
cell.checkmark.isHidden = false
|
cell.checkmark.isHidden = false
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateStyle = .medium
|
formatter.dateStyle = .medium
|
||||||
@ -78,6 +91,9 @@ extension ProfileFieldSection {
|
|||||||
let dateString = formatter.string(from: verifiedAt)
|
let dateString = formatter.string(from: verifiedAt)
|
||||||
cell.checkmark.accessibilityLabel = L10n.Scene.Profile.Fields.Verified.long(dateString)
|
cell.checkmark.accessibilityLabel = L10n.Scene.Profile.Fields.Verified.long(dateString)
|
||||||
cell.checkmarkPopoverString = L10n.Scene.Profile.Fields.Verified.short(dateString)
|
cell.checkmarkPopoverString = L10n.Scene.Profile.Fields.Verified.short(dateString)
|
||||||
|
} else {
|
||||||
|
cell.checkmark.isHidden = true
|
||||||
|
cell.checkmarkPopoverString = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.delegate = configuration.profileFieldCollectionViewCellDelegate
|
cell.delegate = configuration.profileFieldCollectionViewCellDelegate
|
||||||
@ -128,26 +144,10 @@ extension ProfileFieldSection {
|
|||||||
}
|
}
|
||||||
cell.backgroundConfiguration = backgroundConfiguration
|
cell.backgroundConfiguration = backgroundConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
let noResultCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, ProfileFieldItem> { cell, indexPath, item in
|
|
||||||
guard case .noResult = item else { return }
|
|
||||||
|
|
||||||
var contentConfiguration = cell.defaultContentConfiguration()
|
|
||||||
contentConfiguration.text = L10n.Scene.Search.Searching.EmptyState.noResults // FIXME:
|
|
||||||
contentConfiguration.textProperties.alignment = .center
|
|
||||||
cell.contentConfiguration = contentConfiguration
|
|
||||||
|
|
||||||
|
|
||||||
var backgroundConfiguration = UIBackgroundConfiguration.listPlainCell()
|
|
||||||
backgroundConfiguration.backgroundColorTransformer = .init { _ in
|
|
||||||
return .secondarySystemBackground
|
|
||||||
}
|
|
||||||
cell.backgroundConfiguration = backgroundConfiguration
|
|
||||||
}
|
|
||||||
|
|
||||||
let dataSource = UICollectionViewDiffableDataSource<ProfileFieldSection, ProfileFieldItem>(collectionView: collectionView) { collectionView, indexPath, item in
|
let dataSource = UICollectionViewDiffableDataSource<ProfileFieldSection, ProfileFieldItem>(collectionView: collectionView) { collectionView, indexPath, item in
|
||||||
switch item {
|
switch item {
|
||||||
case .field:
|
case .field, .createdAt:
|
||||||
return collectionView.dequeueConfiguredReusableCell(
|
return collectionView.dequeueConfiguredReusableCell(
|
||||||
using: fieldCellRegistration,
|
using: fieldCellRegistration,
|
||||||
for: indexPath,
|
for: indexPath,
|
||||||
@ -165,12 +165,6 @@ extension ProfileFieldSection {
|
|||||||
for: indexPath,
|
for: indexPath,
|
||||||
item: item
|
item: item
|
||||||
)
|
)
|
||||||
case .noResult:
|
|
||||||
return collectionView.dequeueConfiguredReusableCell(
|
|
||||||
using: noResultCellRegistration,
|
|
||||||
for: indexPath,
|
|
||||||
item: item
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,23 +50,33 @@ extension ProfileAboutViewModel {
|
|||||||
var snapshot = NSDiffableDataSourceSnapshot<ProfileFieldSection, ProfileFieldItem>()
|
var snapshot = NSDiffableDataSourceSnapshot<ProfileFieldSection, ProfileFieldItem>()
|
||||||
snapshot.appendSections([.main])
|
snapshot.appendSections([.main])
|
||||||
diffableDataSource.apply(snapshot)
|
diffableDataSource.apply(snapshot)
|
||||||
|
|
||||||
Publishers.CombineLatest4(
|
let fields = Publishers.CombineLatest3(
|
||||||
$isEditing.removeDuplicates(),
|
$isEditing.removeDuplicates(),
|
||||||
profileInfo.$fields.removeDuplicates(),
|
profileInfo.$fields.removeDuplicates(),
|
||||||
profileInfoEditing.$fields.removeDuplicates(),
|
profileInfoEditing.$fields.removeDuplicates()
|
||||||
|
).map { isEditing, displayFields, editingFields in
|
||||||
|
isEditing ? editingFields : displayFields
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Publishers.CombineLatest4(
|
||||||
|
$isEditing.removeDuplicates(),
|
||||||
|
$createdAt.removeDuplicates(),
|
||||||
|
fields,
|
||||||
$emojiMeta.removeDuplicates()
|
$emojiMeta.removeDuplicates()
|
||||||
)
|
)
|
||||||
.throttle(for: 0.3, scheduler: DispatchQueue.main, latest: true)
|
.throttle(for: 0.3, scheduler: DispatchQueue.main, latest: true)
|
||||||
.sink { [weak self] isEditing, displayFields, editingFields, emojiMeta in
|
.sink { [weak self] isEditing, createdAt, fields, emojiMeta in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard let diffableDataSource = self.diffableDataSource else { return }
|
guard let diffableDataSource = self.diffableDataSource else { return }
|
||||||
|
|
||||||
var snapshot = NSDiffableDataSourceSnapshot<ProfileFieldSection, ProfileFieldItem>()
|
var snapshot = NSDiffableDataSourceSnapshot<ProfileFieldSection, ProfileFieldItem>()
|
||||||
snapshot.appendSections([.main])
|
snapshot.appendSections([.main])
|
||||||
|
|
||||||
let fields: [ProfileFieldItem.FieldValue] = isEditing ? editingFields : displayFields
|
var items: [ProfileFieldItem] = [
|
||||||
var items: [ProfileFieldItem] = fields.map { field in
|
.createdAt(date: createdAt),
|
||||||
|
] + fields.map { field in
|
||||||
if isEditing {
|
if isEditing {
|
||||||
return ProfileFieldItem.editField(field: field)
|
return ProfileFieldItem.editField(field: field)
|
||||||
} else {
|
} else {
|
||||||
@ -78,10 +88,6 @@ extension ProfileAboutViewModel {
|
|||||||
items.append(.addEntry)
|
items.append(.addEntry)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isEditing, items.isEmpty {
|
|
||||||
items.append(.noResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshot.appendItems(items, toSection: .main)
|
snapshot.appendItems(items, toSection: .main)
|
||||||
|
|
||||||
diffableDataSource.apply(snapshot, animatingDifferences: false, completion: nil)
|
diffableDataSource.apply(snapshot, animatingDifferences: false, completion: nil)
|
||||||
|
@ -31,6 +31,7 @@ final class ProfileAboutViewModel {
|
|||||||
|
|
||||||
@Published var fields: [MastodonField] = []
|
@Published var fields: [MastodonField] = []
|
||||||
@Published var emojiMeta: MastodonContent.Emojis = [:]
|
@Published var emojiMeta: MastodonContent.Emojis = [:]
|
||||||
|
@Published var createdAt: Date = Date()
|
||||||
|
|
||||||
init(context: AppContext) {
|
init(context: AppContext) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -46,6 +47,11 @@ final class ProfileAboutViewModel {
|
|||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
.flatMap { $0.publisher(for: \.fields) }
|
.flatMap { $0.publisher(for: \.fields) }
|
||||||
.assign(to: &$fields)
|
.assign(to: &$fields)
|
||||||
|
|
||||||
|
$user
|
||||||
|
.compactMap { $0 }
|
||||||
|
.flatMap { $0.publisher(for: \.createdAt) }
|
||||||
|
.assign(to: &$createdAt)
|
||||||
|
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
$fields,
|
$fields,
|
||||||
|
@ -745,6 +745,8 @@ public enum L10n {
|
|||||||
public enum Fields {
|
public enum Fields {
|
||||||
/// Add Row
|
/// Add Row
|
||||||
public static let addRow = L10n.tr("Localizable", "Scene.Profile.Fields.AddRow", fallback: "Add Row")
|
public static let addRow = L10n.tr("Localizable", "Scene.Profile.Fields.AddRow", fallback: "Add Row")
|
||||||
|
/// Joined
|
||||||
|
public static let joined = L10n.tr("Localizable", "Scene.Profile.Fields.Joined", fallback: "Joined")
|
||||||
public enum Placeholder {
|
public enum Placeholder {
|
||||||
/// Content
|
/// Content
|
||||||
public static let content = L10n.tr("Localizable", "Scene.Profile.Fields.Placeholder.Content", fallback: "Content")
|
public static let content = L10n.tr("Localizable", "Scene.Profile.Fields.Placeholder.Content", fallback: "Content")
|
||||||
|
@ -268,6 +268,7 @@ uploaded to Mastodon.";
|
|||||||
"Scene.Profile.Dashboard.Following" = "following";
|
"Scene.Profile.Dashboard.Following" = "following";
|
||||||
"Scene.Profile.Dashboard.Posts" = "posts";
|
"Scene.Profile.Dashboard.Posts" = "posts";
|
||||||
"Scene.Profile.Fields.AddRow" = "Add Row";
|
"Scene.Profile.Fields.AddRow" = "Add Row";
|
||||||
|
"Scene.Profile.Fields.Joined" = "Joined";
|
||||||
"Scene.Profile.Fields.Placeholder.Content" = "Content";
|
"Scene.Profile.Fields.Placeholder.Content" = "Content";
|
||||||
"Scene.Profile.Fields.Placeholder.Label" = "Label";
|
"Scene.Profile.Fields.Placeholder.Label" = "Label";
|
||||||
"Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@";
|
"Scene.Profile.Fields.Verified.Long" = "Ownership of this link was checked on %@";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user