@@ -11,10 +11,18 @@ import KeyboardKit
1111class KeyboardViewController : KeyboardInputViewController {
1212
1313 var recordButton : UIButton !
14+ private let coordinator = AppGroupCoordinator . shared
15+ private var recordingStatusTimer : Timer ?
16+
17+ deinit {
18+ recordingStatusTimer? . invalidate ( )
19+ recordingStatusTimer = nil
20+ }
1421
1522 override func viewDidLoad( ) {
1623 super. viewDidLoad ( )
1724 setupKeyboard ( )
25+ setupRecordingStatusMonitoring ( )
1826 }
1927
2028 private func setupKeyboard( ) {
@@ -47,38 +55,77 @@ class KeyboardViewController: KeyboardInputViewController {
4755 }
4856
4957 private func setupRecordButton( ) {
50- // Create the capsule-shaped record button
58+ // Create the native iOS-style record button
5159 recordButton = UIButton ( type: . system)
52- recordButton. setTitle ( " 🎤 Record " , for: . normal)
53- recordButton. titleLabel? . font = UIFont . systemFont ( ofSize: 12 , weight: . medium)
54- recordButton. backgroundColor = UIColor . systemRed
55- recordButton. setTitleColor ( . white, for: . normal)
56- recordButton. layer. cornerRadius = 14 // Will be adjusted to make it capsule-shaped
5760 recordButton. translatesAutoresizingMaskIntoConstraints = false
5861 recordButton. addTarget ( self , action: #selector( recordButtonTapped) , for: . touchUpInside)
5962
60- // Add some padding and styling for better appearance
61- recordButton. contentEdgeInsets = UIEdgeInsets ( top: 4 , left: 10 , bottom: 4 , right: 10 )
63+ // Configure for idle state initially
64+ configureButtonForIdleState ( )
65+
66+ // Add native iOS styling
67+ recordButton. titleLabel? . font = UIFont . systemFont ( ofSize: 14 , weight: . semibold)
68+ recordButton. contentEdgeInsets = UIEdgeInsets ( top: 6 , left: 16 , bottom: 6 , right: 16 )
69+
70+ // Native iOS shadow and styling
6271 recordButton. layer. shadowColor = UIColor . black. cgColor
6372 recordButton. layer. shadowOffset = CGSize ( width: 0 , height: 1 )
64- recordButton. layer. shadowOpacity = 0.15
65- recordButton. layer. shadowRadius = 1.5
73+ recordButton. layer. shadowOpacity = 0.2
74+ recordButton. layer. shadowRadius = 2
75+
76+ // Add subtle border for better definition
77+ recordButton. layer. borderWidth = 0.5
78+ recordButton. layer. borderColor = UIColor . separator. cgColor
6679
6780 // Add button to main view
6881 view. addSubview ( recordButton)
6982
7083 // Set up constraints - position in top center with safe margins
7184 NSLayoutConstraint . activate ( [
7285 recordButton. centerXAnchor. constraint ( equalTo: view. centerXAnchor) ,
73- recordButton. topAnchor. constraint ( equalTo: view. safeAreaLayoutGuide. topAnchor, constant: 4 ) ,
74- recordButton. heightAnchor. constraint ( equalToConstant: 28 ) ,
75- recordButton. widthAnchor. constraint ( greaterThanOrEqualToConstant: 80 )
86+ recordButton. topAnchor. constraint ( equalTo: view. safeAreaLayoutGuide. topAnchor, constant: 6 ) ,
87+ recordButton. heightAnchor. constraint ( equalToConstant: 32 ) ,
88+ recordButton. widthAnchor. constraint ( greaterThanOrEqualToConstant: 120 )
7689 ] )
7790
7891 // Ensure button stays on top
7992 view. bringSubviewToFront ( recordButton)
8093 }
8194
95+ private func configureButtonForIdleState( ) {
96+ // Use SF Symbol for microphone
97+ let microphoneConfig = UIImage . SymbolConfiguration ( pointSize: 14 , weight: . semibold)
98+ let microphoneImage = UIImage ( systemName: " mic.fill " , withConfiguration: microphoneConfig)
99+
100+ recordButton. setImage ( microphoneImage, for: . normal)
101+ recordButton. setTitle ( " Record " , for: . normal)
102+ recordButton. backgroundColor = UIColor . systemBlue
103+ recordButton. setTitleColor ( . white, for: . normal)
104+ recordButton. tintColor = . white
105+
106+ // Ensure image and text are properly aligned
107+ recordButton. semanticContentAttribute = . forceLeftToRight
108+ recordButton. imageEdgeInsets = UIEdgeInsets ( top: 0 , left: 0 , bottom: 0 , right: 4 )
109+ recordButton. titleEdgeInsets = UIEdgeInsets ( top: 0 , left: 4 , bottom: 0 , right: 0 )
110+ }
111+
112+ private func configureButtonForRecordingState( ) {
113+ // Use SF Symbol for stop
114+ let stopConfig = UIImage . SymbolConfiguration ( pointSize: 14 , weight: . semibold)
115+ let stopImage = UIImage ( systemName: " stop.fill " , withConfiguration: stopConfig)
116+
117+ recordButton. setImage ( stopImage, for: . normal)
118+ recordButton. setTitle ( " Stop " , for: . normal)
119+ recordButton. backgroundColor = UIColor . systemRed
120+ recordButton. setTitleColor ( . white, for: . normal)
121+ recordButton. tintColor = . white
122+
123+ // Ensure image and text are properly aligned
124+ recordButton. semanticContentAttribute = . forceLeftToRight
125+ recordButton. imageEdgeInsets = UIEdgeInsets ( top: 0 , left: 0 , bottom: 0 , right: 4 )
126+ recordButton. titleEdgeInsets = UIEdgeInsets ( top: 0 , left: 4 , bottom: 0 , right: 0 )
127+ }
128+
82129 override func viewDidAppear( _ animated: Bool ) {
83130 super. viewDidAppear ( animated)
84131
@@ -120,14 +167,32 @@ class KeyboardViewController: KeyboardInputViewController {
120167 }
121168
122169 @objc private func recordButtonTapped( ) {
170+ // Add native iOS button press animation
171+ addButtonPressAnimation ( )
123172
124173 // Provide haptic feedback
125174 let impactFeedback = UIImpactFeedbackGenerator ( style: . medium)
126175 impactFeedback. impactOccurred ( )
127176
128- // Simply open the main app for recording
129- // No more complex coordination - just switch to main app
130- openMainAppForRecording ( )
177+ if coordinator. isRecording {
178+ // Stop recording
179+ coordinator. requestStopRecording ( )
180+ updateButtonAppearanceBasedOnState ( )
181+ } else {
182+ // Start recording by opening main app
183+ openMainAppForRecording ( )
184+ }
185+ }
186+
187+ private func addButtonPressAnimation( ) {
188+ // Native iOS button press animation - scale down then back up
189+ UIView . animate ( withDuration: 0.1 , delay: 0 , options: [ . curveEaseInOut] , animations: {
190+ self . recordButton. transform = CGAffineTransform ( scaleX: 0.95 , y: 0.95 )
191+ } ) { _ in
192+ UIView . animate ( withDuration: 0.1 , delay: 0 , options: [ . curveEaseInOut] , animations: {
193+ self . recordButton. transform = CGAffineTransform . identity
194+ } )
195+ }
131196 }
132197
133198 private func openMainAppForRecording( ) {
@@ -194,22 +259,47 @@ class KeyboardViewController: KeyboardInputViewController {
194259
195260 private func showUserMessage( ) {
196261 // Last resort: Update button to show user should open main app manually
197- recordButton. setTitle ( " 📱 Open VoiceInk " , for: . normal)
198- recordButton. backgroundColor = UIColor . systemBlue
262+ let appConfig = UIImage . SymbolConfiguration ( pointSize: 14 , weight: . semibold)
263+ let appImage = UIImage ( systemName: " app " , withConfiguration: appConfig)
264+
265+ recordButton. setImage ( appImage, for: . normal)
266+ recordButton. setTitle ( " Open VoiceInk " , for: . normal)
267+ recordButton. backgroundColor = UIColor . systemOrange
268+ recordButton. setTitleColor ( . white, for: . normal)
269+ recordButton. tintColor = . white
199270
200271 DispatchQueue . main. asyncAfter ( deadline: . now( ) + 2.0 ) {
201- self . recordButton. setTitle ( " 🎤 Record " , for: . normal)
202- self . recordButton. backgroundColor = UIColor . systemRed
272+ self . configureButtonForIdleState ( )
273+ }
274+ }
275+
276+ private func setupRecordingStatusMonitoring( ) {
277+ // Monitor recording status every 0.5 seconds
278+ recordingStatusTimer = Timer . scheduledTimer ( withTimeInterval: 0.5 , repeats: true ) { [ weak self] _ in
279+ self ? . updateButtonAppearanceBasedOnState ( )
203280 }
281+
282+ // Initial state update
283+ updateButtonAppearanceBasedOnState ( )
204284 }
205285
206286 private func updateButtonAppearanceBasedOnState( ) {
207- // Simplified: Always show "Record" since we just open the main app
208- recordButton. backgroundColor = UIColor . systemRed
209- recordButton. setTitle ( " 🎤 Record " , for: . normal)
287+ let isRecording = coordinator. isRecording
210288
211- // Ensure capsule shape is maintained
212- recordButton. layer. cornerRadius = recordButton. frame. height / 2
289+ DispatchQueue . main. async { [ weak self] in
290+ guard let self = self , let button = self . recordButton else { return }
291+
292+ if isRecording {
293+ // Configure for recording state
294+ self . configureButtonForRecordingState ( )
295+ } else {
296+ // Configure for idle state
297+ self . configureButtonForIdleState ( )
298+ }
299+
300+ // Ensure capsule shape is maintained
301+ button. layer. cornerRadius = button. frame. height / 2
302+ }
213303 }
214304
215305 override func textWillChange( _ textInput: UITextInput ? ) {
0 commit comments