Skip to content

Commit

Permalink
Search: Refactor + Better search UX
Browse files Browse the repository at this point in the history
  • Loading branch information
Dimillian committed May 15, 2021
1 parent b3a78ec commit 6d64a16
Show file tree
Hide file tree
Showing 17 changed files with 280 additions and 100 deletions.
74 changes: 74 additions & 0 deletions Packages/UI/Sources/UI/Hovered.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Source: https://stackoverflow.com/questions/65841298/swiftui-onhover-doesnt-register-mouse-leaving-the-element-if-mouse-moves-too-fa

import SwiftUI

extension View {
public func whenHovered(_ mouseIsInside: @escaping (Bool) -> Void) -> some View {
modifier(MouseInsideModifier(mouseIsInside))
}
}

struct MouseInsideModifier: ViewModifier {
let mouseIsInside: (Bool) -> Void

init(_ mouseIsInside: @escaping (Bool) -> Void) {
self.mouseIsInside = mouseIsInside
}

func body(content: Content) -> some View {
content.background(
GeometryReader { proxy in
Representable(mouseIsInside: mouseIsInside,
frame: proxy.frame(in: .global))
}
)
}

private struct Representable: NSViewRepresentable {
let mouseIsInside: (Bool) -> Void
let frame: NSRect

func makeCoordinator() -> Coordinator {
let coordinator = Coordinator()
coordinator.mouseIsInside = mouseIsInside
return coordinator
}

class Coordinator: NSResponder {
var mouseIsInside: ((Bool) -> Void)?

override func mouseEntered(with event: NSEvent) {
mouseIsInside?(true)
}

override func mouseExited(with event: NSEvent) {
mouseIsInside?(false)
}
}

func makeNSView(context: Context) -> NSView {
let view = NSView(frame: frame)

let options: NSTrackingArea.Options = [
.mouseEnteredAndExited,
.inVisibleRect,
.activeInKeyWindow
]

let trackingArea = NSTrackingArea(rect: frame,
options: options,
owner: context.coordinator,
userInfo: nil)

view.addTrackingArea(trackingArea)

return view
}

func updateNSView(_ nsView: NSView, context: Context) {}

static func dismantleNSView(_ nsView: NSView, coordinator: Coordinator) {
nsView.trackingAreas.forEach { nsView.removeTrackingArea($0) }
}
}
}
12 changes: 8 additions & 4 deletions RedditOs.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
6970A0B324B77D1200B11031 /* PostVoteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6970A0B224B77D1200B11031 /* PostVoteView.swift */; };
6970A0B624B783FE00B11031 /* LinkPresentationRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6970A0B524B783FE00B11031 /* LinkPresentationRepresentable.swift */; };
6970A0B924B79AFD00B11031 /* PopoverSearchSubredditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6970A0B824B79AFD00B11031 /* PopoverSearchSubredditView.swift */; };
6970A0BB24B79F5900B11031 /* SearchViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6970A0BA24B79F5900B11031 /* SearchViewModel.swift */; };
6970A0BB24B79F5900B11031 /* SearchState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6970A0BA24B79F5900B11031 /* SearchState.swift */; };
6970A0BD24B82E1C00B11031 /* LoadingRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6970A0BC24B82E1C00B11031 /* LoadingRow.swift */; };
6970A0BF24B8343100B11031 /* PopoverSearchSubredditRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6970A0BE24B8343100B11031 /* PopoverSearchSubredditRow.swift */; };
6970A0C124B88BA200B11031 /* PostViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6970A0C024B88BA200B11031 /* PostViewModel.swift */; };
Expand All @@ -61,6 +61,7 @@
69EACF2524B73DF400303A16 /* SubredditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69EACF2424B73DF400303A16 /* SubredditViewModel.swift */; };
69F74E9324DAE65100E58BD8 /* AwardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F74E9224DAE65100E58BD8 /* AwardView.swift */; };
69F74E9624DB0B7300E58BD8 /* GlobalSearchPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69F74E9524DB0B7300E58BD8 /* GlobalSearchPopoverView.swift */; };
9F4812CC264FC8DB007A719D /* SearchMainContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F4812CB264FC8DB007A719D /* SearchMainContentView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand Down Expand Up @@ -93,7 +94,7 @@
6970A0B224B77D1200B11031 /* PostVoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostVoteView.swift; sourceTree = "<group>"; };
6970A0B524B783FE00B11031 /* LinkPresentationRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPresentationRepresentable.swift; sourceTree = "<group>"; };
6970A0B824B79AFD00B11031 /* PopoverSearchSubredditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverSearchSubredditView.swift; sourceTree = "<group>"; };
6970A0BA24B79F5900B11031 /* SearchViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchViewModel.swift; sourceTree = "<group>"; };
6970A0BA24B79F5900B11031 /* SearchState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchState.swift; sourceTree = "<group>"; };
6970A0BC24B82E1C00B11031 /* LoadingRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingRow.swift; sourceTree = "<group>"; };
6970A0BE24B8343100B11031 /* PopoverSearchSubredditRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopoverSearchSubredditRow.swift; sourceTree = "<group>"; };
6970A0C024B88BA200B11031 /* PostViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewModel.swift; sourceTree = "<group>"; };
Expand All @@ -120,6 +121,7 @@
69EACF2424B73DF400303A16 /* SubredditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubredditViewModel.swift; sourceTree = "<group>"; };
69F74E9224DAE65100E58BD8 /* AwardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AwardView.swift; sourceTree = "<group>"; };
69F74E9524DB0B7300E58BD8 /* GlobalSearchPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchPopoverView.swift; sourceTree = "<group>"; };
9F4812CB264FC8DB007A719D /* SearchMainContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchMainContentView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -247,7 +249,8 @@
6970A0B724B79AF400B11031 /* Search */ = {
isa = PBXGroup;
children = (
6970A0BA24B79F5900B11031 /* SearchViewModel.swift */,
9F4812CB264FC8DB007A719D /* SearchMainContentView.swift */,
6970A0BA24B79F5900B11031 /* SearchState.swift */,
693F85D324D0715000224ADB /* ToolbarSearchBar.swift */,
69F74E9424DB0B5600E58BD8 /* Global Search Popopver */,
693F85D224D06AA700224ADB /* Subreddit Search Popover */,
Expand Down Expand Up @@ -470,6 +473,7 @@
692F237624CB3A7B006C9D40 /* SavedPostsListView.swift in Sources */,
6924D53E24CD94B0005487CA /* UserViewModel.swift in Sources */,
69222AA724CD6D6C009F31B4 /* SubmittedPostsListView.swift in Sources */,
9F4812CC264FC8DB007A719D /* SearchMainContentView.swift in Sources */,
697E324724E3ED620006F00F /* CommentViewModel.swift in Sources */,
692566DA24B8A3830014E0D4 /* PostDetailHeader.swift in Sources */,
69CCB3EA24E2BEAC003FAAD7 /* SubredditAboutPopoverView.swift in Sources */,
Expand All @@ -482,7 +486,7 @@
69222AA324CC0291009F31B4 /* PostNoSelectionPlaceholder.swift in Sources */,
6924D53C24CD949D005487CA /* UserPopoverView.swift in Sources */,
69F74E9324DAE65100E58BD8 /* AwardView.swift in Sources */,
6970A0BB24B79F5900B11031 /* SearchViewModel.swift in Sources */,
6970A0BB24B79F5900B11031 /* SearchState.swift in Sources */,
6924D54524CDD7D7005487CA /* UserSheetOverviewView.swift in Sources */,
6906880D24B743900067D973 /* SubredditPostRow.swift in Sources */,
69DB093824DFCBC60026811F /* SettingsKey.swift in Sources */,
Expand Down
13 changes: 6 additions & 7 deletions RedditOs/Environements/Route.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ enum Route: Identifiable, Hashable {
hasher.combine(id)
}

case user(user: User, isSheet: Bool)
case subreddit(subreddit: String, isSheet: Bool)
case user(user: User)
case subreddit(subreddit: String)
case defaultChannel(chanel: UIState.DefaultChannels)
case none

var id: String {
switch self {
case .user:
return "user"
case let .subreddit(subreddit, _):
case let .subreddit(subreddit):
return subreddit
case .none:
return "none"
Expand All @@ -40,12 +40,11 @@ enum Route: Identifiable, Hashable {
@ViewBuilder
func makeView() -> some View {
switch self {
case let .user(user, _):
case let .user(user):
UserSheetView(user: user)
case let .subreddit(subreddit, isSheet):
SubredditPostsListView(name: subreddit, isSheet: isSheet)
case let .subreddit(subreddit):
SubredditPostsListView(name: subreddit)
.equatable()
.environmentObject(UIState.shared)
case let .defaultChannel(chanel):
SubredditPostsListView(name: chanel.rawValue)
.equatable()
Expand Down
33 changes: 26 additions & 7 deletions RedditOs/Environements/UIState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,42 @@ class UIState: ObservableObject {
}
}

enum Constants {
static let searchTag = "search"
}

private init() {

isSearchActive = .constant(false)
isSearchActive = .init(get: {
self.sidebarSelection == Constants.searchTag
}, set: { _ in })
}

@Published var displayToolbarSearchBar = true

@Published var selectedSubreddit: SubredditViewModel?
@Published var selectedPost: PostViewModel?

@Published var presentedSheetRoute: Route?
@Published var presentedNavigationRoute: Route? {
@Published var searchRoute: Route? {
didSet {
DispatchQueue.main.async {
if let route = self.presentedNavigationRoute {
self.sidebarSelection = route.id
}
if searchRoute != nil {
sidebarSelection = Constants.searchTag
displayToolbarSearchBar = false
}
}
}

@Published var sidebarSelection: String? = DefaultChannels.hot.rawValue
var isSearchActive: Binding<Bool>

@Published var sidebarSelection: String? = DefaultChannels.hot.rawValue {
didSet {
if sidebarSelection != Constants.searchTag {
searchRoute = nil
displayToolbarSearchBar = true
} else {
displayToolbarSearchBar = false
}
}
}
}
7 changes: 6 additions & 1 deletion RedditOs/Features/Post/PostDetailToolbar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@
import SwiftUI

struct PostDetailToolbar: ToolbarContent {
@ObservedObject var uiState: UIState
let shareURL: URL?
@State var searchViewModel = SearchState()

var body: some ToolbarContent {
ToolbarItemGroup {
SharingView(url: shareURL)
Spacer()
ToolbarSearchBar()
if uiState.displayToolbarSearchBar {
ToolbarSearchBar(isPopoverEnabled: true)
.frame(width: 300)
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion RedditOs/Features/Post/PostDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ struct PostDetailView: View, Equatable {
uiState.selectedPost = nil
})
.toolbar {
PostDetailToolbar(shareURL: viewModel.post.redditURL)
PostDetailToolbar(uiState: uiState, shareURL: viewModel.post.redditURL)
}
.frame(minWidth: 500,
maxWidth: .infinity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,24 @@ import Backend
struct GlobalSearchPopoverView: View {
@EnvironmentObject private var uiState: UIState
@EnvironmentObject private var currentUser: CurrentUserStore

@ObservedObject var viewModel: SearchViewModel
@EnvironmentObject private var searchState: SearchState

var body: some View {
ScrollView {
VStack(alignment: .leading) {
makeTitle("Quick access")
makeQuickAccess()

Divider()

makeTitle("My subscriptions")
makeMySubscriptionsSearch()

Divider()

makeTitle("Subreddit search")
makeSubredditSearch()

}.padding()
}.frame(width: 300, height: 500)
Section(header: makeTitle("Quick access")) {
makeQuickAccess()
}

Divider()

Section(header: makeTitle("My subscriptions")) {
makeMySubscriptionsSearch()
}

Divider()

Section(header: makeTitle("Subreddit search")) {
makeSubredditSearch()
}
}

private func makeTitle(_ title: String) -> some View {
Expand All @@ -43,20 +40,20 @@ struct GlobalSearchPopoverView: View {
private func makeQuickAccess() -> some View {
Group {
GlobalSearchSubRow(icon: nil,
name: "Go to r/\(viewModel.searchText)")
name: "Go to r/\(searchState.searchText)")
.onTapGesture {
uiState.presentedNavigationRoute = .subreddit(subreddit: viewModel.searchText, isSheet: false)
uiState.searchRoute = .subreddit(subreddit: searchState.searchText)
}
GlobalSearchSubRow(icon: nil,
name: "Go to u/\(viewModel.searchText)")
name: "Go to u/\(searchState.searchText)")
}.padding(4)
}

private func makeMySubscriptionsSearch() -> some View {
Group {
if let subs = viewModel.filteredSubscriptions {
if let subs = searchState.filteredSubscriptions {
if subs.isEmpty {
Label("No matching subscriptions for \(viewModel.searchText)", systemImage: "magnifyingglass")
Label("No matching subscriptions for \(searchState.searchText)", systemImage: "magnifyingglass")
} else {
ForEach(subs) { sub in
makeSubRow(icon: sub.iconImg, name: sub.displayName)
Expand All @@ -68,15 +65,15 @@ struct GlobalSearchPopoverView: View {

private func makeSubredditSearch() -> some View {
Group {
if let results = viewModel.results {
if let results = searchState.results {
if results.isEmpty {
Label("No matching search for \(viewModel.searchText)", systemImage: "magnifyingglass")
Label("No matching search for \(searchState.searchText)", systemImage: "magnifyingglass")
} else {
ForEach(results) { sub in
makeSubRow(icon: sub.iconImg, name: sub.name)
}
}
} else if viewModel.isLoading {
} else if searchState.isLoading {
LoadingRow(text: nil)
}
}.padding(4)
Expand All @@ -85,7 +82,7 @@ struct GlobalSearchPopoverView: View {
private func makeSubRow(icon: String?, name: String) -> some View {
GlobalSearchSubRow(icon: icon, name: name)
.onTapGesture {
uiState.presentedNavigationRoute = .subreddit(subreddit: name, isSheet: false)
uiState.searchRoute = .subreddit(subreddit: name)
}
}
}
Loading

0 comments on commit 6d64a16

Please sign in to comment.