Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions Modules/Sources/APIClient/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ public struct APIClient: Sendable {
public var loadQMSUser: @Sendable (_ id: Int) async throws -> QMSUser
public var loadQMSChat: @Sendable (_ id: Int) async throws -> QMSChat
public var sendQMSMessage: @Sendable (_ chatId: Int, _ message: String) async throws -> Void

// Search
public var startSearch: @Sendable (_ request: SearchRequest) async throws -> SearchResponse
public var members: @Sendable (_ request: MembersRequest) async throws -> MembersResponse

// STREAMS
public var connectionState: @Sendable () -> AsyncStream<ConnectionState> = { .finished }
Expand Down Expand Up @@ -462,6 +466,33 @@ extension APIClient: DependencyKey {
let _ = try await api.send(QMSCommand.Message.send(data: request))
// Returns chatId + new messageId
},

// MARK: - Search

startSearch: { request in
let command = SearchCommand.content(
on: request.on.toPDAPISearchOn(),
authorId: request.authorId,
text: request.text,
sort: request.sort.toPDAPISearchSort(),
offset: request.offset
)
let response = try await api.send(command)
let status = Int(response.getResponseStatus())

print("Status \(String(describing: status))")
return try await parser.parseSearch(response: response)
},
members: { request in
let command = SearchCommand.members(
term: request.term,
offset: request.offset,
number: request.number
)
let response = try await api.send(command)
let status = Int(response.getResponseStatus())
return try await parser.parseMembers(response: response)
},

// MARK: - Streams

Expand Down Expand Up @@ -604,6 +635,12 @@ extension APIClient: DependencyKey {
},
sendQMSMessage: { _, _ in

},
startSearch: { _ in
return SearchResponse(metadata: [], publications: [])
},
members: { _ in
return MembersResponse(metadata: [], members: [])
},
connectionState: {
return .finished
Expand Down
28 changes: 28 additions & 0 deletions Modules/Sources/APIClient/Requests/MembersRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// MembersRequest.swift
// ForPDA
//
// Created by Рустам Ойтов on 29.10.2025.
//

import PDAPI
import Models

public struct MembersRequest: Sendable, Equatable {

public let term: String
public let offset: Int
public let number: Int

public init(
term: String,
offset: Int,
number: Int
) {
self.term = term
self.offset = offset
self.number = number
}
}


89 changes: 89 additions & 0 deletions Modules/Sources/APIClient/Requests/SearchRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//
// SearchRequest.swift
// ForPDA
//
// Created by Рустам Ойтов on 18.08.2025.
//

import PDAPI
import Models

public struct SearchRequest: Sendable, Equatable {

public let on: SearchOn
public let authorId: Int?
public let text: String
public let sort: SearchSort
public let offset: Int

public enum SearchOn: Sendable, Equatable {
case site
case forum(id: Int?, sIn: ForumSearchIn, asTopics: Bool = false)
case topic(id: Int)
}

public enum ForumSearchIn : Sendable {
case all
case posts
case titles
}

public enum SearchSort: Sendable {
case dateDescSort
case dateAscSort
case relevance
}

public init(
on: SearchOn,
authorId: Int?,
text: String,
sort: SearchSort,
offset: Int
) {
self.on = on
self.authorId = authorId
self.text = text
self.sort = sort
self.offset = offset
}
}

extension SearchRequest.SearchOn {
func toPDAPISearchOn() -> SearchCommand.SearchOn {
switch self {
case .site:
return .site
case let .forum(id: id, sIn: sIn, asTopics: asTopics):
return .forum(id: id, sIn: sIn.toPDAPIForumSearchIn(), asTopics: asTopics)
case let .topic(id: id):
return .topic(id: id)
}
}
}

extension SearchRequest.ForumSearchIn {
func toPDAPIForumSearchIn() -> SearchCommand.ForumSearchIn {
switch self {
case .all:
return .all
case .posts:
return .posts
case .titles:
return .titles
}
}
}

extension SearchRequest.SearchSort {
func toPDAPISearchSort() -> SearchCommand.SearchSort {
switch self {
case .dateAscSort:
return .dateAscSort
case .dateDescSort:
return .dateDescSort
case .relevance:
return .ascSort
}
}
}
21 changes: 18 additions & 3 deletions Modules/Sources/AppFeature/AppFeature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import ProfileFeature
import QMSListFeature
import QMSFeature
import ReputationFeature
import SearchFeature
import SettingsFeature
import NotificationsFeature
import DeveloperFeature
Expand Down Expand Up @@ -56,6 +57,7 @@ public struct AppFeature: Reducer, Sendable {
public var favoritesTab: StackTab.State
public var forumTab: StackTab.State
public var profileFlow: ProfileFlow.State
public var searchTab: StackTab.State

@Presents public var auth: AuthFeature.State?
@Presents public var logStore: LogStoreFeature.State?
Expand Down Expand Up @@ -86,6 +88,7 @@ public struct AppFeature: Reducer, Sendable {
articlesTab: StackTab.State = StackTab.State(root: .articles(.articlesList(ArticlesListFeature.State()))),
favoritesTab: StackTab.State = StackTab.State(root: .favorites(FavoritesRootFeature.State())),
forumTab: StackTab.State = StackTab.State(root: .forum(.forumList(ForumsListFeature.State()))),
searchTab: StackTab.State = StackTab.State(root: .search(SearchFeature.State())),
auth: AuthFeature.State? = nil,
alert: AlertState<Never>? = nil,
selectedTab: AppTab = .articles,
Expand All @@ -98,7 +101,8 @@ public struct AppFeature: Reducer, Sendable {
self.articlesTab = articlesTab
self.favoritesTab = favoritesTab
self.forumTab = forumTab

self.searchTab = searchTab

if let session = _userSession.wrappedValue {
self.profileFlow = .loggedIn(StackTab.State(root: .profile(.profile(ProfileFeature.State(userId: session.userId)))))
} else {
Expand Down Expand Up @@ -130,6 +134,7 @@ public struct AppFeature: Reducer, Sendable {
case favoritesTab(StackTab.Action)
case forumTab(StackTab.Action)
case profileFlow(ProfileFlow.Action)
case searchTab(StackTab.Action)

case auth(PresentationAction<AuthFeature.Action>)
case logStore(PresentationAction<LogStoreFeature.Action>)
Expand Down Expand Up @@ -198,6 +203,10 @@ public struct AppFeature: Reducer, Sendable {
ProfileFlow.body
}

Scope(state: \.searchTab, action: \.searchTab) {
StackTab()
}

// Authorization actions interceptor
Reduce<State, Action> { state, action in
switch action {
Expand Down Expand Up @@ -493,6 +502,7 @@ public struct AppFeature: Reducer, Sendable {
case let .articlesTab(.delegate(.showTabBar(show))),
let .favoritesTab(.delegate(.showTabBar(show))),
let .forumTab(.delegate(.showTabBar(show))),
let .searchTab(.delegate(.showTabBar(show))),
let .profileFlow(.loggedIn(.delegate(.showTabBar(show)))),
let .profileFlow(.loggedOut(.delegate(.showTabBar(show)))):
state.showTabBar = show
Expand All @@ -501,13 +511,14 @@ public struct AppFeature: Reducer, Sendable {
case let .articlesTab(.delegate(.switchTab(to: tab))),
let .favoritesTab(.delegate(.switchTab(to: tab))),
let .forumTab(.delegate(.switchTab(to: tab))),
let .searchTab(.delegate(.switchTab(to: tab))),
let .profileFlow(.loggedIn(.delegate(.switchTab(to: tab)))),
let .profileFlow(.loggedOut(.delegate(.switchTab(to: tab)))):
state.previousTab = state.selectedTab
state.selectedTab = tab
return .none

case .articlesTab, .favoritesTab, .forumTab, .profileFlow:
case .articlesTab, .favoritesTab, .forumTab, .profileFlow, .searchTab:
return .none
}
}
Expand Down Expand Up @@ -549,6 +560,9 @@ public struct AppFeature: Reducer, Sendable {

case .forum:
state.forumTab.path.removeAll()

case .search:
state.forumTab.path.removeAll()

case .profile:
switch state.profileFlow {
Expand Down Expand Up @@ -577,7 +591,7 @@ public struct AppFeature: Reducer, Sendable {
private func removeNotifications(_ state: inout State) -> Effect<Action> {
return .run { [tab = state.selectedTab] _ in
switch tab {
case .articles, .forum, .profile:
case .articles, .forum, .profile, .search:
break
case .favorites:
await notificationsClient.removeNotifications(categories: [.forum, .topic])
Expand Down Expand Up @@ -619,6 +633,7 @@ public struct AppFeature: Reducer, Sendable {
case .articles: state.articlesTab.path.append(element)
case .favorites: state.favoritesTab.path.append(element)
case .forum: state.forumTab.path.append(element)
case .search: state.searchTab.path.append(element)
case .profile:
switch state.profileFlow {
case var .loggedIn(flow):
Expand Down
15 changes: 15 additions & 0 deletions Modules/Sources/AppFeature/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import ProfileFeature
import QMSFeature
import QMSListFeature
import SettingsFeature
import SearchFeature
import SFSafeSymbols
import SharedUI
import SwiftUI
Expand Down Expand Up @@ -135,6 +136,15 @@ struct LiquidTabView: View {
) {
ProfileTab(store: store.scope(state: \.profileFlow, action: \.profileFlow))
}

Tab(
AppTab.search.title,
systemSymbol: AppTab.search.iconSymbol,
value: .search
// role: .search
) {
StackTabView(store: store.scope(state: \.searchTab, action: \.searchTab))
}
}
.tabBarMinimizeBehavior(store.appSettings.hideTabBarOnScroll ? .onScrollDown : .never)
.if(store.appSettings.experimentalFloatingNavigation) { content in
Expand All @@ -160,6 +170,8 @@ struct LiquidTabView: View {
case let .loggedIn(store), let .loggedOut(store):
Page(for: store)
}
case .search:
Page(for: store.scope(state: \.searchTab, action: \.searchTab))
}
}

Expand Down Expand Up @@ -223,6 +235,9 @@ struct OldTabView: View {

ProfileTab(store: store.scope(state: \.profileFlow, action: \.profileFlow))
.tag(AppTab.profile)

StackTabView(store: store.scope(state: \.searchTab, action: \.searchTab))
.tag(AppTab.search)
}

Group {
Expand Down
11 changes: 11 additions & 0 deletions Modules/Sources/AppFeature/Navigation/Path.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import ProfileFeature
import QMSFeature
import QMSListFeature
import ReputationFeature
import SearchFeature
import SettingsFeature
import TopicFeature
import AuthFeature
Expand All @@ -30,6 +31,7 @@ public enum Path {
case favorites(FavoritesRootFeature)
case forum(Forum.Body = Forum.body)
case profile(Profile.Body = Profile.body)
case search(SearchFeature)
case settings(Settings.Body = Settings.body)
case qms(QMS.Body = QMS.body)
case auth(AuthFeature)
Expand All @@ -55,6 +57,11 @@ public enum Path {
case topic(TopicFeature)
}

@Reducer(state: .equatable)
public enum Search {
case search(SearchFeature)
}

@Reducer(state: .equatable)
public enum Settings {
case settings(SettingsFeature)
Expand Down Expand Up @@ -87,6 +94,10 @@ extension Path {
case let .forum(path):
ForumViews(path)

case let .search(store):
SearchScreen(store: store)
.tracking(for: SearchScreen.self)

case let .settings(path):
SettingsViews(path)

Expand Down
14 changes: 14 additions & 0 deletions Modules/Sources/AppFeature/Navigation/StackTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import ForumFeature
import TopicFeature
import FavoritesRootFeature
import ProfileFeature
import SearchFeature
import AnnouncementFeature
import HistoryFeature
import QMSListFeature
Expand Down Expand Up @@ -116,6 +117,9 @@ public struct StackTab: Reducer, Sendable {
case let .profile(action):
return handleProfilePathNavigation(action: action, state: &state)

case let .search(action):
return handleSearchPathNavigation(action: action, state: &state)

case let .settings(action):
return handleSettingsPathNavigation(action: action, state: &state)

Expand Down Expand Up @@ -252,6 +256,16 @@ public struct StackTab: Reducer, Sendable {
return .none
}

// MARK: - Search

private func handleSearchPathNavigation(action: SearchFeature.Action, state: inout State) -> Effect<Action> {
switch action {
default:
break
}
return .none
}

// MARK: - Settings

private func handleSettingsPathNavigation(action: Path.Settings.Action, state: inout State) -> Effect<Action> {
Expand Down
Loading