summaryrefslogtreecommitdiff
path: root/src/ios.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/ios.m')
-rw-r--r--src/ios.m158
1 files changed, 144 insertions, 14 deletions
diff --git a/src/ios.m b/src/ios.m
index a1654df2..b82d54a7 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include "ios.h" 23#include "ios.h"
24#include "app.h" 24#include "app.h"
25#include "audio/player.h"
25#include "ui/command.h" 26#include "ui/command.h"
26#include "ui/window.h" 27#include "ui/window.h"
27 28
@@ -32,9 +33,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
32#include <SDL_syswm.h> 33#include <SDL_syswm.h>
33#include <SDL_timer.h> 34#include <SDL_timer.h>
34 35
35#import <UIKit/UIKit.h>
36#import <CoreHaptics/CoreHaptics.h>
37#import <AVFAudio/AVFAudio.h> 36#import <AVFAudio/AVFAudio.h>
37#import <CoreHaptics/CoreHaptics.h>
38#import <UIKit/UIKit.h>
39#import <MediaPlayer/MediaPlayer.h>
38 40
39static iBool isSystemDarkMode_ = iFalse; 41static iBool isSystemDarkMode_ = iFalse;
40static iBool isPhone_ = iFalse; 42static iBool isPhone_ = iFalse;
@@ -64,6 +66,7 @@ API_AVAILABLE(ios(13.0))
64@interface HapticState : NSObject 66@interface HapticState : NSObject
65@property (nonatomic, strong) CHHapticEngine *engine; 67@property (nonatomic, strong) CHHapticEngine *engine;
66@property (nonatomic, strong) NSDictionary *tapDef; 68@property (nonatomic, strong) NSDictionary *tapDef;
69@property (nonatomic, strong) NSDictionary *gentleTapDef;
67@end 70@end
68 71
69@implementation HapticState 72@implementation HapticState
@@ -105,26 +108,47 @@ API_AVAILABLE(ios(13.0))
105 CHHapticPatternKeyEvent: @{ 108 CHHapticPatternKeyEvent: @{
106 CHHapticPatternKeyEventType: CHHapticEventTypeHapticTransient, 109 CHHapticPatternKeyEventType: CHHapticEventTypeHapticTransient,
107 CHHapticPatternKeyTime: @0.0, 110 CHHapticPatternKeyTime: @0.0,
108 CHHapticPatternKeyEventDuration:@0.1 111 CHHapticPatternKeyEventDuration:@0.1,
112 CHHapticPatternKeyEventParameters: @[
113 @{
114 CHHapticPatternKeyParameterID: CHHapticEventParameterIDHapticIntensity,
115 CHHapticPatternKeyParameterValue: @1.0
116 }
117 ]
118 },
119 },
120 ]
121 };
122 self.gentleTapDef = @{
123 CHHapticPatternKeyPattern:
124 @[
125 @{
126 CHHapticPatternKeyEvent: @{
127 CHHapticPatternKeyEventType: CHHapticEventTypeHapticTransient,
128 CHHapticPatternKeyTime: @0.0,
129 CHHapticPatternKeyEventDuration:@0.1,
130 CHHapticPatternKeyEventParameters: @[
131 @{
132 CHHapticPatternKeyParameterID: CHHapticEventParameterIDHapticIntensity,
133 CHHapticPatternKeyParameterValue: @0.33
134 }
135 ]
109 }, 136 },
110 }, 137 },
111 ], 138 ]
112 }; 139 };
113} 140}
114 141
115-(void)playTapEffect { 142
143-(void)playHapticEffect:(NSDictionary *)def {
116 NSError *error = nil; 144 NSError *error = nil;
117 CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:self.tapDef 145 CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:def
118 error:&error]; 146 error:&error];
119 // TODO: Check the error. 147 // TODO: Check the error.
120 id<CHHapticPatternPlayer> player = [self.engine createPlayerWithPattern:pattern error:&error]; 148 id<CHHapticPatternPlayer> player = [self.engine createPlayerWithPattern:pattern error:&error];
121 // TODO: Check the error. 149 // TODO: Check the error.
122 [self.engine startWithCompletionHandler:^(NSError *err){ 150 [self.engine startWithCompletionHandler:^(NSError *err){
123 if (err == nil) { 151 if (err == nil) {
124 /* Just keep it running. */
125// [self.engine notifyWhenPlayersFinished:^(NSError * _Nullable error) {
126// return CHHapticEngineFinishedActionStopEngine;
127// }];
128 NSError *startError = nil; 152 NSError *startError = nil;
129 [player startAtTime:0.0 error:&startError]; 153 [player startAtTime:0.0 error:&startError];
130 } 154 }
@@ -181,11 +205,22 @@ static AppState *appState_;
181 205
182- (void)documentPicker:(UIDocumentPickerViewController *)controller 206- (void)documentPicker:(UIDocumentPickerViewController *)controller
183didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { 207didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
184 [self removeSavedFile]; 208 if (fileBeingSaved) {
209 [self removeSavedFile];
210 }
211 else {
212 /* A file is being opened. */
213 NSURL *url = [urls firstObject];
214 iString *path = localFilePathFromUrl_String(collectNewCStr_String([[url absoluteString]
215 UTF8String]));
216 postCommandf_App("file.open temp:1 path:%s", cstrCollect_String(path));
217 }
185} 218}
186 219
187- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { 220- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
188 [self removeSavedFile]; 221 if (fileBeingSaved) {
222 [self removeSavedFile];
223 }
189} 224}
190 225
191-(void)keyboardOnScreen:(NSNotification *)notification { 226-(void)keyboardOnScreen:(NSNotification *)notification {
@@ -230,6 +265,55 @@ void setupApplication_iOS(void) {
230 name:UIKeyboardWillHideNotification 265 name:UIKeyboardWillHideNotification
231 object:nil]; 266 object:nil];
232 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; 267 [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
268 /* Media player remote controls. */
269 MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter];
270 [[commandCenter pauseCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
271 iPlayer *player = active_Player();
272 if (player) {
273 setPaused_Player(player, iTrue);
274 return MPRemoteCommandHandlerStatusSuccess;
275 }
276 return MPRemoteCommandHandlerStatusCommandFailed;
277 }];
278 [[commandCenter playCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
279 iPlayer *player = active_Player();
280 if (player) {
281 if (isPaused_Player(player)) {
282 setPaused_Player(player, iFalse);
283 }
284 else {
285 start_Player(player);
286 }
287 return MPRemoteCommandHandlerStatusSuccess;
288 }
289 return MPRemoteCommandHandlerStatusCommandFailed;
290 }];
291 [[commandCenter stopCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
292 iPlayer *player = active_Player();
293 if (player) {
294 stop_Player(player);
295 return MPRemoteCommandHandlerStatusSuccess;
296 }
297 return MPRemoteCommandHandlerStatusCommandFailed;
298 }];
299 [[commandCenter togglePlayPauseCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) {
300 iPlayer *player = active_Player();
301 if (player) {
302 setPaused_Player(player, !isPaused_Player(player));
303 return MPRemoteCommandHandlerStatusSuccess;
304 }
305 return MPRemoteCommandHandlerStatusCommandFailed;
306 }];
307 [[commandCenter nextTrackCommand] setEnabled:NO];
308 [[commandCenter previousTrackCommand] setEnabled:NO];
309 [[commandCenter changeRepeatModeCommand] setEnabled:NO];
310 [[commandCenter changeShuffleModeCommand] setEnabled:NO];
311 [[commandCenter changePlaybackRateCommand] setEnabled:NO];
312 [[commandCenter seekForwardCommand] setEnabled:NO];
313 [[commandCenter seekBackwardCommand] setEnabled:NO];
314 [[commandCenter skipForwardCommand] setEnabled:NO];
315 [[commandCenter skipBackwardCommand] setEnabled:NO];
316 [[commandCenter changePlaybackPositionCommand] setEnabled:NO];
233} 317}
234 318
235static iBool isDarkMode_(iWindow *window) { 319static iBool isDarkMode_(iWindow *window) {
@@ -265,7 +349,7 @@ iBool isPhone_iOS(void) {
265} 349}
266 350
267int displayRefreshRate_iOS(void) { 351int displayRefreshRate_iOS(void) {
268 return uiWindow_(get_Window()).screen.maximumFramesPerSecond; 352 return (int) uiWindow_(get_Window()).screen.maximumFramesPerSecond;
269} 353}
270 354
271void setupWindow_iOS(iWindow *window) { 355void setupWindow_iOS(iWindow *window) {
@@ -279,7 +363,10 @@ void playHapticEffect_iOS(enum iHapticEffect effect) {
279 HapticState *hs = (HapticState *) appState_.haptic; 363 HapticState *hs = (HapticState *) appState_.haptic;
280 switch(effect) { 364 switch(effect) {
281 case tap_HapticEffect: 365 case tap_HapticEffect:
282 [hs playTapEffect]; 366 [hs playHapticEffect:hs.tapDef];
367 break;
368 case gentleTap_HapticEffect:
369 [hs playHapticEffect:hs.gentleTapDef];
283 break; 370 break;
284 } 371 }
285 } 372 }
@@ -324,6 +411,38 @@ iBool processEvent_iOS(const SDL_Event *ev) {
324 return iFalse; /* allow normal processing */ 411 return iFalse; /* allow normal processing */
325} 412}
326 413
414void updateNowPlayingInfo_iOS(void) {
415 const iPlayer *player = active_Player();
416 if (!player) {
417 clearNowPlayingInfo_iOS();
418 return;
419 }
420 NSMutableDictionary<NSString *, id> *info = [[NSMutableDictionary<NSString *, id> alloc] init];
421 [info setObject:[NSNumber numberWithDouble:time_Player(player)]
422 forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
423 [info setObject:[NSNumber numberWithInt:MPNowPlayingInfoMediaTypeAudio]
424 forKey:MPNowPlayingInfoPropertyMediaType];
425 [info setObject:[NSNumber numberWithDouble:duration_Player(player)]
426 forKey:MPMediaItemPropertyPlaybackDuration];
427 const iString *title = tag_Player(player, title_PlayerTag);
428 const iString *artist = tag_Player(player, artist_PlayerTag);
429 if (isEmpty_String(title)) {
430 title = collectNewCStr_String("Audio"); /* TODO: Use link label or URL file name */
431 }
432 if (isEmpty_String(artist)) {
433 artist = collectNewCStr_String("Lagrange"); /* TODO: Use domain or base URL */
434 }
435 [info setObject:[NSString stringWithUTF8String:cstr_String(title)]
436 forKey:MPMediaItemPropertyTitle];
437 [info setObject:[NSString stringWithUTF8String:cstr_String(artist)]
438 forKey:MPMediaItemPropertyArtist];
439 [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:info];
440}
441
442void clearNowPlayingInfo_iOS(void) {
443 [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil];
444}
445
327void exportDownloadedFile_iOS(const iString *path) { 446void exportDownloadedFile_iOS(const iString *path) {
328 NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path) 447 NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path)
329 encoding:NSUTF8StringEncoding]]; 448 encoding:NSUTF8StringEncoding]];
@@ -335,6 +454,17 @@ void exportDownloadedFile_iOS(const iString *path) {
335 [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; 454 [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil];
336} 455}
337 456
457void pickFileForOpening_iOS(void) {
458 UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc]
459 initWithDocumentTypes:@[@"fi.skyjake.lagrange.gemini",
460 @"public.text",
461 @"public.image",
462 @"public.audio"]
463 inMode:UIDocumentPickerModeImport];
464 picker.delegate = appState_;
465 [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil];
466}
467
338/*----------------------------------------------------------------------------------------------*/ 468/*----------------------------------------------------------------------------------------------*/
339 469
340enum iAVFAudioPlayerState { 470enum iAVFAudioPlayerState {