Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ux: Local Search Redesign #2211

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
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
8 changes: 8 additions & 0 deletions damus.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@
50B5685329F97CB400A23243 /* CredentialHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B5685229F97CB400A23243 /* CredentialHandler.swift */; };
50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */; };
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; };
5C02F2C22BA3C767002BF29D /* SearchContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C02F2C12BA3C767002BF29D /* SearchContentView.swift */; };
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
Expand All @@ -405,6 +406,7 @@
5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; };
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
5C8E588B2BACB252003D0A45 /* PostingTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C8E588A2BACB252003D0A45 /* PostingTimelineView.swift */; };
5CC868DD2AA29B3200FB22BA /* NeutralButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */; };
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */; };
5CF2DCCE2AABE1A500984B8D /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; };
Expand Down Expand Up @@ -1319,6 +1321,7 @@
50B5685229F97CB400A23243 /* CredentialHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialHandler.swift; sourceTree = "<group>"; };
50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVPlayer+Additions.swift"; sourceTree = "<group>"; };
50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = "<group>"; };
5C02F2C12BA3C767002BF29D /* SearchContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchContentView.swift; sourceTree = "<group>"; };
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
Expand All @@ -1328,6 +1331,7 @@
5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; };
5C7389B62B9E692E00781E0A /* MutinyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyButton.swift; sourceTree = "<group>"; };
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyGradient.swift; sourceTree = "<group>"; };
5C8E588A2BACB252003D0A45 /* PostingTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostingTimelineView.swift; sourceTree = "<group>"; };
5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeutralButtonStyle.swift; sourceTree = "<group>"; };
5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPicView.swift; sourceTree = "<group>"; };
5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLightGradient.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2400,6 +2404,7 @@
4CCEB7AD29B53D260078AA28 /* SearchingEventView.swift */,
4CCEB7AF29B5415A0078AA28 /* SearchingProfileView.swift */,
4C9D6D1A2B1D35D7004E5CD9 /* PullDownSearch.swift */,
5C02F2C12BA3C767002BF29D /* SearchContentView.swift */,
);
path = Search;
sourceTree = "<group>";
Expand All @@ -2408,6 +2413,7 @@
isa = PBXGroup;
children = (
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */,
5C8E588A2BACB252003D0A45 /* PostingTimelineView.swift */,
);
path = Timeline;
sourceTree = "<group>";
Expand Down Expand Up @@ -3120,6 +3126,7 @@
4CB883B6297730E400DC99E7 /* LNUrls.swift in Sources */,
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */,
BA4AB0AE2A63B9270070A32A /* AddEmojiView.swift in Sources */,
5C8E588B2BACB252003D0A45 /* PostingTimelineView.swift in Sources */,
4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */,
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */,
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */,
Expand Down Expand Up @@ -3365,6 +3372,7 @@
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
4C1253682A76D2470004F4B8 /* MuteNotify.swift in Sources */,
5C02F2C22BA3C767002BF29D /* SearchContentView.swift in Sources */,
4CDA128C29EB19C40006FA5A /* LocalNotification.swift in Sources */,
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
4C7D09762A0AF19E00943473 /* FillAndStroke.swift in Sources */,
Expand Down
55 changes: 1 addition & 54 deletions damus/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,59 +90,6 @@ struct ContentView: View {
// connect retry timer
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

var mystery: some View {
Text("Are you lost?", comment: "Text asking the user if they are lost in the app.")
.id("what")
}

func content_filter(_ fstate: FilterState) -> ((NostrEvent) -> Bool) {
var filters = ContentFilters.defaults(damus_state: damus_state!)
filters.append(fstate.filter)
return ContentFilters(filters: filters).filter
}

var PostingTimelineView: some View {
VStack {
ZStack {
TabView(selection: $filter_state) {
// This is needed or else there is a bug when switching from the 3rd or 2nd tab to first. no idea why.
mystery

contentTimelineView(filter: content_filter(.posts))
.tag(FilterState.posts)
.id(FilterState.posts)
contentTimelineView(filter: content_filter(.posts_and_replies))
.tag(FilterState.posts_and_replies)
.id(FilterState.posts_and_replies)
}
.tabViewStyle(.page(indexDisplayMode: .never))

if privkey != nil {
PostButtonContainer(is_left_handed: damus_state?.settings.left_handed ?? false) {
self.active_sheet = .post(.posting(.none))
}
}
}
}
.safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) {
CustomPicker(selection: $filter_state, content: {
Text("Notes", comment: "Label for filter for seeing only notes (instead of notes and replies).").tag(FilterState.posts)
Text("Notes & Replies", comment: "Label for filter for seeing notes and replies (instead of only notes).").tag(FilterState.posts_and_replies)
})
Divider()
.frame(height: 1)
}
.background(colorScheme == .dark ? Color.black : Color.white)
}
}

func contentTimelineView(filter: (@escaping (NostrEvent) -> Bool)) -> some View {
TimelineView(events: home.events, loading: .constant(false), damus: damus_state, show_friend_icon: false, filter: filter) {
PullDownSearchView(state: damus_state, on_cancel: {})
}
}

func navIsAtRoot() -> Bool {
return navigationCoordinator.isAtRoot()
}
Expand Down Expand Up @@ -170,7 +117,7 @@ struct ContentView: View {
}

