mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-01 12:41:00 +00:00
Merge branch 'main' into iOS-18
This commit is contained in:
commit
416dc702b2
9 changed files with 758 additions and 297 deletions
|
@ -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):
|
||||||
|
|
|
@ -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
|
@ -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() {}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue