Redesign News + support links attributions

This commit is contained in:
Thomas Ricouard 2024-07-02 19:59:21 +02:00
parent 2d04433783
commit 478a788f87
9 changed files with 158 additions and 52 deletions

View file

@ -44,6 +44,12 @@ extension View {
selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false)
case let .linkTimeline(url, title):
TimelineView(timeline: .constant(.link(url: url, title: title)),
pinnedFilters: .constant([]),
selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0),
canFilterTimeline: false)
case let .following(id):
AccountsListView(mode: .following(accountId: id))
case let .followers(id):

View file

@ -221,7 +221,7 @@ class iOSTabs {
@AppStorage(TabEntries.first.rawValue) var firstTab = Tab.timeline
@AppStorage(TabEntries.second.rawValue) var secondTab = Tab.notifications
@AppStorage(TabEntries.third.rawValue) var thirdTab = Tab.explore
@AppStorage(TabEntries.fourth.rawValue) var fourthTab = Tab.messages
@AppStorage(TabEntries.fourth.rawValue) var fourthTab = Tab.links
@AppStorage(TabEntries.fifth.rawValue) var fifthTab = Tab.profile
}

View file

@ -28457,115 +28457,115 @@
"localizations" : {
"be" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Трэндавыя спасылкі"
}
},
"ca" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Enllaços populars"
}
},
"de" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Links im Trend"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Trending Links"
"value" : "News"
}
},
"en-GB" : {
"stringUnit" : {
"state" : "translated",
"value" : "Trending Links"
"value" : "News"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Enlaces que son tendencia"
}
},
"eu" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Gori-gorian dauden estekak"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Liens tendance"
}
},
"it" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Link in tendenza"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "人気のリンク"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "뜨고 있는 링크"
}
},
"nb" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Trendende lenker"
}
},
"nl" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Trending links"
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Popularne linki"
}
},
"pt-BR" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Links em Tendência"
}
},
"tr" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Yükselişteki Bağlantılar"
}
},
"uk" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Популярні посилання"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "当下流行的网页"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "流行連結"
}
}
@ -82387,7 +82387,7 @@
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld people talking"
}
}
@ -82417,13 +82417,13 @@
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld Person redet"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld Personen reden"
}
}
@ -82436,13 +82436,13 @@
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld person talking"
"value" : "%lld post"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld people talking"
"value" : "%lld posts"
}
}
}
@ -82454,13 +82454,13 @@
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld people talking"
"value" : "%lld post"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld people talking"
"value" : "%lld posts"
}
}
}
@ -82471,13 +82471,13 @@
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld persona hablando"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld personas hablando"
}
}
@ -82489,13 +82489,13 @@
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "Pertsona batek hizpide"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld pertsonak hizpide"
}
}
@ -82507,13 +82507,13 @@
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld personne parle"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld personnes parlent"
}
}
@ -82525,13 +82525,13 @@
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld persona ne parla"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld persone ne parlano"
}
}
@ -82615,25 +82615,25 @@
"plural" : {
"few" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld osoby rozmawiają"
}
},
"many" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld osób rozmawia"
}
},
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld osoba rozmawia"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld osób rozmawia"
}
}
@ -82663,13 +82663,13 @@
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld kişi konuşuyor"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld kişi konuşuyor"
}
}
@ -82699,13 +82699,13 @@
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld 人正在参与讨论"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld 人正在参与讨论"
}
}
@ -82717,13 +82717,13 @@
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld 人議論中"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"state" : "needs_review",
"value" : "%lld 人議論中"
}
}

View file

@ -38,6 +38,10 @@ import Observation
public var isNotificationsFilterSupported: Bool {
version >= 4.3
}
public var isLinkTimelineSupported: Bool {
version >= 4.3
}
private init() {}

View file