case .home:
PostingTimelineView
PostingTimelineView(damus_state: damus_state!, home: home, active_sheet: $active_sheet)

case .notifications:
NotificationsView(state: damus, notifications: home.notifications)
Expand Down
94 changes: 34 additions & 60 deletions damus/Views/Search/PullDownSearch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import Foundation
import SwiftUI

struct PullDownSearchView: View {
@State private var search_text = ""
@State private var results: [NostrEvent] = []
@State private var is_active: Bool = false
let debouncer: Debouncer = Debouncer(interval: 0.25)
let state: DamusState
let on_cancel: () -> Void

@Binding var search_text: String
@Binding var results: [NostrEvent]
@FocusState private var isFocused: Bool

func do_search(query: String) {
let limit = 16
Expand Down Expand Up @@ -51,76 +51,50 @@ struct PullDownSearchView: View {
results = res_
}
}

var body: some View {
VStack(alignment: .leading) {
HStack {
TextField(NSLocalizedString("Search", comment: "Title of the text field for searching."), text: $search_text)
.textFieldStyle(RoundedBorderTextFieldStyle())

var SearchInput: some View {
HStack {
HStack{
Image("search")
.foregroundColor(.gray)
TextField(NSLocalizedString("Search", comment: "Placeholder text to prompt entry of search query."), text: $search_text)
.autocorrectionDisabled(true)
.textInputAutocapitalization(.never)
.focused($isFocused)
.onChange(of: search_text) { query in
debouncer.debounce {
Task.detached {
do_search(query: query)
}
}
}
.onTapGesture {
is_active = true
}

if is_active {
Button(action: {
search_text = ""
results = []
end_editing()
on_cancel()
}, label: {
Text("Cancel", comment: "Button to cancel out of search text entry mode.")
})
}
}
.padding()

if results.count > 0 {
HStack {
Image("search")
Text(NSLocalizedString("Top hits", comment: "A label indicating that the notes being displayed below it are all top note search results"))
Spacer()
}
.padding(.horizontal)
.foregroundColor(.secondary)

ForEach(results, id: \.self) { note in
EventView(damus: state, event: note)
.onTapGesture {
let event = note.get_inner_event(cache: state.events) ?? note
let thread = ThreadModel(event: event, damus_state: state)
state.nav.push(route: Route.Thread(thread: thread))
}
}

HStack {
Image("notes.fill")
Text(NSLocalizedString("Notes", comment: "A label indicating that the notes being displayed below it are from a timeline, not search results"))
Spacer()
}
.foregroundColor(.secondary)
.padding(.horizontal)
} else if results.count == 0 && !search_text.isEmpty {
HStack {
Image("search")
Text(NSLocalizedString("No results", comment: "A label indicating that note search resulted in no results"))
Spacer()
}
.padding(.horizontal)
.foregroundColor(.secondary)
.padding(7)
.background(.secondary.opacity(0.2))
.cornerRadius(15)

if(!search_text.isEmpty || isFocused) {
Button(action: {
search_text = ""
isFocused = false
results = []
}, label: {
Text("Cancel", comment: "Button to cancel out of search text entry mode.")
})
}
}
.padding([.horizontal, .top], 10)
}

var body: some View {
VStack(alignment: .leading) {
SearchInput
}
}
}

struct PullDownSearchView_Previews: PreviewProvider {
static var previews: some View {
PullDownSearchView(state: test_damus_state, on_cancel: {})
PullDownSearchView(state: test_damus_state, search_text: .constant(""), results: .constant([]))
}
}
74 changes: 74 additions & 0 deletions damus/Views/Search/SearchContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// SearchContentView.swift
// damus
//
// Created by eric on 3/14/24.
//

import SwiftUI

struct SearchContentView: View {
let state: DamusState
@Binding var search_text: String
@Binding var results: [NostrEvent]

var event_options: EventViewOptions {
if self.state.settings.truncate_timeline_text {
return [.wide, .truncate_content]
}
return [.wide]
}

var body: some View {
ScrollView {
if results.count > 0 {
HStack {
Image("search")
Text(NSLocalizedString("Top hits", comment: "A label indicating that the notes being displayed below it are all top note search results"))
Spacer()
}
.foregroundColor(.secondary)
.padding()

ForEach(results, id: \.self) { note in
EventView(damus: state, event: note, options: event_options)
.onTapGesture {
let event = note.get_inner_event(cache: state.events) ?? note
let thread = ThreadModel(event: event, damus_state: state)
state.nav.push(route: Route.Thread(thread: thread))
}
.padding(.top, 7)

ThiccDivider()
.padding([.top], 7)
}

} else if results.count == 0 && !search_text.isEmpty {
VStack(alignment: .center) {
HStack {
Image("search")
Text(NSLocalizedString("No results", comment: "A label indicating that note search resulted in no results"))
}
}
.padding(.vertical)
.foregroundColor(.secondary)
} else if search_text.isEmpty {
VStack(alignment: .center) {
Text(NSLocalizedString("Try searching for keywords", comment: "A label suggesting the user search for keywords"))
}
.padding(.vertical)
.foregroundColor(.secondary)
}
}
.onChange(of: search_text) { _ in
if search_text.isEmpty {
results = [NostrEvent]()
}
}
.onChange(of: results) { _ in
if search_text.isEmpty {
results = [NostrEvent]()
}
}
}
}
Loading