added support for saving transcribed text in markdown and txt file
This commit is contained in:
parent
1b4ff63d1c
commit
3d66cee7fc
@ -38,7 +38,10 @@ struct AudioTranscribeView: View {
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
AnimatedCopyButton(textToCopy: enhancedText)
|
||||
HStack(spacing: 8) {
|
||||
AnimatedCopyButton(textToCopy: enhancedText)
|
||||
AnimatedSaveButton(textToSave: enhancedText)
|
||||
}
|
||||
}
|
||||
Text(enhancedText)
|
||||
.textSelection(.enabled)
|
||||
@ -52,7 +55,10 @@ struct AudioTranscribeView: View {
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
AnimatedCopyButton(textToCopy: transcription.text)
|
||||
HStack(spacing: 8) {
|
||||
AnimatedCopyButton(textToCopy: transcription.text)
|
||||
AnimatedSaveButton(textToSave: transcription.text)
|
||||
}
|
||||
}
|
||||
Text(transcription.text)
|
||||
.textSelection(.enabled)
|
||||
@ -64,7 +70,10 @@ struct AudioTranscribeView: View {
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Spacer()
|
||||
AnimatedCopyButton(textToCopy: transcription.text)
|
||||
HStack(spacing: 8) {
|
||||
AnimatedCopyButton(textToCopy: transcription.text)
|
||||
AnimatedSaveButton(textToSave: transcription.text)
|
||||
}
|
||||
}
|
||||
Text(transcription.text)
|
||||
.textSelection(.enabled)
|
||||
|
||||
120
VoiceInk/Views/Common/AnimatedSaveButton.swift
Normal file
120
VoiceInk/Views/Common/AnimatedSaveButton.swift
Normal file
@ -0,0 +1,120 @@
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct AnimatedSaveButton: View {
|
||||
let textToSave: String
|
||||
@State private var isSaved: Bool = false
|
||||
@State private var showingSavePanel = false
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
Button("Save as TXT") {
|
||||
saveFile(as: .plainText, extension: "txt")
|
||||
}
|
||||
|
||||
Button("Save as MD") {
|
||||
saveFile(as: .text, extension: "md")
|
||||
}
|
||||
} label: {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: isSaved ? "checkmark" : "square.and.arrow.down")
|
||||
.font(.system(size: 12, weight: isSaved ? .bold : .regular))
|
||||
.foregroundColor(.white)
|
||||
Text(isSaved ? "Saved" : "Save")
|
||||
.font(.system(size: 12, weight: isSaved ? .medium : .regular))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(isSaved ? Color.green.opacity(0.8) : Color.orange)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.scaleEffect(isSaved ? 1.05 : 1.0)
|
||||
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: isSaved)
|
||||
}
|
||||
|
||||
private func saveFile(as contentType: UTType, extension fileExtension: String) {
|
||||
let panel = NSSavePanel()
|
||||
panel.allowedContentTypes = [contentType]
|
||||
panel.nameFieldStringValue = "\(generateFileName()).\(fileExtension)"
|
||||
panel.title = "Save Transcription"
|
||||
|
||||
if panel.runModal() == .OK {
|
||||
guard let url = panel.url else { return }
|
||||
|
||||
do {
|
||||
let content = fileExtension == "md" ? formatAsMarkdown(textToSave) : textToSave
|
||||
try content.write(to: url, atomically: true, encoding: .utf8)
|
||||
|
||||
withAnimation {
|
||||
isSaved = true
|
||||
}
|
||||
|
||||
// Reset the animation after a delay
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
||||
withAnimation {
|
||||
isSaved = false
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("Failed to save file: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func generateFileName() -> String {
|
||||
// Clean the text and split into words
|
||||
let cleanedText = textToSave
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.replacingOccurrences(of: "\n", with: " ")
|
||||
.replacingOccurrences(of: "\r", with: " ")
|
||||
|
||||
let words = cleanedText.components(separatedBy: .whitespaces)
|
||||
.filter { !$0.isEmpty }
|
||||
|
||||
// Take first 5-8 words (depending on length)
|
||||
let wordCount = min(words.count, words.count <= 3 ? words.count : (words.count <= 6 ? 6 : 8))
|
||||
let selectedWords = Array(words.prefix(wordCount))
|
||||
|
||||
if selectedWords.isEmpty {
|
||||
return "transcription"
|
||||
}
|
||||
|
||||
// Create filename by joining words and cleaning invalid characters
|
||||
let fileName = selectedWords.joined(separator: "-")
|
||||
.lowercased()
|
||||
.replacingOccurrences(of: "[^a-z0-9\\-]", with: "", options: .regularExpression)
|
||||
.replacingOccurrences(of: "--+", with: "-", options: .regularExpression)
|
||||
.trimmingCharacters(in: CharacterSet(charactersIn: "-"))
|
||||
|
||||
// Ensure filename isn't empty and isn't too long
|
||||
let finalFileName = fileName.isEmpty ? "transcription" : String(fileName.prefix(50))
|
||||
|
||||
return finalFileName
|
||||
}
|
||||
|
||||
private func formatAsMarkdown(_ text: String) -> String {
|
||||
let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .short)
|
||||
return """
|
||||
# Transcription
|
||||
|
||||
**Date:** \(timestamp)
|
||||
|
||||
\(text)
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
struct AnimatedSaveButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack(spacing: 20) {
|
||||
AnimatedSaveButton(textToSave: "Hello world this is a sample transcription text")
|
||||
Text("Save Button Preview")
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user