Merge branch 'main' into iOS-18

This commit is contained in:
Thomas Ricouard 2024-07-02 20:22:29 +02:00
commit 416dc702b2
9 changed files with 758 additions and 297 deletions

View file

@ -44,6 +44,12 @@ extension View {
selectedTagGroup: .constant(nil), selectedTagGroup: .constant(nil),
scrollToTopSignal: .constant(0), scrollToTopSignal: .constant(0),
canFilterTimeline: false) 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): case let .following(id):
AccountsListView(mode: .following(accountId: id)) AccountsListView(mode: .following(accountId: id))
case let .followers(id): case let .followers(id):

View file

@ -186,6 +186,7 @@ class SidebarTabs {
.init(tab: .favorites, enabled: true), .init(tab: .favorites, enabled: true),
.init(tab: .followedTags, enabled: true), .init(tab: .followedTags, enabled: true),
.init(tab: .lists, enabled: true), .init(tab: .lists, enabled: true),
.init(tab: .links, enabled: true),
.init(tab: .settings, enabled: true), .init(tab: .settings, enabled: true),
.init(tab: .profile, enabled: true), .init(tab: .profile, enabled: true),
@ -221,7 +222,7 @@ class iOSTabs {
@AppStorage(TabEntries.first.rawValue) var firstTab = Tab.timeline @AppStorage(TabEntries.first.rawValue) var firstTab = Tab.timeline
@AppStorage(TabEntries.second.rawValue) var secondTab = Tab.notifications @AppStorage(TabEntries.second.rawValue) var secondTab = Tab.notifications
@AppStorage(TabEntries.third.rawValue) var thirdTab = Tab.explore @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 @AppStorage(TabEntries.fifth.rawValue) var fifthTab = Tab.profile
} }

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ public enum Timelines: Endpoint {
case home(sinceId: String?, maxId: String?, minId: String?) case home(sinceId: String?, maxId: String?, minId: String?)
case list(listId: String, 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 hashtag(tag: String, additional: [String]?, maxId: String?, minId: String?)
case link(url: URL, sinceId: String?, maxId: String?, minId: String?)
public func path() -> String { public func path() -> String {
switch self { switch self {
@ -16,6 +17,8 @@ public enum Timelines: Endpoint {
"timelines/list/\(listId)" "timelines/list/\(listId)"
case let .hashtag(tag, _, _, _): case let .hashtag(tag, _, _, _):
"timelines/tag/\(tag)" "timelines/tag/\(tag)"
case .link:
"timelines/link"
} }
} }
@ -34,6 +37,10 @@ public enum Timelines: Endpoint {
params.append(contentsOf: (additional ?? []) params.append(contentsOf: (additional ?? [])
.map { URLQueryItem(name: "any[]", value: $0) }) .map { URLQueryItem(name: "any[]", value: $0) })
return params 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(Theme.self) private var theme
@Environment(RouterPath.self) private var routerPath @Environment(RouterPath.self) private var routerPath
@Environment(CurrentInstance.self) private var currentInstance
let card: Card let card: Card
@ -122,6 +123,9 @@ public struct StatusRowCardView: View {
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
.lineLimit(1) .lineLimit(1)
if let account = card.authors?.first?.account {
moreFromAccountView(account)
}
} }
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.padding(10) .padding(10)
@ -149,18 +153,39 @@ public struct StatusRowCardView: View {
.frame(width: imageHeight, height: imageHeight) .frame(width: imageHeight, height: imageHeight)
} }
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
Text(title) Text(card.providerName ?? url.host() ?? url.absoluteString)
.font(.scaledHeadline)
.lineLimit(3)
Text(url.host() ?? url.absoluteString)
.font(.scaledFootnote) .font(.scaledFootnote)
.foregroundColor(theme.tintColor) .foregroundColor(theme.tintColor)
.lineLimit(1) .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 { if let history = card.history {
let uses = history.compactMap { Int($0.accounts) }.reduce(0, +) let uses = history.compactMap { Int($0.accounts) }.reduce(0, +)
HStack(spacing: 4) { HStack(spacing: 4) {
Image(systemName: "bubble.left.and.text.bubble.right") Button {
Text("trending-tag-people-talking \(uses)") 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() Spacer()
Button { Button {
#if targetEnvironment(macCatalyst) #if targetEnvironment(macCatalyst)
@ -170,11 +195,11 @@ public struct StatusRowCardView: View {
#endif #endif
} label: { } label: {
Image(systemName: "quote.opening") Image(systemName: "quote.opening")
.foregroundStyle(.secondary)
} }
.buttonStyle(.plain) .buttonStyle(.plain)
} }
.font(.scaledCaption) .font(.scaledCaption)
.foregroundStyle(.secondary)
.lineLimit(1) .lineLimit(1)
.padding(.top, 12) .padding(.top, 12)
} }
@ -222,6 +247,37 @@ public struct StatusRowCardView: View {
}.padding(16) }.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 { 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 tagGroup(title: String, tags: [String], symbolName: String?)
case list(list: Models.List) case list(list: Models.List)
case remoteLocal(server: String, filter: RemoteTimelineFilter) case remoteLocal(server: String, filter: RemoteTimelineFilter)
case link(url: URL, title: String)
case latest case latest
case resume case resume
@ -46,6 +47,8 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
return list.id return list.id
case let .tagGroup(title, tags, _): case let .tagGroup(title, tags, _):
return title + tags.joined() return title + tags.joined()
case let .link(url, _):
return url.absoluteString
default: default:
return title return title
} }
@ -87,6 +90,8 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
"Trending" "Trending"
case .home: case .home:
"Home" "Home"
case let .link(_, title):
title
case let .hashtag(tag, _): case let .hashtag(tag, _):
"#\(tag)" "#\(tag)"
case let .tagGroup(title, _, _): case let .tagGroup(title, _, _):
@ -112,6 +117,8 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
"timeline.trending" "timeline.trending"
case .home: case .home:
"timeline.home" "timeline.home"
case let .link(_, title):
LocalizedStringKey(title)
case let .hashtag(tag, _): case let .hashtag(tag, _):
"#\(tag)" "#\(tag)"
case let .tagGroup(title, _, _): case let .tagGroup(title, _, _):
@ -145,6 +152,8 @@ public enum TimelineFilter: Hashable, Equatable, Identifiable, Sendable {
symbolName ?? "tag" symbolName ?? "tag"
case .hashtag: case .hashtag:
"number" "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 .resume: return Timelines.home(sinceId: nil, maxId: nil, minId: nil)
case .home: return Timelines.home(sinceId: sinceId, maxId: maxId, minId: minId) case .home: return Timelines.home(sinceId: sinceId, maxId: maxId, minId: minId)
case .trending: return Trends.statuses(offset: offset) 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 .list(list): return Timelines.list(listId: list.id, sinceId: sinceId, maxId: maxId, minId: minId)
case let .hashtag(tag, accountId): case let .hashtag(tag, accountId):
if let accountId { if let accountId {
@ -202,6 +212,7 @@ extension TimelineFilter: Codable {
case remoteLocal case remoteLocal
case latest case latest
case resume case resume
case link
} }
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
@ -250,6 +261,11 @@ extension TimelineFilter: Codable {
) )
case .latest: case .latest:
self = .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: default:
throw DecodingError.dataCorrupted( throw DecodingError.dataCorrupted(
DecodingError.Context( DecodingError.Context(
@ -290,6 +306,10 @@ extension TimelineFilter: Codable {
try container.encode(CodingKeys.latest.rawValue, forKey: .latest) try container.encode(CodingKeys.latest.rawValue, forKey: .latest)
case .resume: case .resume:
try container.encode(CodingKeys.resume.rawValue, forKey: .latest) 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)
} }
} }
} }