Multiple improvements: Removed migration code, updated sound files, improved prompt templates
This commit is contained in:
parent
6100ed1c6c
commit
b5773b38a1
@ -1,195 +0,0 @@
|
||||
import Foundation
|
||||
import os
|
||||
|
||||
class DataMigrationManager {
|
||||
private let logger = Logger(
|
||||
subsystem: "com.prakashjoshipax.VoiceInk",
|
||||
category: "DataMigration"
|
||||
)
|
||||
|
||||
static let shared = DataMigrationManager()
|
||||
private let swiftDataMigrationKey = "hasPerformedSwiftDataMigration"
|
||||
private let whisperModelsMigrationKey = "hasPerformedWhisperModelsMigration"
|
||||
private let preferencesMigrationKey = "hasPerformedPreferencesMigration"
|
||||
|
||||
private init() {}
|
||||
|
||||
func performMigrationsIfNeeded() {
|
||||
migratePreferencesIfNeeded() // Do preferences first as other migrations might need new preferences
|
||||
migrateSwiftDataStoreIfNeeded()
|
||||
migrateWhisperModelsIfNeeded()
|
||||
}
|
||||
|
||||
private func migratePreferencesIfNeeded() {
|
||||
// Check if migration has already been performed
|
||||
if UserDefaults.standard.bool(forKey: preferencesMigrationKey) {
|
||||
logger.info("Preferences migration already performed")
|
||||
return
|
||||
}
|
||||
|
||||
logger.info("Starting preferences migration")
|
||||
|
||||
let bundleId = "com.prakashjoshipax.VoiceInk"
|
||||
|
||||
// Old location (in Containers)
|
||||
let oldPrefsURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("Containers/\(bundleId)/Data/Library/Preferences/\(bundleId).plist")
|
||||
|
||||
// New location
|
||||
let newPrefsURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("Preferences/\(bundleId).plist")
|
||||
|
||||
do {
|
||||
if FileManager.default.fileExists(atPath: oldPrefsURL.path) {
|
||||
logger.info("Found old preferences at: \(oldPrefsURL.path)")
|
||||
|
||||
// Read old preferences
|
||||
let oldPrefsData = try Data(contentsOf: oldPrefsURL)
|
||||
if let oldPrefs = try PropertyListSerialization.propertyList(from: oldPrefsData, format: nil) as? [String: Any] {
|
||||
|
||||
// Migrate each preference to new UserDefaults
|
||||
for (key, value) in oldPrefs {
|
||||
UserDefaults.standard.set(value, forKey: key)
|
||||
logger.info("Migrated preference: \(key)")
|
||||
}
|
||||
|
||||
// Ensure changes are written to disk
|
||||
UserDefaults.standard.synchronize()
|
||||
|
||||
// Try to remove old preferences file
|
||||
try? FileManager.default.removeItem(at: oldPrefsURL)
|
||||
logger.info("Removed old preferences file")
|
||||
}
|
||||
|
||||
// Mark migration as complete
|
||||
UserDefaults.standard.set(true, forKey: preferencesMigrationKey)
|
||||
logger.info("Preferences migration completed successfully")
|
||||
} else {
|
||||
logger.info("No old preferences file found at: \(oldPrefsURL.path)")
|
||||
// Mark migration as complete even if no old prefs found
|
||||
UserDefaults.standard.set(true, forKey: preferencesMigrationKey)
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to migrate preferences: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
private func migrateSwiftDataStoreIfNeeded() {
|
||||
// Check if migration has already been performed
|
||||
if UserDefaults.standard.bool(forKey: swiftDataMigrationKey) {
|
||||
logger.info("SwiftData migration already performed")
|
||||
return
|
||||
}
|
||||
|
||||
logger.info("Starting SwiftData store migration")
|
||||
|
||||
// Old location (in Containers directory)
|
||||
let oldContainerURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||
.deletingLastPathComponent() // Go up to Library
|
||||
.appendingPathComponent("Containers/com.prakashjoshipax.VoiceInk/Data/Library/Application Support")
|
||||
|
||||
// New location (in Application Support)
|
||||
let newContainerURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("com.prakashjoshipax.VoiceInk")
|
||||
|
||||
let storeFiles = [
|
||||
"default.store",
|
||||
"default.store-wal",
|
||||
"default.store-shm"
|
||||
]
|
||||
|
||||
do {
|
||||
// Create new directory if it doesn't exist
|
||||
try FileManager.default.createDirectory(at: newContainerURL, withIntermediateDirectories: true)
|
||||
|
||||
// Migrate each store file
|
||||
for fileName in storeFiles {
|
||||
let oldURL = oldContainerURL.appendingPathComponent(fileName)
|
||||
let newURL = newContainerURL.appendingPathComponent(fileName)
|
||||
|
||||
if FileManager.default.fileExists(atPath: oldURL.path) {
|
||||
logger.info("Migrating \(fileName)")
|
||||
|
||||
// If a file already exists at the destination, remove it first
|
||||
if FileManager.default.fileExists(atPath: newURL.path) {
|
||||
try FileManager.default.removeItem(at: newURL)
|
||||
}
|
||||
|
||||
// Copy the file to new location
|
||||
try FileManager.default.copyItem(at: oldURL, to: newURL)
|
||||
|
||||
// Remove the old file
|
||||
try FileManager.default.removeItem(at: oldURL)
|
||||
|
||||
logger.info("Successfully migrated \(fileName)")
|
||||
} else {
|
||||
logger.info("No \(fileName) found at old location")
|
||||
}
|
||||
}
|
||||
|
||||
// Mark migration as complete
|
||||
UserDefaults.standard.set(true, forKey: swiftDataMigrationKey)
|
||||
logger.info("SwiftData migration completed successfully")
|
||||
|
||||
} catch {
|
||||
logger.error("Failed to migrate SwiftData store: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
private func migrateWhisperModelsIfNeeded() {
|
||||
// Check if migration has already been performed
|
||||
if UserDefaults.standard.bool(forKey: whisperModelsMigrationKey) {
|
||||
logger.info("Whisper models migration already performed")
|
||||
return
|
||||
}
|
||||
|
||||
logger.info("Starting Whisper models migration")
|
||||
|
||||
// Old location (in Containers directory)
|
||||
let oldModelsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
.deletingLastPathComponent() // Go up to Documents
|
||||
.deletingLastPathComponent() // Go up to Data
|
||||
.deletingLastPathComponent() // Go up to com.prakashjoshipax.VoiceInk
|
||||
.deletingLastPathComponent() // Go up to Containers
|
||||
.appendingPathComponent("com.prakashjoshipax.VoiceInk/Data/Documents/WhisperModels")
|
||||
|
||||
// New location (in Documents)
|
||||
let newModelsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("WhisperModels")
|
||||
|
||||
do {
|
||||
// Create new directory if it doesn't exist
|
||||
try FileManager.default.createDirectory(at: newModelsURL, withIntermediateDirectories: true)
|
||||
|
||||
// Get all files in the old directory
|
||||
if let modelFiles = try? FileManager.default.contentsOfDirectory(at: oldModelsURL, includingPropertiesForKeys: nil) {
|
||||
for modelURL in modelFiles {
|
||||
let fileName = modelURL.lastPathComponent
|
||||
let newURL = newModelsURL.appendingPathComponent(fileName)
|
||||
|
||||
logger.info("Migrating model: \(fileName)")
|
||||
|
||||
// If a file already exists at the destination, remove it first
|
||||
if FileManager.default.fileExists(atPath: newURL.path) {
|
||||
try FileManager.default.removeItem(at: newURL)
|
||||
}
|
||||
|
||||
// Copy the file to new location
|
||||
try FileManager.default.copyItem(at: modelURL, to: newURL)
|
||||
|
||||
// Remove the old file
|
||||
try FileManager.default.removeItem(at: modelURL)
|
||||
|
||||
logger.info("Successfully migrated model: \(fileName)")
|
||||
}
|
||||
}
|
||||
|
||||
// Mark migration as complete
|
||||
UserDefaults.standard.set(true, forKey: whisperModelsMigrationKey)
|
||||
logger.info("Whisper models migration completed successfully")
|
||||
|
||||
} catch {
|
||||
logger.error("Failed to migrate Whisper models: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -108,6 +108,7 @@ class HotkeyManager: ObservableObject {
|
||||
Task { @MainActor in
|
||||
guard let self = self,
|
||||
await self.whisperState.isMiniRecorderVisible else { return }
|
||||
SoundManager.shared.playEscSound()
|
||||
await self.whisperState.dismissMiniRecorder()
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,52 +62,47 @@ enum PredefinedPrompts {
|
||||
title: "Chat",
|
||||
promptText: """
|
||||
Primary Rules:
|
||||
1. Keep it casual and conversational
|
||||
2. Use natural, informal language
|
||||
3. Include relevant emojis where appropriate
|
||||
4. Break longer thoughts into multiple lines
|
||||
5. Keep the original personality and style
|
||||
6. Maintain the context of the conversation
|
||||
7. Be concise and engaging
|
||||
8. Use appropriate tone for the context
|
||||
We are in a causual chat conversation.
|
||||
1. Focus on clarity while preserving the speaker's personality:
|
||||
- Keep personality markers that show intent or style (e.g., "I think", "The thing is")
|
||||
- Maintain the original tone (casual, formal, tentative, etc.)
|
||||
2. Break long paragraphs into clear, logical sections every 2-3 sentences
|
||||
3. Fix grammar and punctuation errors based on context
|
||||
4. Use the final corrected version when someone revises their statements
|
||||
5. Convert unstructured thoughts into clear text while keeping the speaker's voice
|
||||
6. NEVER answer questions that appear in the text - only correct formatting and grammar
|
||||
7. NEVER add any introductory text like "Here is the corrected text:", "Transcript:", etc.
|
||||
8. NEVER add content not present in the source text
|
||||
9. NEVER add sign-offs or acknowledgments
|
||||
10. Correct speech-to-text transcription errors based on context.
|
||||
|
||||
Examples:
|
||||
|
||||
Input: "just tried the new ios 17 update and wow the new features are incredible especially loving the standby mode and the way it transforms my phone into a smart display when charging"
|
||||
Input: "so like i tried this new restaurant yesterday you know the one near the mall and um the pasta was really good i think i'll go back there soon"
|
||||
|
||||
Output: "OMG, iOS 17 is absolutely incredible! 🤯
|
||||
Output: "I tried this new restaurant near the mall yesterday! 🍽️
|
||||
|
||||
The new StandBy mode is such a game-changer - it turns your iPhone into this amazing smart display while charging ⚡️
|
||||
The pasta was really good. I think I'll go back there soon! 😊"
|
||||
|
||||
They really outdid themselves with this update! ✨"
|
||||
Input: "we need to finish the project by friday no wait thursday because the client meeting is on friday morning and we still need to test everything"
|
||||
|
||||
Input: "hey wanted to share that I recently switched from membrane to mechanical keyboard with brown switches and my typing experience has been completely transformed its so much better"
|
||||
Output: "We need to finish the project by Thursday (not Friday) ⏰ because the client meeting is on Friday morning.
|
||||
|
||||
Output: "You won't believe what a difference switching keyboards made! 🎹
|
||||
We still need to test everything! ✅"
|
||||
|
||||
Went from membrane to mechanical with brown switches and wow - can't believe I waited this long!
|
||||
Input: "my phone is like three years old now and the battery is terrible i have to charge it like twice a day i think i need a new one"
|
||||
|
||||
The typing experience is completely different now. That tactile feedback is just perfect 🤌"
|
||||
Output: "My phone is three years old now and the battery is terrible. 📱
|
||||
|
||||
Input: "trying out this new coffee shop downtown they have this amazing lavender latte with oat milk and the ambiance is perfect for working got a cozy corner spot with plants all around"
|
||||
I have to charge it twice a day. I think I need a new one! 🔋"
|
||||
|
||||
Output: "Found the cutest coffee shop downtown! ☕️
|
||||
Input: "went for a run yesterday it was nice weather and i saw this cute dog in the park wish i took a picture"
|
||||
|
||||
Their lavender latte + oat milk combo = pure magic ✨
|
||||
Output: "Went for a run yesterday! 🏃♀️
|
||||
|
||||
Got the perfect cozy corner surrounded by plants 🪴
|
||||
|
||||
Perfect spot for getting work done! 💯"
|
||||
It was nice weather and I saw this cute dog in the park. 🐶
|
||||
|
||||
Input: "about the coffee beans for cold brew actually found that medium roast guatemalan beans work amazing when steeped for 18 hours the flavor is so smooth"
|
||||
|
||||
Output: "You have to try Guatemalan medium roast for cold brew!
|
||||
|
||||
18-hour steep = liquid gold ✨
|
||||
|
||||
It makes the smoothest cold brew ever!
|
||||
|
||||
Let me know if you try it! ☕️"
|
||||
Wish I took a picture! 📸"
|
||||
""",
|
||||
icon: .chatFill,
|
||||
description: "Casual chat-style formatting",
|
||||
|
||||
@ -44,46 +44,34 @@ enum PromptTemplates {
|
||||
|
||||
Examples:
|
||||
|
||||
Input: "hey just wanted to follow up about the meeting from yesterday we discussed the new feature implementation and decided on the timeline so basically we need to have it done by next month and also please send me the documentation when you can thanks"
|
||||
Input: "hey just wanted to follow up on yesterday's meeting about the timeline we need to finish by next month can you send the docs when ready thanks"
|
||||
|
||||
Output: "I wanted to follow up regarding yesterday's meeting about the new feature implementation.
|
||||
Output: "Hi,
|
||||
|
||||
We discussed and agreed on the following points:
|
||||
1. Feature implementation timeline has been set
|
||||
2. Project completion is scheduled for next month
|
||||
I wanted to follow up on yesterday's meeting about the timeline. We need to finish by next month.
|
||||
|
||||
Could you please send me the documentation when available?
|
||||
Could you send the docs when ready?
|
||||
|
||||
Thanks,
|
||||
[Your Name]"
|
||||
|
||||
Input: "quick update on the project we're at 60% complete but facing some testing issues that might delay things we're working on solutions"
|
||||
|
||||
Output: "We're at 60% complete but facing some testing issues that might delay things. We're working on solutions.
|
||||
Can you please push the recent changes that we have made to GitHub?
|
||||
I'll keep you updated.
|
||||
|
||||
Regards,
|
||||
[[Your Name]]"
|
||||
[Your Name]"
|
||||
|
||||
Input: "quick update on the project status so we've completed about 60% of the development phase but we're facing some challenges with the integration testing which might impact our deadline but we're working on solutions"
|
||||
|
||||
Output: "I'm writing to provide a status update on the project:
|
||||
|
||||
Current Progress:
|
||||
- Development phase: 60% complete
|
||||
- Currently experiencing challenges with integration testing
|
||||
|
||||
Please note that these challenges may impact our deadline. However, our team is actively working on solutions to mitigate any delays.
|
||||
|
||||
I will keep you updated on our progress.
|
||||
|
||||
Regards,
|
||||
[[Your Name]]"
|
||||
|
||||
Input: "hey sareh just checking in about the design review feedback from last week's presentation wanted to know if you have any additional comments or if we're good to proceed with the next phase thanks"
|
||||
Input: "hi sarah checking in about the design feedback from last week can we proceed to the next phase"
|
||||
|
||||
Output: "Hi Sarah,
|
||||
|
||||
I hope this email finds you well. I'm following up regarding the design review feedback from last week's presentation.
|
||||
I'm checking in about the design feedback from last week. Can we proceed to the next phase?
|
||||
|
||||
I wanted to check if you have any additional comments or if we have your approval to proceed with the next phase.
|
||||
|
||||
Looking forward to your response.
|
||||
|
||||
Regards,
|
||||
[[Your Name]]"
|
||||
Thanks,
|
||||
[Your Name]"
|
||||
""",
|
||||
icon: .emailFill,
|
||||
description: "Template for converting casual messages into professional email format"
|
||||
@ -106,48 +94,35 @@ enum PromptTemplates {
|
||||
|
||||
Examples:
|
||||
|
||||
Input: "ok so in today's meeting with the design team we talked about the new UI changes Sarah mentioned we need to update the color scheme by next week and then John was saying something about accessibility improvements and we also need to test it with users oh and we decided to launch it end of next month"
|
||||
Input: "meeting with design team today we talked about UI changes Sarah will update colors by next week John will work on accessibility and we'll launch next month"
|
||||
|
||||
Output: "Design Team Meeting Summary:
|
||||
Output: "Design Team Meeting:
|
||||
|
||||
Key Discussion Points:
|
||||
• UI Changes Review
|
||||
• Color Scheme Updates
|
||||
• Accessibility Improvements
|
||||
• User Testing Requirements
|
||||
Discussion:
|
||||
• UI changes
|
||||
• Color updates
|
||||
• Accessibility improvements
|
||||
|
||||
Action Items:
|
||||
1. Update color scheme (Owner: Sarah)
|
||||
Deadline: Next week
|
||||
2. Implement accessibility improvements (Owner: John)
|
||||
3. Conduct user testing
|
||||
• Sarah: Update colors by next week
|
||||
• John: Work on accessibility
|
||||
|
||||
Important Decisions:
|
||||
- Project launch scheduled for end of next month
|
||||
Decision:
|
||||
• Launch next month"
|
||||
|
||||
Next Steps:
|
||||
• Begin color scheme updates
|
||||
• Plan user testing sessions"
|
||||
Input: "backend sync meeting we need to optimize database queries Mark will do this week Lisa will help with caching done by Friday then testing"
|
||||
|
||||
Input: "quick sync about the backend changes we need to optimize the database queries Mark said he'll look into it this week and Lisa will help with the caching implementation we should have it done by friday and then we can start testing"
|
||||
Output: "Backend Sync Meeting:
|
||||
|
||||
Output: "Backend Optimization Sync:
|
||||
Focus: Database optimization
|
||||
|
||||
Discussion Points:
|
||||
1. Database Query Optimization
|
||||
2. Caching Implementation
|
||||
|
||||
Assignments:
|
||||
• Database optimization - Mark
|
||||
• Caching implementation - Lisa
|
||||
Tasks:
|
||||
• Mark: Optimize database queries this week
|
||||
• Lisa: Help with caching
|
||||
|
||||
Timeline:
|
||||
• Implementation deadline: Friday
|
||||
• Testing to begin after implementation
|
||||
|
||||
Next Steps:
|
||||
1. Complete optimization work
|
||||
2. Begin testing phase"
|
||||
• Complete by Friday
|
||||
• Begin testing after"
|
||||
""",
|
||||
icon: .meetingFill,
|
||||
description: "Template for structuring meeting notes and action items"
|
||||
@ -169,43 +144,29 @@ enum PromptTemplates {
|
||||
|
||||
Examples:
|
||||
|
||||
Input: "just tried the new ios 17 update and wow the new features are incredible especially loving the standby mode and the way it transforms my phone into a smart display when charging"
|
||||
Input: "tried ios 17 today and the standby mode is amazing turns your phone into a smart display while charging"
|
||||
|
||||
Output: "Just tried iOS 17 and I'm blown away! 🤯
|
||||
Output: "Tried iOS 17 today and the standby mode is amazing! 🤯
|
||||
|
||||
The new StandBy mode is a game-changer - turns your iPhone into a smart display while charging ⚡️
|
||||
Turns your phone into a smart display while charging ⚡️ #iOS17"
|
||||
|
||||
Apple really outdid themselves this time! #iOS17"
|
||||
Input: "just switched from membrane to mechanical keyboard with brown switches and my typing feels so much better"
|
||||
|
||||
Input: "hey saw your thread about mechanical keyboards and wanted to share that I recently switched from membrane to mechanical with brown switches and my typing experience has been completely transformed its so much better"
|
||||
Output: "Just switched from membrane to mechanical keyboard with brown switches and my typing feels so much better! 🎹
|
||||
|
||||
Output: "@TechGuru Jumping on your mech keyboard thread! 🎹
|
||||
That tactile feedback is perfect 🤌 #MechKeys"
|
||||
|
||||
Made the switch from membrane to brown switches and OMG - can't believe I waited this long!
|
||||
Input: "found a nice coffee shop downtown with great lavender latte and cozy spots with plants perfect for working"
|
||||
|
||||
My typing experience is completely different now. That tactile feedback is *chef's kiss* 🤌 #MechKeys"
|
||||
Output: "Found a nice coffee shop downtown! ☕️
|
||||
|
||||
Input: "trying out this new coffee shop downtown they have this amazing lavender latte with oat milk and the ambiance is perfect for working got a cozy corner spot with plants all around"
|
||||
Great lavender latte and cozy spots with plants - perfect for working 🪴 #CoffeeVibes"
|
||||
|
||||
Output: "Found the cutest coffee shop downtown! ☕️
|
||||
Input: "for cold brew coffee medium roast guatemalan beans steeped for 18 hours makes the smoothest flavor"
|
||||
|
||||
Their lavender latte + oat milk combo = pure magic ✨
|
||||
Output: "For cold brew coffee: medium roast Guatemalan beans steeped for 18 hours makes the smoothest flavor! ☕️
|
||||
|
||||
Secured the perfect cozy corner surrounded by plants 🪴
|
||||
|
||||
Productivity level = 💯
|
||||
|
||||
#CoffeeVibes #WorkFromCafe"
|
||||
|
||||
Input: " responding to your question about the best coffee beans for cold brew actually found that medium roast guatemalan beans work amazing when steeped for 18 hours the flavor is so smooth"
|
||||
|
||||
Output: "@CoffeeExplorer Re: cold brew beans - you NEED to try Guatemalan medium roast!
|
||||
|
||||
18-hour steep = liquid gold ✨
|
||||
|
||||
The smoothest cold brew you'll ever taste, no cap!
|
||||
|
||||
Let me know if you try it! ☕️ #ColdBrew"
|
||||
Absolute liquid gold ✨ #ColdBrew"
|
||||
""",
|
||||
icon: .chatFill,
|
||||
description: "Template for crafting engaging tweets and replies with personality"
|
||||
|
||||
Binary file not shown.
BIN
VoiceInk/Resources/Sounds/esc.wav
Executable file
BIN
VoiceInk/Resources/Sounds/esc.wav
Executable file
Binary file not shown.
BIN
VoiceInk/Resources/Sounds/pastes.mp3
Executable file
BIN
VoiceInk/Resources/Sounds/pastes.mp3
Executable file
Binary file not shown.
BIN
VoiceInk/Resources/Sounds/recstart.mp3
Executable file
BIN
VoiceInk/Resources/Sounds/recstart.mp3
Executable file
Binary file not shown.
Binary file not shown.
@ -7,6 +7,7 @@ class SoundManager {
|
||||
|
||||
private var startSound: AVAudioPlayer?
|
||||
private var stopSound: AVAudioPlayer?
|
||||
private var escSound: AVAudioPlayer?
|
||||
|
||||
@AppStorage("isSoundFeedbackEnabled") private var isSoundFeedbackEnabled = false
|
||||
|
||||
@ -18,10 +19,11 @@ class SoundManager {
|
||||
print("Attempting to load sound files...")
|
||||
|
||||
// Try loading directly from the main bundle
|
||||
if let startSoundURL = Bundle.main.url(forResource: "start", withExtension: "mp3"),
|
||||
let stopSoundURL = Bundle.main.url(forResource: "Stop", withExtension: "mp3") {
|
||||
if let startSoundURL = Bundle.main.url(forResource: "recstart", withExtension: "mp3"),
|
||||
let stopSoundURL = Bundle.main.url(forResource: "pastes", withExtension: "mp3"),
|
||||
let escSoundURL = Bundle.main.url(forResource: "esc", withExtension: "wav") {
|
||||
print("Found sounds in main bundle")
|
||||
try? loadSounds(start: startSoundURL, stop: stopSoundURL)
|
||||
try? loadSounds(start: startSoundURL, stop: stopSoundURL, esc: escSoundURL)
|
||||
return
|
||||
}
|
||||
|
||||
@ -40,20 +42,23 @@ class SoundManager {
|
||||
}
|
||||
}
|
||||
|
||||
private func loadSounds(start startURL: URL, stop stopURL: URL) throws {
|
||||
private func loadSounds(start startURL: URL, stop stopURL: URL, esc escURL: URL) throws {
|
||||
do {
|
||||
startSound = try AVAudioPlayer(contentsOf: startURL)
|
||||
stopSound = try AVAudioPlayer(contentsOf: stopURL)
|
||||
escSound = try AVAudioPlayer(contentsOf: escURL)
|
||||
|
||||
// Set lower volume for both sounds
|
||||
startSound?.volume = 0.2
|
||||
stopSound?.volume = 0.2
|
||||
// Set lower volume for all sounds
|
||||
startSound?.volume = 0.7
|
||||
stopSound?.volume = 0.7
|
||||
escSound?.volume = 0.3
|
||||
|
||||
// Prepare sounds for instant playback
|
||||
startSound?.prepareToPlay()
|
||||
stopSound?.prepareToPlay()
|
||||
escSound?.prepareToPlay()
|
||||
|
||||
print("✅ Successfully loaded both sound files")
|
||||
print("✅ Successfully loaded all sound files")
|
||||
} catch {
|
||||
print("❌ Error loading sounds: \(error.localizedDescription)")
|
||||
throw error
|
||||
@ -70,8 +75,13 @@ class SoundManager {
|
||||
stopSound?.play()
|
||||
}
|
||||
|
||||
func playEscSound() {
|
||||
guard isSoundFeedbackEnabled else { return }
|
||||
escSound?.play()
|
||||
}
|
||||
|
||||
var isEnabled: Bool {
|
||||
get { isSoundFeedbackEnabled }
|
||||
set { isSoundFeedbackEnabled = newValue }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,29 +1,13 @@
|
||||
import SwiftUI
|
||||
|
||||
// Old format for migration
|
||||
private struct LegacyDictionaryItem: Codable {
|
||||
let id: UUID
|
||||
var word: String
|
||||
var dateAdded: Date
|
||||
}
|
||||
|
||||
struct DictionaryItem: Identifiable, Hashable, Codable {
|
||||
let id: UUID
|
||||
var word: String
|
||||
var dateAdded: Date
|
||||
var isEnabled: Bool
|
||||
|
||||
// Migration initializer
|
||||
fileprivate init(from legacy: LegacyDictionaryItem) {
|
||||
self.id = legacy.id
|
||||
self.word = legacy.word
|
||||
self.dateAdded = legacy.dateAdded
|
||||
self.isEnabled = true // Default to enabled for migrated items
|
||||
}
|
||||
|
||||
// Standard initializer
|
||||
init(word: String, dateAdded: Date = Date(), isEnabled: Bool = true) {
|
||||
self.id = UUID()
|
||||
init(id: UUID = UUID(), word: String, dateAdded: Date = Date(), isEnabled: Bool = true) {
|
||||
self.id = id
|
||||
self.word = word
|
||||
self.dateAdded = dateAdded
|
||||
self.isEnabled = isEnabled
|
||||
@ -43,17 +27,10 @@ class DictionaryManager: ObservableObject {
|
||||
private func loadItems() {
|
||||
guard let data = UserDefaults.standard.data(forKey: saveKey) else { return }
|
||||
|
||||
// Try loading with new format first
|
||||
if let savedItems = try? JSONDecoder().decode([DictionaryItem].self, from: data) {
|
||||
items = savedItems.sorted(by: { $0.dateAdded > $1.dateAdded })
|
||||
} else {
|
||||
// If that fails, try loading old format and migrate
|
||||
if let legacyItems = try? JSONDecoder().decode([LegacyDictionaryItem].self, from: data) {
|
||||
items = legacyItems.map(DictionaryItem.init).sorted(by: { $0.dateAdded > $1.dateAdded })
|
||||
// Save in new format immediately
|
||||
saveItems()
|
||||
}
|
||||
}
|
||||
|
||||
Task { @MainActor in
|
||||
await whisperState.saveDictionaryItems(items)
|
||||
}
|
||||
|
||||
@ -19,9 +19,6 @@ struct VoiceInkApp: App {
|
||||
|
||||
init() {
|
||||
do {
|
||||
// Perform all data migrations if needed
|
||||
DataMigrationManager.shared.performMigrationsIfNeeded()
|
||||
|
||||
let schema = Schema([
|
||||
Transcription.self
|
||||
])
|
||||
|
||||
@ -761,6 +761,7 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
|
||||
|
||||
func cancelRecording() async {
|
||||
shouldCancelRecording = true
|
||||
SoundManager.shared.playEscSound()
|
||||
if isRecording {
|
||||
await recorder.stopRecording()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user