From 59a326e6c382e130a8fb15e0610349dbe9f78510 Mon Sep 17 00:00:00 2001 From: Owen Gretzinger Date: Thu, 10 Jul 2025 21:46:24 -0400 Subject: [PATCH] feat: add search functionality --- notetaker/ContentView.swift | 16 +------- .../ViewModels/MeetingListViewModel.swift | 19 ++++++++++ notetaker/Views/MeetingListView.swift | 38 +++++++++++++++---- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/notetaker/ContentView.swift b/notetaker/ContentView.swift index 26debea..7f41084 100644 --- a/notetaker/ContentView.swift +++ b/notetaker/ContentView.swift @@ -11,21 +11,7 @@ struct ContentView: View { @State private var showingSettings = false var body: some View { - VStack { - MeetingListView() - .toolbar { - ToolbarItem(placement: .automatic) { - Button { - showingSettings = true - } label: { - Label("Settings", systemImage: "gear") - } - } - } - } - .sheet(isPresented: $showingSettings) { - SettingsView() - } + MeetingListView() } } diff --git a/notetaker/ViewModels/MeetingListViewModel.swift b/notetaker/ViewModels/MeetingListViewModel.swift index 72a7ffc..153553d 100644 --- a/notetaker/ViewModels/MeetingListViewModel.swift +++ b/notetaker/ViewModels/MeetingListViewModel.swift @@ -7,9 +7,28 @@ class MeetingListViewModel: ObservableObject { @Published var meetings: [Meeting] = [] @Published var isLoading = false @Published var errorMessage: String? + @Published var searchText: String = "" private var cancellables = Set() + // Computed property to filter meetings based on search text + var filteredMeetings: [Meeting] { + guard !searchText.isEmpty else { return meetings } + + return meetings.filter { meeting in + // Search in title + meeting.title.localizedCaseInsensitiveContains(searchText) || + // Search in user notes + meeting.userNotes.localizedCaseInsensitiveContains(searchText) || + // Search in generated notes + meeting.generatedNotes.localizedCaseInsensitiveContains(searchText) || + // Search in transcript text + meeting.transcriptChunks.contains { chunk in + chunk.text.localizedCaseInsensitiveContains(searchText) + } + } + } + init() { loadMeetings() diff --git a/notetaker/Views/MeetingListView.swift b/notetaker/Views/MeetingListView.swift index d1094fb..cf2a59a 100644 --- a/notetaker/Views/MeetingListView.swift +++ b/notetaker/Views/MeetingListView.swift @@ -3,15 +3,16 @@ import SwiftUI struct MeetingListView: View { @StateObject private var viewModel = MeetingListViewModel() @State private var navigationPath = NavigationPath() + @State private var showingSettings = false var body: some View { NavigationStack(path: $navigationPath) { List { - if viewModel.meetings.isEmpty && !viewModel.isLoading { + if viewModel.filteredMeetings.isEmpty && !viewModel.isLoading { ContentUnavailableView( - "No Meetings Yet", - systemImage: "mic.slash", - description: Text("Start a new meeting to begin transcribing") + viewModel.searchText.isEmpty ? "No Meetings Yet" : "No Results", + systemImage: viewModel.searchText.isEmpty ? "mic.slash" : "magnifyingglass", + description: Text(viewModel.searchText.isEmpty ? "Start a new meeting to begin transcribing" : "Try a different search term") ) } else { ForEach(groupedMeetings, id: \.day) { dayGroup in @@ -37,15 +38,38 @@ struct MeetingListView: View { } .navigationTitle("Meetings") .toolbar { - ToolbarItem(placement: .primaryAction) { + ToolbarItemGroup(placement: .primaryAction) { + // Search field (leftmost within the right-aligned group) + HStack(spacing: 4) { + Image(systemName: "magnifyingglass") + .foregroundColor(.secondary) + TextField("Search meetings...", text: $viewModel.searchText) + .textFieldStyle(.plain) + } + .padding(6) + .background( + RoundedRectangle(cornerRadius: 6) + .stroke(Color.secondary.opacity(0.5)) + ) + .frame(width: 220) + // Settings button (middle) + Button { + showingSettings = true + } label: { + Image(systemName: "gearshape") + } + // Plus button (rightmost) Button { let newMeeting = viewModel.createNewMeeting() navigationPath.append(newMeeting) } label: { - Label("New Meeting", systemImage: "plus") + Image(systemName: "plus") } } } + .sheet(isPresented: $showingSettings) { + SettingsView() + } .navigationDestination(for: Meeting.self) { meeting in MeetingDetailView(meeting: meeting) } @@ -64,7 +88,7 @@ struct MeetingListView: View { let calendar = Calendar.current let now = Date() - let grouped = Dictionary(grouping: viewModel.meetings) { meeting in + let grouped = Dictionary(grouping: viewModel.filteredMeetings) { meeting in calendar.startOfDay(for: meeting.date) }