@ -21,6 +21,7 @@ public enum RouterDestination: Hashable {
case rebloggedBy(id: String)
case accountsList(accounts: [Account])
case trendingTimeline
case linkTimeline(url: URL, title: String)
case trendingLinks(cards: [Card])
case tagsList(tags: [Tag])
case notificationsRequests

View file

@ -4,15 +4,27 @@ public struct Card: Codable, Identifiable, Equatable, Hashable {
public var id: String {
url
}
public struct CardAuthor: Codable, Sendable, Identifiable, Equatable, Hashable {
public var id: String {
url
}
public let name: String
public let url: String
public let account: Account?
}
public let url: String
public let title: String?
public let authorName: String?
public let description: String?
public let providerName: String?
public let type: String
public let image: URL?
public let width: CGFloat
public let height: CGFloat
public let history: [History]?
public let authors: [CardAuthor]?
}
extension Card: Sendable {}

View file

@ -5,6 +5,7 @@ public enum Timelines: Endpoint {
case home(sinceId: String?, maxId: String?, minId: String?)
case list(listId: String, sinceId: String?, maxId: String?, minId: String?)
case hashtag(tag: String, additional: [String]?, maxId: String?, minId: String?)
case link(url: URL, sinceId: String?, maxId: String?, minId: String?)
public func path() -> String {
switch self {
@ -16,6 +17,8 @@ public enum Timelines: Endpoint {
"timelines/list/\(listId)"
case let .hashtag(tag, _, _, _):
"timelines/tag/\(tag)"
case .link:
"timelines/link"
}
}
@ -34,6 +37,10 @@ public enum Timelines: Endpoint {
params.append(contentsOf: (additional ?? [])
.map { URLQueryItem(name: "any[]", value: $0) })
return params
case let .link(url, sinceId, maxId, minId):
var params = makePaginationParam(sinceId: sinceId, maxId: maxId, mindId: minId) ?? []
params.append(.init(name: "url", value: url.absoluteString))
return params
}
}
}

View file

@ -14,6 +14,7 @@ public struct StatusRowCardView: View {
@Environment(Theme.self) private var theme
@Environment(RouterPath.self) private var routerPath
@Environment(CurrentInstance.self) private var currentInstance
let card: Card
@ -122,6 +123,9 @@ public struct StatusRowCardView: View {
.font(.scaledFootnote)
.foregroundColor(theme.tintColor)
.lineLimit(1)
if let account = card.authors?.first?.account {
moreFromAccountView(account)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(10)
@ -149,18 +153,39 @@ public struct StatusRowCardView: View {
.frame(width: imageHeight, height: imageHeight)
}
VStack(alignment: .leading, spacing: 6) {
Text(title)
.font(.scaledHeadline)
.lineLimit(3)
Text(url.host() ?? url.absoluteString)
Text(card.providerName ?? url.host() ?? url.absoluteString)
.font(.scaledFootnote)
.foregroundColor(theme.tintColor)
.lineLimit(1)
Text(title)
.font(.scaledHeadline)
.lineLimit(3)
if let account = card.authors?.first?.account {
moreFromAccountView(account, divider: false)
} else if let authorName = card.authorName, !authorName.isEmpty {
Text("by \(authorName)")
.font(.scaledFootnote)
.foregroundStyle(.secondary)
.lineLimit(1)
}
if let history = card.history {
let uses = history.compactMap { Int($0.accounts) }.reduce(0, +)
HStack(spacing: 4) {
Image(systemName: "bubble.left.and.text.bubble.right")
Text("trending-tag-people-talking \(uses)")
Button {
if currentInstance.isLinkTimelineSupported {
routerPath.navigate(to: .linkTimeline(url: url, title: title))
}
} label: {
HStack(spacing: 4) {
Image(systemName: "bubble.left.and.text.bubble.right")
Text("trending-tag-people-talking \(uses)")
if currentInstance.isLinkTimelineSupported {
Image(systemName: "chevron.right")
}
}
}
.buttonStyle(.bordered)
Spacer()
Button {
#if targetEnvironment(macCatalyst)
@ -170,11 +195,11 @@ public struct StatusRowCardView: View {
#endif
} label: {
Image(systemName: "quote.opening")
.foregroundStyle(.secondary)
}
.buttonStyle(.plain)
}
.font(.scaledCaption)
.foregroundStyle(.secondary)
.lineLimit(1)
.padding(.top, 12)
}
@ -222,6 +247,37 @@ public struct StatusRowCardView: View {
}.padding(16)
}
}
@ViewBuilder
private func moreFromAccountView(_ account: Account, divider: Bool = true) -> some View {
if divider {
Divider()
}
Button {
routerPath.navigate(to: .accountDetailWithAccount(account: account))
} label: {
HStack(alignment: .center, spacing: 8) {
AvatarView(account.avatar, config: .list)
.padding(.top, 2)
HStack(alignment: .firstTextBaseline, spacing: 0) {
Text("More from ")
EmojiTextApp(account.cachedDisplayName, emojis: account.emojis)
.fontWeight(.semibold)
.emojiText.size(Font.scaledFootnoteFont.emojiSize)
.emojiText.baselineOffset(Font.scaledFootnoteFont.emojiBaselineOffset)
}
.font(.scaledFootnote)
.lineLimit(1)
.padding(.top, 3)
Spacer()
Image(systemName: "chevron.right")
}
.padding(.vertical, 4)
}
.buttonStyle(.plain)
}
}
struct DefaultPreviewImage: View {

View file

@ -35,6 +35,7 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
case tagGroup(title: String, tags: [String], symbolName: String?)
case list(list: Models.List)
case remoteLocal(server: String, filter: RemoteTimelineFilter)
case link(url: URL, title: String)
case latest
case resume
@ -46,6 +47,8 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
return list.id
case let .tagGroup(title, tags, _):
return title + tags.joined()
case let .link(url, _):
return url.absoluteString
default:
return title
}
@ -87,6 +90,8 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
"Trending"
case .home:
"Home"
case let .link(_, title):
title
case let .hashtag(tag, _):
"#\(tag)"
case let .tagGroup(title, _, _):
@ -112,6 +117,8 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
"timeline.trending"
case .home:
"timeline.home"
case let .link(_, title):
LocalizedStringKey(title)
case let .hashtag(tag, _):
"#\(tag)"
case let .tagGroup(title, _, _):
@ -145,6 +152,8 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
symbolName ?? "tag"
case .hashtag:
"number"
case .link:
"link"
}
}
@ -165,6 +174,7 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
case .resume: return Timelines.home(sinceId: nil, maxId: nil, minId: nil)
case .home: return Timelines.home(sinceId: sinceId, maxId: maxId, minId: minId)
case .trending: return Trends.statuses(offset: offset)
case let .link(url, _): return Timelines.link(url: url, sinceId: sinceId, maxId: maxId, minId: minId)
case let .list(list): return Timelines.list(listId: list.id, sinceId: sinceId, maxId: maxId, minId: minId)
case let .hashtag(tag, accountId):
if let accountId {
@ -202,6 +212,7 @@ extension TimelineFilter: Codable {
case remoteLocal
case latest
case resume
case link
}
public init(from decoder: Decoder) throws {
@ -250,6 +261,11 @@ extension TimelineFilter: Codable {
)
case .latest:
self = .latest
case .link:
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .link)
let url = try nestedContainer.decode(URL.self)
let title = try nestedContainer.decode(String.self)
self = .link(url: url, title: title)
default:
throw DecodingError.dataCorrupted(
DecodingError.Context(
@ -290,6 +306,10 @@ extension TimelineFilter: Codable {
try container.encode(CodingKeys.latest.rawValue, forKey: .latest)
case .resume:
try container.encode(CodingKeys.resume.rawValue, forKey: .latest)
case let .link(url, title):
var nestedContainer = container.nestedUnkeyedContainer(forKey: .link)
try nestedContainer.encode(url)
try nestedContainer.encode(title)
}
}
}