mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-04-27 02:14:45 +00:00
Open HashTag
This commit is contained in:
parent
029b625acd
commit
3d7042832e
8 changed files with 85 additions and 36 deletions
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
36
Packages/Timeline/Sources/Timeline/TimelineFilter.swift
Normal file
36
Packages/Timeline/Sources/Timeline/TimelineFilter.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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: {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue