feat: new button layout (#48)
This commit is contained in:
@@ -184,16 +184,6 @@ class MeetingViewModel: ObservableObject {
|
|||||||
func stopRecording() {
|
func stopRecording() {
|
||||||
recordingSessionManager.stopRecording()
|
recordingSessionManager.stopRecording()
|
||||||
saveMeeting()
|
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() {
|
func loadTemplates() {
|
||||||
|
|||||||
@@ -282,8 +282,38 @@ struct MeetingDetailContentView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
// Right: Recording and Copy Buttons
|
// Right: Generate and Recording Buttons
|
||||||
HStack(spacing: 12) {
|
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: {
|
Button(action: {
|
||||||
viewModel.toggleRecording()
|
viewModel.toggleRecording()
|
||||||
}) {
|
}) {
|
||||||
@@ -299,12 +329,42 @@ struct MeetingDetailContentView: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.disabled(cannotStartRecording || viewModel.isValidatingKey || viewModel.isStartingRecording)
|
.disabled(cannotStartRecording || viewModel.isValidatingKey || viewModel.isStartingRecording)
|
||||||
.help(cannotStartRecording ? "Another meeting is currently being recorded" : "Start or stop recording for this meeting")
|
.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: {
|
Button(action: {
|
||||||
viewModel.copyCurrentTabContent()
|
viewModel.copyCurrentTabContent()
|
||||||
showCopyConfirmation = true
|
showCopyConfirmation = true
|
||||||
|
|
||||||
// Reset confirmation after 2 seconds
|
// Reset confirmation after 1 second
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||||
showCopyConfirmation = false
|
showCopyConfirmation = false
|
||||||
}
|
}
|
||||||
@@ -315,16 +375,15 @@ struct MeetingDetailContentView: View {
|
|||||||
Text(showCopyConfirmation ? "Copied!" : "Copy")
|
Text(showCopyConfirmation ? "Copied!" : "Copy")
|
||||||
.foregroundColor(showCopyConfirmation ? .green : .primary)
|
.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))
|
.background(showCopyConfirmation ? Color.green.opacity(0.1) : Color.gray.opacity(0.1))
|
||||||
.cornerRadius(8)
|
.cornerRadius(6)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Content Area
|
// Tab Content
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
switch viewModel.selectedTab {
|
switch viewModel.selectedTab {
|
||||||
case .myNotes:
|
case .myNotes:
|
||||||
myNotesView
|
myNotesView
|
||||||
@@ -362,11 +421,8 @@ struct MeetingDetailContentView: View {
|
|||||||
// MARK: - Content Views
|
// MARK: - Content Views
|
||||||
|
|
||||||
private var myNotesView: some View {
|
private var myNotesView: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
Text("My Notes")
|
if isEditing {
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
|
|
||||||
TextEditor(text: $viewModel.meeting.userNotes)
|
TextEditor(text: $viewModel.meeting.userNotes)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.padding(8)
|
.padding(8)
|
||||||
@@ -374,15 +430,21 @@ struct MeetingDetailContentView: View {
|
|||||||
.background(Color.gray.opacity(0.05))
|
.background(Color.gray.opacity(0.05))
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
.frame(maxHeight: .infinity)
|
.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var transcriptView: some View {
|
private var transcriptView: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
|
||||||
Text("Transcript")
|
|
||||||
.font(.headline)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
|
|
||||||
ScrollView {
|
ScrollView {
|
||||||
if viewModel.meeting.collapsedTranscriptChunks.isEmpty {
|
if viewModel.meeting.collapsedTranscriptChunks.isEmpty {
|
||||||
Text("Transcript will appear here...")
|
Text("Transcript will appear here...")
|
||||||
@@ -402,57 +464,9 @@ struct MeetingDetailContentView: View {
|
|||||||
.background(Color.gray.opacity(0.05))
|
.background(Color.gray.opacity(0.05))
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private var enhancedNotesView: some View {
|
private var enhancedNotesView: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isEditing {
|
if isEditing {
|
||||||
TextEditor(text: Binding(
|
TextEditor(text: Binding(
|
||||||
get: { viewModel.meeting.generatedNotes },
|
get: { viewModel.meeting.generatedNotes },
|
||||||
|
|||||||
Reference in New Issue
Block a user