mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-22 07:06:40 +00:00
Featured tags
This commit is contained in:
parent
07188a6818
commit
0f2c2df624
8 changed files with 71 additions and 23 deletions
|
@ -15,8 +15,8 @@ extension View {
|
||||||
AccountDetailView(account: account)
|
AccountDetailView(account: account)
|
||||||
case let .statusDetail(id):
|
case let .statusDetail(id):
|
||||||
StatusDetailView(statusId: id)
|
StatusDetailView(statusId: id)
|
||||||
case let .hashTag(tag):
|
case let .hashTag(tag, accountId):
|
||||||
TimelineView(timeline: .hashtag(tag: tag))
|
TimelineView(timeline: .hashtag(tag: tag, accountId: accountId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,8 @@ public struct AccountDetailView: View {
|
||||||
} content: {
|
} content: {
|
||||||
LazyVStack {
|
LazyVStack {
|
||||||
headerView
|
headerView
|
||||||
|
featuredTagsView
|
||||||
|
.offset(y: -36)
|
||||||
if isCurrentUser {
|
if isCurrentUser {
|
||||||
Picker("", selection: $viewModel.selectedTab) {
|
Picker("", selection: $viewModel.selectedTab) {
|
||||||
ForEach(AccountDetailViewModel.Tab.allCases, id: \.self) { tab in
|
ForEach(AccountDetailViewModel.Tab.allCases, id: \.self) { tab in
|
||||||
|
@ -107,6 +109,29 @@ public struct AccountDetailView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var featuredTagsView: some View {
|
||||||
|
if !viewModel.featuredTags.isEmpty {
|
||||||
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
|
HStack(spacing: 4) {
|
||||||
|
ForEach(viewModel.featuredTags) { tag in
|
||||||
|
Button {
|
||||||
|
routeurPath.navigate(to: .hashTag(tag: tag.name, account: viewModel.accountId))
|
||||||
|
} label: {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text("#\(tag.name)")
|
||||||
|
.font(.callout)
|
||||||
|
Text("\(tag.statusesCount) posts")
|
||||||
|
.font(.caption2)
|
||||||
|
}
|
||||||
|
}.buttonStyle(.bordered)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.leading, DS.Constants.layoutPadding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func makeTagsListView(tags: [Tag]) -> some View {
|
private func makeTagsListView(tags: [Tag]) -> some View {
|
||||||
Group {
|
Group {
|
||||||
ForEach(tags) { tag in
|
ForEach(tags) { tag in
|
||||||
|
@ -123,7 +148,7 @@ public struct AccountDetailView: View {
|
||||||
.padding(.horizontal, DS.Constants.layoutPadding)
|
.padding(.horizontal, DS.Constants.layoutPadding)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
routeurPath.navigate(to: .hashTag(tag: tag.name))
|
routeurPath.navigate(to: .hashTag(tag: tag.name, account: nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
|
||||||
@Published var relationship: Relationshionship?
|
@Published var relationship: Relationshionship?
|
||||||
@Published var favourites: [Status] = []
|
@Published var favourites: [Status] = []
|
||||||
@Published var followedTags: [Tag] = []
|
@Published var followedTags: [Tag] = []
|
||||||
|
@Published var featuredTags: [FeaturedTag] = []
|
||||||
@Published var selectedTab = Tab.statuses {
|
@Published var selectedTab = Tab.statuses {
|
||||||
didSet {
|
didSet {
|
||||||
reloadTabState()
|
reloadTabState()
|
||||||
|
@ -82,6 +83,8 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
|
||||||
let relationships: [Relationshionship] = try await client.get(endpoint: Accounts.relationships(id: accountId))
|
let relationships: [Relationshionship] = try await client.get(endpoint: Accounts.relationships(id: accountId))
|
||||||
self.relationship = relationships.first
|
self.relationship = relationships.first
|
||||||
}
|
}
|
||||||
|
self.featuredTags = try await client.get(endpoint: Accounts.featuredTags(id: accountId))
|
||||||
|
self.featuredTags.sort { $0.statusesCountInt > $1.statusesCountInt }
|
||||||
self.title = account.displayName
|
self.title = account.displayName
|
||||||
accountState = .data(account: account)
|
accountState = .data(account: account)
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -93,7 +96,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
|
||||||
guard let client else { return }
|
guard let client else { return }
|
||||||
do {
|
do {
|
||||||
tabState = .statuses(statusesState: .loading)
|
tabState = .statuses(statusesState: .loading)
|
||||||
statuses = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: nil))
|
statuses = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: nil, tag: nil))
|
||||||
if isCurrentUser {
|
if isCurrentUser {
|
||||||
favourites = try await client.get(endpoint: Accounts.favourites)
|
favourites = try await client.get(endpoint: Accounts.favourites)
|
||||||
}
|
}
|
||||||
|
@ -110,7 +113,7 @@ class AccountDetailViewModel: ObservableObject, StatusesFetcher {
|
||||||
case .statuses:
|
case .statuses:
|
||||||
guard let lastId = statuses.last?.id else { return }
|
guard let lastId = statuses.last?.id else { return }
|
||||||
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .loadingNextPage))
|
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .loadingNextPage))
|
||||||
let newStatuses: [Status] = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: lastId))
|
let newStatuses: [Status] = try await client.get(endpoint: Accounts.statuses(id: accountId, sinceId: lastId, tag: nil))
|
||||||
statuses.append(contentsOf: newStatuses)
|
statuses.append(contentsOf: newStatuses)
|
||||||
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .hasNextPage))
|
tabState = .statuses(statusesState: .display(statuses: statuses, nextPageState: .hasNextPage))
|
||||||
case .favourites, .followedTags:
|
case .favourites, .followedTags:
|
||||||
|
|
|
@ -25,3 +25,13 @@ public struct Tag: Codable, Identifiable {
|
||||||
history.compactMap{ Int($0.accounts) }.reduce(0, +)
|
history.compactMap{ Int($0.accounts) }.reduce(0, +)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct FeaturedTag: Codable, Identifiable {
|
||||||
|
public let id: String
|
||||||
|
public let name: String
|
||||||
|
public let url: URL
|
||||||
|
public let statusesCount: String
|
||||||
|
public var statusesCountInt: Int {
|
||||||
|
Int(statusesCount) ?? 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@ public enum Accounts: Endpoint {
|
||||||
case accounts(id: String)
|
case accounts(id: String)
|
||||||
case favourites
|
case favourites
|
||||||
case followedTags
|
case followedTags
|
||||||
case featuredTags
|
case featuredTags(id: String)
|
||||||
case verifyCredentials
|
case verifyCredentials
|
||||||
case statuses(id: String, sinceId: String?)
|
case statuses(id: String, sinceId: String?, tag: String?)
|
||||||
case relationships(id: String)
|
case relationships(id: String)
|
||||||
case follow(id: String)
|
case follow(id: String)
|
||||||
case unfollow(id: String)
|
case unfollow(id: String)
|
||||||
|
@ -19,11 +19,11 @@ public enum Accounts: Endpoint {
|
||||||
return "favourites"
|
return "favourites"
|
||||||
case .followedTags:
|
case .followedTags:
|
||||||
return "followed_tags"
|
return "followed_tags"
|
||||||
case .featuredTags:
|
case .featuredTags(let id):
|
||||||
return "featured_tags"
|
return "accounts/\(id)/featured_tags"
|
||||||
case .verifyCredentials:
|
case .verifyCredentials:
|
||||||
return "accounts/verify_credentials"
|
return "accounts/verify_credentials"
|
||||||
case .statuses(let id, _):
|
case .statuses(let id, _, _):
|
||||||
return "accounts/\(id)/statuses"
|
return "accounts/\(id)/statuses"
|
||||||
case .relationships:
|
case .relationships:
|
||||||
return "accounts/relationships"
|
return "accounts/relationships"
|
||||||
|
@ -36,9 +36,15 @@ public enum Accounts: Endpoint {
|
||||||
|
|
||||||
public func queryItems() -> [URLQueryItem]? {
|
public func queryItems() -> [URLQueryItem]? {
|
||||||
switch self {
|
switch self {
|
||||||
case .statuses(_, let sinceId):
|
case .statuses(_, let sinceId, let tag):
|
||||||
guard let sinceId else { return nil }
|
var params: [URLQueryItem] = []
|
||||||
return [.init(name: "max_id", value: sinceId)]
|
if let tag {
|
||||||
|
params.append(.init(name: "tagged", value: tag))
|
||||||
|
}
|
||||||
|
if let sinceId {
|
||||||
|
params.append(.init(name: "max_id", value: sinceId))
|
||||||
|
}
|
||||||
|
return params
|
||||||
case let .relationships(id):
|
case let .relationships(id):
|
||||||
return [.init(name: "id", value: id)]
|
return [.init(name: "id", value: id)]
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -6,7 +6,7 @@ public enum RouteurDestinations: Hashable {
|
||||||
case accountDetail(id: String)
|
case accountDetail(id: String)
|
||||||
case accountDetailWithAccount(account: Account)
|
case accountDetailWithAccount(account: Account)
|
||||||
case statusDetail(id: String)
|
case statusDetail(id: String)
|
||||||
case hashTag(tag: String)
|
case hashTag(tag: String, account: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SheetDestinations: Identifiable {
|
public enum SheetDestinations: Identifiable {
|
||||||
|
@ -33,7 +33,7 @@ public class RouterPath: ObservableObject {
|
||||||
public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result {
|
public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result {
|
||||||
if url.pathComponents.contains(where: { $0 == "tags" }),
|
if url.pathComponents.contains(where: { $0 == "tags" }),
|
||||||
let tag = url.pathComponents.last {
|
let tag = url.pathComponents.last {
|
||||||
navigate(to: .hashTag(tag: tag))
|
navigate(to: .hashTag(tag: tag, account: nil))
|
||||||
return .handled
|
return .handled
|
||||||
} else if let mention = status.mentions.first(where: { $0.url == url }) {
|
} else if let mention = status.mentions.first(where: { $0.url == url }) {
|
||||||
navigate(to: .accountDetail(id: mention.id))
|
navigate(to: .accountDetail(id: mention.id))
|
||||||
|
|
|
@ -4,7 +4,7 @@ import Network
|
||||||
|
|
||||||
public enum TimelineFilter: Hashable, Equatable {
|
public enum TimelineFilter: Hashable, Equatable {
|
||||||
case pub, home
|
case pub, home
|
||||||
case hashtag(tag: String)
|
case hashtag(tag: String, accountId: String?)
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(title())
|
hasher.combine(title())
|
||||||
|
@ -20,17 +20,21 @@ public enum TimelineFilter: Hashable, Equatable {
|
||||||
return "Public"
|
return "Public"
|
||||||
case .home:
|
case .home:
|
||||||
return "Home"
|
return "Home"
|
||||||
case let .hashtag(tag):
|
case let .hashtag(tag, _):
|
||||||
return "#\(tag)"
|
return "#\(tag)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func endpoint(sinceId: String?) -> Timelines {
|
func endpoint(sinceId: String?) -> Endpoint {
|
||||||
switch self {
|
switch self {
|
||||||
case .pub: return .pub(sinceId: sinceId)
|
case .pub: return Timelines.pub(sinceId: sinceId)
|
||||||
case .home: return .home(sinceId: sinceId)
|
case .home: return Timelines.home(sinceId: sinceId)
|
||||||
case let .hashtag(tag):
|
case let .hashtag(tag, accountId):
|
||||||
return .hashtag(tag: tag, sinceId: sinceId)
|
if let accountId {
|
||||||
|
return Accounts.statuses(id: accountId, sinceId: nil, tag: tag)
|
||||||
|
} else {
|
||||||
|
return Timelines.hashtag(tag: tag, sinceId: sinceId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ class TimelineViewModel: ObservableObject, StatusesFetcher {
|
||||||
Task {
|
Task {
|
||||||
await fetchStatuses()
|
await fetchStatuses()
|
||||||
switch timeline {
|
switch timeline {
|
||||||
case let .hashtag(tag):
|
case let .hashtag(tag, _):
|
||||||
await fetchTag(id: tag)
|
await fetchTag(id: tag)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
|
Loading…
Reference in a new issue