feat: new button layout (#48)

This commit is contained in:
Owen Gretzinger
2025-07-28 15:59:20 -04:00
committed by GitHub
parent 0ad714d6a6
commit 2b7940fc30
2 changed files with 101 additions and 97 deletions
@@ -184,16 +184,6 @@ class MeetingViewModel: ObservableObject {
func stopRecording() {
recordingSessionManager.stopRecording()
saveMeeting()
// Auto-generate notes if there's a transcript and no existing notes
if !meeting.formattedTranscript.isEmpty && meeting.generatedNotes.isEmpty {
print("🤖 Auto-generating notes after recording stopped")
// Switch to enhanced notes tab immediately when starting generation
selectedTab = .enhancedNotes
Task {
await generateNotes()
}
}
}
func loadTemplates() {
+101 -87
View File
@@ -282,8 +282,38 @@ struct MeetingDetailContentView: View {
Spacer()
// Right: Recording and Copy Buttons
// Right: Generate and Recording Buttons
HStack(spacing: 12) {
// Generate Button (Dropdown)
Menu {
ForEach(viewModel.templates) { template in
Button(template.title) {
viewModel.selectedTemplateId = template.id
viewModel.selectedTab = .enhancedNotes
isEditing = false
}
}
} label: {
HStack(spacing: 4) {
if viewModel.isGeneratingNotes {
ProgressView()
.scaleEffect(0.4)
.frame(width: 12, height: 12)
} else {
Image(systemName: "sparkles")
.font(.caption)
}
Text("Generate")
}
.frame(minWidth: 110, minHeight: 36)
.background(Color.green.opacity(0.1))
.cornerRadius(8)
}
.buttonStyle(.plain)
.disabled(viewModel.meeting.transcript.isEmpty || viewModel.isGeneratingNotes || viewModel.isRecording || viewModel.isStartingRecording)
.help("Generate enhanced notes using a template")
// Recording Button
Button(action: {
viewModel.toggleRecording()
}) {
@@ -299,12 +329,42 @@ struct MeetingDetailContentView: View {
.buttonStyle(.plain)
.disabled(cannotStartRecording || viewModel.isValidatingKey || viewModel.isStartingRecording)
.help(cannotStartRecording ? "Another meeting is currently being recorded" : "Start or stop recording for this meeting")
}
}
// Content Area with Tab-specific Headers
VStack(alignment: .leading, spacing: 8) {
// Tab Header with Copy and Edit buttons
HStack {
Text(viewModel.selectedTab.rawValue)
.font(.headline)
.foregroundColor(.secondary)
Spacer()
// Edit/Preview button (for My Notes and Enhanced Notes)
if viewModel.selectedTab == .myNotes || viewModel.selectedTab == .enhancedNotes {
Button(action: {
isEditing.toggle()
}) {
HStack(spacing: 4) {
Image(systemName: isEditing ? "eye" : "pencil")
Text(isEditing ? "Preview" : "Edit")
}
.frame(minWidth: 75, minHeight: 24)
.font(.caption)
.background(Color.gray.opacity(0.1))
.cornerRadius(6)
}
.buttonStyle(.plain)
}
// Copy button
Button(action: {
viewModel.copyCurrentTabContent()
showCopyConfirmation = true
// Reset confirmation after 2 seconds
// Reset confirmation after 1 second
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
showCopyConfirmation = false
}
@@ -315,16 +375,15 @@ struct MeetingDetailContentView: View {
Text(showCopyConfirmation ? "Copied!" : "Copy")
.foregroundColor(showCopyConfirmation ? .green : .primary)
}
.frame(minWidth: 90, minHeight: 36)
.frame(minWidth: 70, minHeight: 24)
.font(.caption)
.background(showCopyConfirmation ? Color.green.opacity(0.1) : Color.gray.opacity(0.1))
.cornerRadius(8)
.cornerRadius(6)
}
.buttonStyle(.plain)
}
}
// Content Area
VStack(alignment: .leading, spacing: 8) {
// Tab Content
switch viewModel.selectedTab {
case .myNotes:
myNotesView
@@ -362,97 +421,52 @@ struct MeetingDetailContentView: View {
// MARK: - Content Views
private var myNotesView: some View {
VStack(alignment: .leading, spacing: 8) {
Text("My Notes")
.font(.headline)
.foregroundColor(.secondary)
TextEditor(text: $viewModel.meeting.userNotes)
.font(.body)
.padding(8)
.scrollContentBackground(.hidden)
VStack(alignment: .leading, spacing: 0) {
if isEditing {
TextEditor(text: $viewModel.meeting.userNotes)
.font(.body)
.padding(8)
.scrollContentBackground(.hidden)
.background(Color.gray.opacity(0.05))
.cornerRadius(8)
.frame(maxHeight: .infinity)
} else {
ScrollView {
Text(viewModel.meeting.userNotes.isEmpty ? "No notes yet..." : viewModel.meeting.userNotes)
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.foregroundColor(viewModel.meeting.userNotes.isEmpty ? .secondary : .primary)
}
.frame(maxHeight: .infinity)
.background(Color.gray.opacity(0.05))
.cornerRadius(8)
.frame(maxHeight: .infinity)
}
}
}
private var transcriptView: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Transcript")
.font(.headline)
.foregroundColor(.secondary)
ScrollView {
if viewModel.meeting.collapsedTranscriptChunks.isEmpty {
Text("Transcript will appear here...")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.foregroundColor(.secondary)
} else {
LazyVStack(alignment: .leading, spacing: 4) {
ForEach(viewModel.meeting.collapsedTranscriptChunks) { chunk in
CollapsedTranscriptChunkView(chunk: chunk)
}
}
ScrollView {
if viewModel.meeting.collapsedTranscriptChunks.isEmpty {
Text("Transcript will appear here...")
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
.foregroundColor(.secondary)
} else {
LazyVStack(alignment: .leading, spacing: 4) {
ForEach(viewModel.meeting.collapsedTranscriptChunks) { chunk in
CollapsedTranscriptChunkView(chunk: chunk)
}
}
.padding()
}
.frame(maxHeight: .infinity)
.background(Color.gray.opacity(0.05))
.cornerRadius(8)
}
.frame(maxHeight: .infinity)
.background(Color.gray.opacity(0.05))
.cornerRadius(8)
}
private var enhancedNotesView: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Enhanced Notes")
.font(.headline)
.foregroundColor(.secondary)
Spacer()
if viewModel.isGeneratingNotes {
ProgressView()
.scaleEffect(0.7)
} else {
// Template selector
Picker("", selection: $viewModel.selectedTemplateId) {
ForEach(viewModel.templates) { template in
Text(template.title).tag(template.id as UUID?)
}
}
.pickerStyle(.menu)
.frame(width: 200)
Button(action: {
Task {
await viewModel.generateNotes()
isEditing = false
}
}) {
HStack(spacing: 4) {
Image(systemName: "sparkles")
Text("Generate")
}
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.accentColor.opacity(0.1))
.cornerRadius(6)
}
.buttonStyle(.plain)
.disabled(viewModel.meeting.transcript.isEmpty)
}
Button(action: {
isEditing.toggle()
}) {
Image(systemName: isEditing ? "pencil.circle.fill" : "pencil.circle")
}
.buttonStyle(.plain)
}
VStack(alignment: .leading, spacing: 0) {
if isEditing {
TextEditor(text: Binding(
get: { viewModel.meeting.generatedNotes },
@@ -477,4 +491,4 @@ struct MeetingDetailContentView: View {
#Preview {
MeetingListView(settingsViewModel: SettingsViewModel())
}
}