Open HashTag

This commit is contained in:
Thomas Ricouard 2022-12-20 15:37:51 +01:00
parent 029b625acd
commit 3d7042832e
8 changed files with 85 additions and 36 deletions

View file

@ -15,6 +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):
TimelineView(timeline: .hashtag(tag: tag))
} }
} }
} }

View file

@ -16,10 +16,12 @@ struct IceCubesApp: App {
.tabItem { .tabItem {
Label("Home", systemImage: "globe") Label("Home", systemImage: "globe")
} }
NotificationsTab() if appAccountsManager.currentClient.isAuth {
.tabItem { NotificationsTab()
Label("Notifications", systemImage: "bell") .tabItem {
} Label("Notifications", systemImage: "bell")
}
}
SettingsTabs() SettingsTabs()
.tabItem { .tabItem {
Label("Settings", systemImage: "gear") Label("Settings", systemImage: "gear")

View file

@ -2,7 +2,13 @@ import Foundation
import SwiftUI import SwiftUI
import Models import Models
public class Client: ObservableObject { public class Client: ObservableObject, Equatable {
public static func == (lhs: Client, rhs: Client) -> Bool {
lhs.isAuth == rhs.isAuth &&
lhs.server == rhs.server &&
lhs.oauthToken?.accessToken == rhs.oauthToken?.accessToken
}
public enum Version: String { public enum Version: String {
case v1 case v1
} }

View file

@ -3,6 +3,7 @@ import Foundation
public enum Timelines: Endpoint { public enum Timelines: Endpoint {
case pub(sinceId: String?) case pub(sinceId: String?)
case home(sinceId: String?) case home(sinceId: String?)
case hashtag(tag: String, sinceId: String?)
public func path() -> String { public func path() -> String {
switch self { switch self {
@ -10,6 +11,8 @@ public enum Timelines: Endpoint {
return "timelines/public" return "timelines/public"
case .home: case .home:
return "timelines/home" return "timelines/home"
case let .hashtag(tag, _):
return "timelines/tag/\(tag)"
} }
} }
@ -21,6 +24,9 @@ public enum Timelines: Endpoint {
case .home(let sinceId): case .home(let sinceId):
guard let sinceId else { return nil } guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)] return [.init(name: "max_id", value: sinceId)]
case let .hashtag(_, sinceId):
guard let sinceId else { return nil }
return [.init(name: "max_id", value: sinceId)]
} }
} }
} }

View file

@ -6,6 +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)
} }
public enum SheetDestinations: Identifiable { public enum SheetDestinations: Identifiable {
@ -30,7 +31,11 @@ public class RouterPath: ObservableObject {
} }
public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result { public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result {
if let mention = status.mentions.first(where: { $0.url == url }) { if url.pathComponents.contains(where: { $0 == "tags" }),
let tag = url.pathComponents.last {
navigate(to: .hashTag(tag: tag))
return .handled
} else if let mention = status.mentions.first(where: { $0.url == url }) {
navigate(to: .accountDetail(id: mention.id)) navigate(to: .accountDetail(id: mention.id))
return .handled return .handled
} }

View file

@ -0,0 +1,36 @@
import Foundation
import Models
import Network
public enum TimelineFilter: Hashable, Equatable {
case pub, home
case hashtag(tag: String)
public func hash(into hasher: inout Hasher) {
hasher.combine(title())
}
static func availableTimeline() -> [TimelineFilter] {
return [.pub, .home]
}
func title() -> String {
switch self {
case .pub:
return "Public"
case .home:
return "Home"
case let .hashtag(tag):
return tag
}
}
func endpoint(sinceId: String?) -> Timelines {
switch self {
case .pub: return .pub(sinceId: sinceId)
case .home: return .home(sinceId: sinceId)
case let .hashtag(tag):
return .hashtag(tag: tag, sinceId: sinceId)
}
}
}

View file

@ -8,9 +8,12 @@ import DesignSystem
public struct TimelineView: View { public struct TimelineView: View {
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@StateObject private var viewModel = TimelineViewModel() @StateObject private var viewModel = TimelineViewModel()
@State private var didAppear = false
public init() {} private let filter: TimelineFilter?
public init(timeline: TimelineFilter? = nil) {
self.filter = timeline
}
public var body: some View { public var body: some View {
ScrollView { ScrollView {
@ -19,33 +22,38 @@ public struct TimelineView: View {
} }
.padding(.top, DS.Constants.layoutPadding) .padding(.top, DS.Constants.layoutPadding)
} }
.navigationTitle(viewModel.timeline.rawValue) .navigationTitle(filter?.title() ?? viewModel.timeline.title())
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.toolbar { .toolbar {
ToolbarItem(placement: .navigationBarTrailing) { if filter == nil {
timelineFilterButton ToolbarItem(placement: .navigationBarTrailing) {
timelineFilterButton
}
} }
} }
.task { .onAppear {
viewModel.client = client viewModel.client = client
if !didAppear { if let filter {
await viewModel.fetchStatuses() viewModel.timeline = filter
didAppear = true } else {
viewModel.timeline = client.isAuth ? .home : .pub
} }
} }
.refreshable { .refreshable {
await viewModel.fetchStatuses() Task {
await viewModel.fetchStatuses()
}
} }
} }
private var timelineFilterButton: some View { private var timelineFilterButton: some View {
Menu { Menu {
ForEach(TimelineViewModel.TimelineFilter.allCases, id: \.self) { filter in ForEach(TimelineFilter.availableTimeline(), id: \.self) { filter in
Button { Button {
viewModel.timeline = filter viewModel.timeline = filter
} label: { } label: {
Text(filter.rawValue) Text(filter.title())
} }
} }
} label: { } label: {

View file

@ -5,30 +5,14 @@ import Status
@MainActor @MainActor
class TimelineViewModel: ObservableObject, StatusesFetcher { class TimelineViewModel: ObservableObject, StatusesFetcher {
enum TimelineFilter: String, CaseIterable { var client: Client?
case pub = "Public"
case home = "Home"
func endpoint(sinceId: String?) -> Timelines {
switch self {
case .pub: return .pub(sinceId: sinceId)
case .home: return .home(sinceId: sinceId)
}
}
}
var client: Client? {
didSet {
timeline = client?.isAuth == true ? .home : .pub
}
}
private var statuses: [Status] = [] private var statuses: [Status] = []
@Published var statusesState: StatusesState = .loading @Published var statusesState: StatusesState = .loading
@Published var timeline: TimelineFilter = .pub { @Published var timeline: TimelineFilter = .pub {
didSet { didSet {
if oldValue != timeline { if oldValue != timeline || statuses.isEmpty {
Task { Task {
await fetchStatuses() await fetchStatuses()
} }