mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-10 19:20:59 +00:00
Redesign News + support links attributions
This commit is contained in:
parent
2d04433783
commit
478a788f87
9 changed files with 158 additions and 52 deletions
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 人議論中"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,10 @@ import Observation
|
|||
public var isNotificationsFilterSupported: Bool {
|
||||
version >= 4.3
|
||||
}
|
||||
|
||||
public var isLinkTimelineSupported: Bool {
|
||||
version >= 4.3
|
||||
}
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue