diff --git a/BeMyEyes.xcodeproj/project.pbxproj b/BeMyEyes.xcodeproj/project.pbxproj index 38b7801..249e379 100644 --- a/BeMyEyes.xcodeproj/project.pbxproj +++ b/BeMyEyes.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 4E8E21F519E8C03C00316AD8 /* IntroVideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8E21F219E8C03C00316AD8 /* IntroVideoViewController.swift */; }; 4E8E21F619E8C03C00316AD8 /* OnboardingVideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8E21F319E8C03C00316AD8 /* OnboardingVideoViewController.swift */; }; 4E8E21F719E8C03C00316AD8 /* VideoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E8E21F419E8C03C00316AD8 /* VideoViewController.swift */; }; + 4EB3A9A41A0E6D4F0044D4B9 /* BMEOpenTokVideoCapture.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EB3A9A31A0E6D4F0044D4B9 /* BMEOpenTokVideoCapture.m */; }; 4EB46C4419F080FE008405B8 /* BMEAccessLocalization.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4EB46C4619F080FE008405B8 /* BMEAccessLocalization.strings */; }; 4EB6A1E219E5725F00C72A02 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EB6A1E119E5725F00C72A02 /* Button.swift */; }; 4EB6AD1219E1F47A00141615 /* BMETaskTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EB6AD1119E1F47A00141615 /* BMETaskTableViewCell.m */; }; @@ -200,6 +201,8 @@ 4E8E21F219E8C03C00316AD8 /* IntroVideoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntroVideoViewController.swift; sourceTree = ""; }; 4E8E21F319E8C03C00316AD8 /* OnboardingVideoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingVideoViewController.swift; sourceTree = ""; }; 4E8E21F419E8C03C00316AD8 /* VideoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoViewController.swift; sourceTree = ""; }; + 4EB3A9A21A0E6D4F0044D4B9 /* BMEOpenTokVideoCapture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BMEOpenTokVideoCapture.h; sourceTree = ""; }; + 4EB3A9A31A0E6D4F0044D4B9 /* BMEOpenTokVideoCapture.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BMEOpenTokVideoCapture.m; sourceTree = ""; }; 4EB46C4519F080FE008405B8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/BMEAccessLocalization.strings; sourceTree = ""; }; 4EB46C4719F08102008405B8 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/BMEAccessLocalization.strings; sourceTree = ""; }; 4EB6A1E119E5725F00C72A02 /* Button.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; @@ -670,6 +673,8 @@ 4E6544861A0136F700B71C1C /* BMEScrollViewTextFieldHelper.h */, 4E6544871A0136F700B71C1C /* BMEScrollViewTextFieldHelper.m */, 4E6AE89D1A01927F0019247A /* FacebookHelper.swift */, + 4EB3A9A21A0E6D4F0044D4B9 /* BMEOpenTokVideoCapture.h */, + 4EB3A9A31A0E6D4F0044D4B9 /* BMEOpenTokVideoCapture.m */, ); path = Library; sourceTree = ""; @@ -998,6 +1003,7 @@ 4E3ACE1819FFEF4F002548C8 /* PostCallViewController.swift in Sources */, 72DA826818D45B52001F3312 /* BMEHelperMainViewController.m in Sources */, 7236E04A18B8DD8E00F622AA /* BMESignUpViewController.m in Sources */, + 4EB3A9A41A0E6D4F0044D4B9 /* BMEOpenTokVideoCapture.m in Sources */, 7296569918CC8C3E00E1A9EE /* BMEMainViewController.m in Sources */, 4EB6AD1719E1F48C00141615 /* GradientView.swift in Sources */, 72A7676C19029F34003121C4 /* NSDate+BMESnoozeRelativeDate.m in Sources */, diff --git a/BeMyEyes/Base.lproj/Main.storyboard b/BeMyEyes/Base.lproj/Main.storyboard index f2827aa..85abf28 100644 --- a/BeMyEyes/Base.lproj/Main.storyboard +++ b/BeMyEyes/Base.lproj/Main.storyboard @@ -1,5 +1,5 @@ - + @@ -349,7 +349,7 @@ - + diff --git a/BeMyEyes/BeMyEyes-Info.plist b/BeMyEyes/BeMyEyes-Info.plist index 02d472a..f93c25d 100644 --- a/BeMyEyes/BeMyEyes-Info.plist +++ b/BeMyEyes/BeMyEyes-Info.plist @@ -30,7 +30,7 @@ CFBundleVersion - 35 + 36 FacebookAppID 771890076161460 FacebookDisplayName @@ -39,6 +39,7 @@ UIBackgroundModes + audio remote-notification UILaunchStoryboardName @@ -57,6 +58,13 @@ UIInterfaceOrientationPortrait + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeRight + UIViewControllerBasedStatusBarAppearance diff --git a/BeMyEyes/Source/Application/BMEAccessControlHandler.h b/BeMyEyes/Source/Application/BMEAccessControlHandler.h index f528c3a..aa38ba0 100644 --- a/BeMyEyes/Source/Application/BMEAccessControlHandler.h +++ b/BeMyEyes/Source/Application/BMEAccessControlHandler.h @@ -10,13 +10,13 @@ @interface BMEAccessControlHandler : NSObject -+ (void)requireNotificationsEnabled:(void(^)(BOOL isEnabled))completion; -+ (void)hasNotificationsEnabled:(void(^)(BOOL isEnabled))completion; ++ (void)requireNotificationsEnabled:(void(^)(BOOL isEnabled, BOOL validToken))completion; ++ (void)hasNotificationsEnabled:(void(^)(BOOL isEnabled, BOOL validToken))completion; + (void)requireMicrophoneEnabled:(void(^)(BOOL isEnabled))completion; + (void)hasMicrophoneEnabled:(void(^)(BOOL isEnabled))completion; + (void)requireCameraEnabled:(void(^)(BOOL isEnabled))completion; + (void)hasVideoEnabled:(void(^)(BOOL isEnabled))completion; -+ (void)enabledForRole:(BMERole)role completion:(void(^)(BOOL isEnabled))completion; ++ (void)enabledForRole:(BMERole)role completion:(void(^)(BOOL isEnabled, BOOL validToken))completion; @end diff --git a/BeMyEyes/Source/Application/BMEAccessControlHandler.m b/BeMyEyes/Source/Application/BMEAccessControlHandler.m index b586515..1ac41a1 100644 --- a/BeMyEyes/Source/Application/BMEAccessControlHandler.m +++ b/BeMyEyes/Source/Application/BMEAccessControlHandler.m @@ -12,7 +12,7 @@ @interface BMEAccessControlHandler() -@property (strong, nonatomic) void (^notificationsCompletion)(BOOL); +@property (strong, nonatomic) void (^notificationsCompletion)(BOOL isEnabled, BOOL validToken); @end @@ -50,13 +50,13 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N { if ([keyPath isEqualToString:NSStringFromSelector(@selector(deviceToken))] && object == [GVUserDefaults standardUserDefaults]) { - [BMEAccessControlHandler hasNotificationsEnabled:^(BOOL isEnabled) { + [BMEAccessControlHandler hasNotificationsEnabled:^(BOOL isEnabled, BOOL validToken) { if (!isEnabled) { [BMEAccessControlHandler showNotificationsAlert]; } if (self.notificationsCompletion) { - self.notificationsCompletion(isEnabled); - self.notificationsCompletion = nil; + self.notificationsCompletion(isEnabled, validToken); + _notificationsCompletion = nil; } }]; return; @@ -68,10 +68,10 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N #pragma mark - #pragma mark Public Methods -+ (void)enabledForRole:(BMERole)role completion:(void (^)(BOOL))completion ++ (void)enabledForRole:(BMERole)role completion:(void (^)(BOOL isEnabled, BOOL validToken))completion { #if TARGET_IPHONE_SIMULATOR - completion(YES); + completion(YES, YES); return; #endif @@ -81,11 +81,11 @@ + (void)enabledForRole:(BMERole)role completion:(void (^)(BOOL))completion // Ask for microphone + video [self hasMicrophoneEnabled:^(BOOL isEnabled) { if (!isEnabled) { - completion(NO); + completion(NO, YES); return; } [self hasVideoEnabled:^(BOOL isEnabled) { - completion(isEnabled); + completion(isEnabled, YES); }]; }]; } @@ -93,18 +93,18 @@ + (void)enabledForRole:(BMERole)role completion:(void (^)(BOOL))completion case BMERoleHelper: { // Ask for notifications + microphone + video - [self hasNotificationsEnabled:^(BOOL isEnabled) { + [self hasNotificationsEnabled:^(BOOL isEnabled, BOOL validToken) { if (!isEnabled) { - completion(NO); + completion(NO, validToken); return; } [self hasMicrophoneEnabled:^(BOOL isEnabled) { if (!isEnabled) { - completion(NO); + completion(NO, validToken); return; } [self hasVideoEnabled:^(BOOL isEnabled) { - completion(isEnabled); + completion(isEnabled, validToken); }]; }]; }]; @@ -119,13 +119,13 @@ + (void)enabledForRole:(BMERole)role completion:(void (^)(BOOL))completion // Remote notifications -+ (void)requireNotificationsEnabled:(void(^)(BOOL isEnabled))completion { ++ (void)requireNotificationsEnabled:(void(^)(BOOL isEnabled, BOOL validToken))completion { // Store completion block [self sharedInstance].notificationsCompletion = completion; [self registerForRemoteNotifications]; } -+ (void)hasNotificationsEnabled:(void(^)(BOOL isEnabled))completion { ++ (void)hasNotificationsEnabled:(void(^)(BOOL isUserEnabled, BOOL validToken))completion { // System – require badge and alert BOOL isUserEnabled = NO; if ([[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) { @@ -149,8 +149,7 @@ + (void)hasNotificationsEnabled:(void(^)(BOOL isEnabled))completion { BOOL isTemporary = [deviceToken rangeOfString:@"bmetemp"].location == 0; // [GVUserDefaults standardUserDefaults].isTemporaryDeviceToken might not have been set yet BOOL hasValidToken = hasNotificationsToken && !isTemporary; // Combined - BOOL isEnabled = hasValidToken && isUserEnabled; - completion(isEnabled); + completion(isUserEnabled, hasValidToken); } diff --git a/BeMyEyes/Source/Application/BMEAppDelegate.m b/BeMyEyes/Source/Application/BMEAppDelegate.m index d6f8066..e478b2a 100644 --- a/BeMyEyes/Source/Application/BMEAppDelegate.m +++ b/BeMyEyes/Source/Application/BMEAppDelegate.m @@ -75,9 +75,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( [NewRelicAgent startWithApplicationToken:@"AA9b45f5411736426b5fac31cce185b50d173d99ea"]; [self configureRESTClient]; - dispatch_async(dispatch_get_main_queue(), ^{ - [self checkIfLoggedIn]; - }); + [self checkIfLoggedIn]; if (self.isLaunchedWithShortID) { [self performSelector:@selector(didAnswerCallWithShortId:) withObject:shortIdInLaunchOptions afterDelay:0.0f]; @@ -269,6 +267,7 @@ - (void)checkIfLoggedIn { } }]; } else { + [self showFrontPage]; NSLog(@"Device token: %@", [GVUserDefaults standardUserDefaults].deviceToken); NSLog(@"Is valid: %@", [BMEClient sharedClient].isTokenValid ? @"YES" : @"NO"); } @@ -405,25 +404,34 @@ - (void)didLogIn:(NSNotification *)notification { - (void)showFrontPage { - BMETopNavigationController *initialViewController; - if ([self.window.rootViewController isKindOfClass:[BMETopNavigationController class]]) { - initialViewController = (BMETopNavigationController *)self.window.rootViewController; + if ([self.window.rootViewController.restorationIdentifier isEqualToString:BMEFrontPageNavigationControllerIdentifier]) { + BMETopNavigationController *initialViewController = (BMETopNavigationController *)self.window.rootViewController; if (initialViewController.presentedViewController) { [initialViewController dismissViewControllerAnimated:YES completion:nil]; } [initialViewController popToRootViewControllerAnimated:YES]; - } else { - initialViewController = (BMETopNavigationController *)[self.window.rootViewController.storyboard instantiateInitialViewController]; - self.window.rootViewController = initialViewController; + return; } + [self setTopViewController:[self.storyboard instantiateViewControllerWithIdentifier:BMEFrontPageNavigationControllerIdentifier]]; } - (void)showLoggedInMainView { - if ([self.window.rootViewController.presentedViewController.restorationIdentifier isEqualToString:BMEMainNavigationControllerIdentifier]) { + if ([self.window.rootViewController.restorationIdentifier isEqualToString:BMEMainNavigationControllerIdentifier]) { return; } - UIViewController *mainController = [self.window.rootViewController.storyboard instantiateViewControllerWithIdentifier:BMEMainNavigationControllerIdentifier]; - [self.window.rootViewController presentViewController:mainController animated:YES completion:nil]; + [self setTopViewController:[self.storyboard instantiateViewControllerWithIdentifier:BMEMainNavigationControllerIdentifier]]; +} + +- (void)setTopViewController:(UIViewController *)viewController { + for (UIView *view in self.window.subviews) { // Clear out, since presentedViewController might not be removed when settings window.rootViewController + [view removeFromSuperview]; + } + self.window.rootViewController = viewController; +} + +- (UIStoryboard *)storyboard { + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; + return storyboard; } @end diff --git a/BeMyEyes/Source/Controllers/BMEAccessViewController.m b/BeMyEyes/Source/Controllers/BMEAccessViewController.m index 939bf45..75aa9bb 100644 --- a/BeMyEyes/Source/Controllers/BMEAccessViewController.m +++ b/BeMyEyes/Source/Controllers/BMEAccessViewController.m @@ -69,14 +69,14 @@ - (void)dealloc - (void)checkAccess { - [BMEAccessControlHandler enabledForRole:self.role completion:^(BOOL isEnabled) { - if (isEnabled) { + [BMEAccessControlHandler enabledForRole:self.role completion:^(BOOL isEnabled, BOOL validToken) { + if (isEnabled && validToken) { [self dismissViewControllerAnimated:YES completion:nil]; return; } if (_accessNotificationsView) { - [BMEAccessControlHandler hasNotificationsEnabled:^(BOOL isEnabled) { + [BMEAccessControlHandler hasNotificationsEnabled:^(BOOL isEnabled, BOOL validToken) { _accessNotificationsView.selected = isEnabled; }]; } @@ -87,7 +87,7 @@ - (void)checkAccess } if (_accessCameraView) { [BMEAccessControlHandler hasVideoEnabled:^(BOOL isEnabled) { - self.accessCameraView.selected = isEnabled; + _accessCameraView.selected = isEnabled; }]; } }]; @@ -196,8 +196,8 @@ - (void)touchUpInsideNotificationsView if (self.accessNotificationsView.selected) { return; } - [BMEAccessControlHandler requireNotificationsEnabled:^(BOOL isEnabled) { - self.accessNotificationsView.selected = isEnabled; + [BMEAccessControlHandler requireNotificationsEnabled:^(BOOL isUserEnabled, BOOL validToken) { + self.accessNotificationsView.selected = isUserEnabled; [self checkAccess]; }]; } diff --git a/BeMyEyes/Source/Controllers/BMEBaseViewController.m b/BeMyEyes/Source/Controllers/BMEBaseViewController.m index 9b47fac..1b042d3 100644 --- a/BeMyEyes/Source/Controllers/BMEBaseViewController.m +++ b/BeMyEyes/Source/Controllers/BMEBaseViewController.m @@ -18,11 +18,8 @@ - (BOOL)shouldAutorotate { } - (NSUInteger)supportedInterfaceOrientations { - return UIInterfaceOrientationMaskPortrait; -} - -- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation { - return UIInterfaceOrientationPortrait; + BOOL isIpad = [UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad; + return isIpad ? UIInterfaceOrientationMaskAll : UIInterfaceOrientationMaskPortrait; } - (UIStatusBarStyle)preferredStatusBarStyle diff --git a/BeMyEyes/Source/Controllers/BMECallViewController.m b/BeMyEyes/Source/Controllers/BMECallViewController.m index 821860d..8772efa 100644 --- a/BeMyEyes/Source/Controllers/BMECallViewController.m +++ b/BeMyEyes/Source/Controllers/BMECallViewController.m @@ -16,6 +16,7 @@ #import "BMERequest.h" #import "BMESpeaker.h" #import "BMECallAudioPlayer.h" +#import "BMEOpenTokVideoCapture.h" static NSString *BMECallPostSegue = @"PostCall"; @@ -222,6 +223,7 @@ - (void)publish { self.publisher.cameraPosition = AVCaptureDevicePositionBack; self.publisher.publishAudio = YES; self.publisher.publishVideo = [self isUserBlind]; + self.publisher.videoCapture = [BMEOpenTokVideoCapture new]; OTError *error = nil; [self.session publish:self.publisher error:&error]; diff --git a/BeMyEyes/Source/Controllers/BMEHelperMainViewController.m b/BeMyEyes/Source/Controllers/BMEHelperMainViewController.m index 8d3d666..b3b72e5 100644 --- a/BeMyEyes/Source/Controllers/BMEHelperMainViewController.m +++ b/BeMyEyes/Source/Controllers/BMEHelperMainViewController.m @@ -103,9 +103,10 @@ - (void)viewDidLoad { [MKLocalization registerForLocalization:self]; - self.tableView.estimatedRowHeight = self.tableView.rowHeight; - self.tableView.rowHeight = UITableViewAutomaticDimension; - + if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { + self.tableView.estimatedRowHeight = self.tableView.rowHeight; + self.tableView.rowHeight = UITableViewAutomaticDimension; + } self.pointsHelpedPersonsLabel.colors = self.pointsTotalLabel.colors = @{ @(0.0f) : [UIColor lightTextColor], diff --git a/BeMyEyes/Source/Controllers/BMELoginViewController.m b/BeMyEyes/Source/Controllers/BMELoginViewController.m index 34dbe00..006422c 100644 --- a/BeMyEyes/Source/Controllers/BMELoginViewController.m +++ b/BeMyEyes/Source/Controllers/BMELoginViewController.m @@ -43,10 +43,7 @@ - (void)viewDidLoad { } - (void)dealloc { - if (_loggingInOverlayView) { - [_loggingInOverlayView hide:YES]; - } - + [self hideLoggingInOverlay]; _loggingInOverlayView = nil; } @@ -84,6 +81,7 @@ - (IBAction)loginButtonPressed:(id)sender { - (void)performLoginUsingFacebook:(BOOL)useFacebook { [self dismissKeyboard]; + [self showLoggingInOverlay]; NSString *deviceToken = [GVUserDefaults standardUserDefaults].deviceToken; BOOL isTemporaryDeviceToken = NO; @@ -104,6 +102,7 @@ - (void)performLoginUsingFacebook:(BOOL)useFacebook { [self performLoginWithEmail]; } } else { + [self hideLoggingInOverlay]; NSString *title = nil; NSString *message = nil; NSString *cancelButton = nil; @@ -140,14 +139,14 @@ - (void)performLoginWithEmail { } - (void)performLoginWithFacebook { - self.loggingInOverlayView = [self addLoggingInOverlay]; + [self showLoggingInOverlay]; [[BMEClient sharedClient] loginUsingFacebookWithDeviceToken:[GVUserDefaults standardUserDefaults].deviceToken success:^(BMEToken *token) { - [self.loggingInOverlayView hide:YES]; + [self hideLoggingInOverlay]; [self didLogin]; } loginFailure:^(NSError *error) { - [self.loggingInOverlayView hide:YES]; + [self hideLoggingInOverlay]; if ([error code] == BMEClientErrorUserFacebookUserNotFound) { NSString *title = MKLocalizedFromTable(BME_LOGIN_ALERT_FACEBOOK_USER_NOT_REGISTERED_TITLE, BMELoginLocalizationTable); @@ -165,7 +164,7 @@ - (void)performLoginWithFacebook { NSLog(@"Could not log in with Facebook: %@", error); } accountFailure:^(NSError *error) { - [self.loggingInOverlayView hide:YES]; + [self hideLoggingInOverlay]; if ([error code] == ACErrorAccountNotFound) { NSString *title = MKLocalizedFromTable(BME_LOGIN_ALERT_FACEBOOK_ACCOUNT_NOT_FOUND_TITLE, BMELoginLocalizationTable); @@ -186,14 +185,14 @@ - (void)performLoginWithFacebook { } - (void)loginWithEmail:(NSString *)email password:(NSString *)password { - self.loggingInOverlayView = [self addLoggingInOverlay]; + [self showLoggingInOverlay]; [[BMEClient sharedClient] loginWithEmail:email password:password deviceToken:[GVUserDefaults standardUserDefaults].deviceToken success:^(BMEToken *token) { - [self.loggingInOverlayView hide:YES]; + [self hideLoggingInOverlay]; [self didLogin]; } failure:^(NSError *error) { - [self.loggingInOverlayView hide:YES]; + [self hideLoggingInOverlay]; if ([error code] == BMEClientErrorUserIncorrectCredentials) { NSString *title = MKLocalizedFromTable(BME_LOGIN_ALERT_INCORRECT_CREDENTIALS_TITLE, BMELoginLocalizationTable); @@ -221,6 +220,21 @@ - (void)dismissKeyboard { } } +- (void)showLoggingInOverlay { + if (_loggingInOverlayView) { + return; + } + self.loggingInOverlayView = [self addLoggingInOverlay]; +} + +- (void)hideLoggingInOverlay { + if (!_loggingInOverlayView) { + return; + } + [self.loggingInOverlayView hide:YES]; + _loggingInOverlayView = nil; +} + - (MRProgressOverlayView *)addLoggingInOverlay { MRProgressOverlayView *progressOverlayView = [MRProgressOverlayView showOverlayAddedTo:self.view.window animated:YES]; progressOverlayView.mode = MRProgressOverlayViewModeIndeterminate; diff --git a/BeMyEyes/Source/Controllers/BMEMainViewController.m b/BeMyEyes/Source/Controllers/BMEMainViewController.m index 2ab9bf2..cc967f0 100644 --- a/BeMyEyes/Source/Controllers/BMEMainViewController.m +++ b/BeMyEyes/Source/Controllers/BMEMainViewController.m @@ -64,15 +64,19 @@ - (void)handleAppBecameActive - (void)check { [self askForMoreLanguagesIfNecessary]; - [self askForAccessIfNecessary]; if ([BMEClient sharedClient].currentUser.role == BMERoleHelper) { - [BMEAccessControlHandler hasNotificationsEnabled:^(BOOL isEnabled) { - if (isEnabled) { + [BMEAccessControlHandler hasNotificationsEnabled:^(BOOL isUserEnabled, BOOL validToken) { + if (isUserEnabled) { // If user is helper and has notifications enabled, to a request to register for a possibly new device token - [BMEAccessControlHandler requireNotificationsEnabled:^(BOOL isEnabled) { + [BMEAccessControlHandler requireNotificationsEnabled:^(BOOL isUserEnabled, BOOL validToken) { + [self askForAccessIfNecessary]; }]; + } else { + [self askForAccessIfNecessary]; } }]; + } else { + [self askForAccessIfNecessary]; } } @@ -158,10 +162,20 @@ - (void)askForMoreLanguagesIfNecessary { - (void)askForAccessIfNecessary { - [BMEAccessControlHandler enabledForRole:[BMEClient sharedClient].currentUser.role completion:^(BOOL isEnabled) { + [BMEAccessControlHandler enabledForRole:[BMEClient sharedClient].currentUser.role completion:^(BOOL isEnabled, BOOL validToken) { if (isEnabled) { return; } + if (!validToken) { + // User has enable push, but something else went wrong + NSString *title = MKLocalizedFromTable(BME_MAIN_ALERT_NOTIFICATIONS_ERROR_TITLE, BMEMainLocalizationTable); + NSString *message = MKLocalizedFromTable(BME_MAIN_ALERT_NOTIFICATIONS_ERROR_MESSAGE, BMEMainLocalizationTable); + NSString *cancelButton = MKLocalizedFromTable(BME_MAIN_ALERT_CANCEL, BMEMainLocalizationTable); + PSPDFAlertView *alertView = [[PSPDFAlertView alloc] initWithTitle:title message:message]; + [alertView setCancelButtonWithTitle:cancelButton block:nil]; + [alertView show]; + return; + } [self performSegueWithIdentifier:BMEAccessViewSegue sender:self]; }]; } diff --git a/BeMyEyes/Source/Controllers/BaseViewController.swift b/BeMyEyes/Source/Controllers/BaseViewController.swift index c4d736b..2ca1f69 100644 --- a/BeMyEyes/Source/Controllers/BaseViewController.swift +++ b/BeMyEyes/Source/Controllers/BaseViewController.swift @@ -19,11 +19,8 @@ class BaseViewController: UIViewController { } override func supportedInterfaceOrientations() -> Int { - return Int(UIInterfaceOrientationMask.Portrait.rawValue) - } - - override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation { - return .Portrait + let isIpad = UIDevice.currentDevice().userInterfaceIdiom == .Pad; + return Int((isIpad ? UIInterfaceOrientationMask.All : .Portrait).rawValue); } override func preferredStatusBarStyle() -> UIStatusBarStyle { diff --git a/BeMyEyes/Source/Controllers/IntroVideoViewController.swift b/BeMyEyes/Source/Controllers/IntroVideoViewController.swift index 5caa18d..6d52c65 100644 --- a/BeMyEyes/Source/Controllers/IntroVideoViewController.swift +++ b/BeMyEyes/Source/Controllers/IntroVideoViewController.swift @@ -12,7 +12,8 @@ class IntroVideoViewController: VideoViewController { override func viewDidLoad() { super.viewDidLoad() - moviePlayerController.scalingMode = .AspectFill + let isIpad = UIDevice.currentDevice().userInterfaceIdiom == .Pad + moviePlayerController.scalingMode = isIpad ? .AspectFit : .AspectFill if let videoPath = NSBundle.mainBundle().pathForResource("intro", ofType: "mp4") { let videoUrl = NSURL(fileURLWithPath: videoPath) moviePlayerController.contentURL = videoUrl diff --git a/BeMyEyes/Source/Library/BMEOpenTokVideoCapture.h b/BeMyEyes/Source/Library/BMEOpenTokVideoCapture.h new file mode 100644 index 0000000..be8cece --- /dev/null +++ b/BeMyEyes/Source/Library/BMEOpenTokVideoCapture.h @@ -0,0 +1,36 @@ +// +// BMEOpenTokVideo.h +// BeMyEyes +// +// Created by Tobias Due Munk on 08/11/14. +// Copyright (c) 2014 Be My Eyes. All rights reserved. +// + +#import +#import +#import + +@protocol OTVideoCapture; + +@interface BMEOpenTokVideoCapture : NSObject + +{ +@protected + dispatch_queue_t _capture_queue; +} + +@property (nonatomic, retain) AVCaptureSession *captureSession; +@property (nonatomic, retain) AVCaptureVideoDataOutput *videoOutput; +@property (nonatomic, retain) AVCaptureDeviceInput *videoInput; + +@property (nonatomic, assign) NSString* captureSessionPreset; +@property (readonly) NSArray* availableCaptureSessionPresets; + +@property (nonatomic, assign) double activeFrameRate; +- (BOOL)isAvailableActiveFrameRate:(double)frameRate; + +@property (nonatomic, assign) AVCaptureDevicePosition cameraPosition; +@property (readonly) NSArray* availableCameraPositions; +- (BOOL)toggleCameraPosition; + +@end diff --git a/BeMyEyes/Source/Library/BMEOpenTokVideoCapture.m b/BeMyEyes/Source/Library/BMEOpenTokVideoCapture.m new file mode 100644 index 0000000..bd8906b --- /dev/null +++ b/BeMyEyes/Source/Library/BMEOpenTokVideoCapture.m @@ -0,0 +1,644 @@ +// +// BMEOpenTokVideo.m +// BeMyEyes +// +// Created by Tobias Due Munk on 08/11/14. +// Copyright (c) 2014 Be My Eyes. All rights reserved. +// + +#import "BMEOpenTokVideoCapture.h" + +#import +#import +#import +#import + +@implementation BMEOpenTokVideoCapture { + OTVideoFrame* _videoFrame; + + uint32_t _captureWidth; + uint32_t _captureHeight; + NSString* _capturePreset; + + AVCaptureSession *_captureSession; + AVCaptureDeviceInput *_videoInput; + AVCaptureVideoDataOutput *_videoOutput; + + BOOL _capturing; + +} + +@synthesize captureSession = _captureSession; +@synthesize videoInput = _videoInput, videoOutput = _videoOutput; +@synthesize videoCaptureConsumer = _videoCaptureConsumer; + +#define OT_VIDEO_CAPTURE_IOS_DEFAULT_INITIAL_FRAMERATE 20 + +-(id)init { + self = [super init]; + if (self) { + _capturePreset = AVCaptureSessionPreset640x480; + [[self class] dimensionsForCapturePreset:_capturePreset + width:&_captureWidth + height:&_captureHeight]; + _capture_queue = dispatch_queue_create("com.tokbox.OTVideoCapture", + DISPATCH_QUEUE_SERIAL); + _videoFrame = [[OTVideoFrame alloc] initWithFormat: + [OTVideoFormat videoFormatNV12WithWidth:_captureWidth + height:_captureHeight]]; + } + return self; +} + +- (int32_t)captureSettings:(OTVideoFormat*)videoFormat { + videoFormat.pixelFormat = OTPixelFormatNV12; + videoFormat.imageWidth = _captureWidth; + videoFormat.imageHeight = _captureHeight; + return 0; +} + +- (void)dealloc { + [self stopCapture]; + [self releaseCapture]; + + if (_capture_queue) { + _capture_queue = nil; + } +} + +- (AVCaptureDevice *) cameraWithPosition:(AVCaptureDevicePosition) position { + NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + for (AVCaptureDevice *device in devices) { + if ([device position] == position) { + return device; + } + } + return nil; +} + +- (AVCaptureDevice *) frontFacingCamera { + return [self cameraWithPosition:AVCaptureDevicePositionFront]; +} + +- (AVCaptureDevice *) backFacingCamera { + return [self cameraWithPosition:AVCaptureDevicePositionBack]; +} + +- (BOOL) hasMultipleCameras { + return [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] count] > 1; +} + +- (BOOL) hasTorch { + return [[[self videoInput] device] hasTorch]; +} + +- (AVCaptureTorchMode) torchMode { + return [[[self videoInput] device] torchMode]; +} + +- (void) setTorchMode:(AVCaptureTorchMode) torchMode { + + AVCaptureDevice *device = [[self videoInput] device]; + if ([device isTorchModeSupported:torchMode] && [device torchMode] != torchMode) { + NSError *error; + if ([device lockForConfiguration:&error]) { + [device setTorchMode:torchMode]; + [device unlockForConfiguration]; + } else { + //Handle Error + } + } +} + +- (double) maxSupportedFrameRate { + AVFrameRateRange* firstRange = + [_videoInput.device.activeFormat.videoSupportedFrameRateRanges + objectAtIndex:0]; + + CMTime bestDuration = firstRange.minFrameDuration; + double bestFrameRate = bestDuration.timescale / bestDuration.value; + CMTime currentDuration; + double currentFrameRate; + for (AVFrameRateRange* range in + _videoInput.device.activeFormat.videoSupportedFrameRateRanges) + { + currentDuration = range.minFrameDuration; + currentFrameRate = currentDuration.timescale / currentDuration.value; + if (currentFrameRate > bestFrameRate) { + bestFrameRate = currentFrameRate; + } + } + + return bestFrameRate; +} + +- (BOOL)isAvailableActiveFrameRate:(double)frameRate +{ + return (nil != [self frameRateRangeForFrameRate:frameRate]); +} + +- (double) activeFrameRate { + CMTime minFrameDuration = _videoInput.device.activeVideoMinFrameDuration; + double framesPerSecond = minFrameDuration.timescale / minFrameDuration.value; + + return framesPerSecond; +} + +- (AVFrameRateRange*)frameRateRangeForFrameRate:(double)frameRate { + for (AVFrameRateRange* range in + _videoInput.device.activeFormat.videoSupportedFrameRateRanges) + { + if (range.minFrameRate <= frameRate && frameRate <= range.maxFrameRate) + { + return range; + } + } + return nil; +} + +- (void)setActiveFrameRate:(double)frameRate { + + if (!_videoOutput || !_videoInput) { + return; + } + + AVFrameRateRange* frameRateRange = [self frameRateRangeForFrameRate:frameRate]; + if (nil == frameRateRange) { + NSLog(@"unsupported frameRate %f", frameRate); + return; + } + CMTime desiredMinFrameDuration = CMTimeMake(1, frameRate); + CMTime desiredMaxFrameDuration = CMTimeMake(1, frameRate); + + [_captureSession beginConfiguration]; + + NSError* error; + if ([_videoInput.device lockForConfiguration:&error]) { + [_videoInput.device setActiveVideoMinFrameDuration:desiredMinFrameDuration]; + [_videoInput.device setActiveVideoMaxFrameDuration:desiredMaxFrameDuration]; + [_videoInput.device unlockForConfiguration]; + } else { + NSLog(@"%@", error); + } + [_captureSession commitConfiguration]; +} + ++ (void)dimensionsForCapturePreset:(NSString*)preset + width:(uint32_t*)width + height:(uint32_t*)height +{ + if ([preset isEqualToString:AVCaptureSessionPreset352x288]) { + *width = 352; + *height = 288; + } else if ([preset isEqualToString:AVCaptureSessionPreset640x480]) { + *width = 640; + *height = 480; + } else if ([preset isEqualToString:AVCaptureSessionPreset1280x720]) { + *width = 1280; + *height = 720; + } else if ([preset isEqualToString:AVCaptureSessionPreset1920x1080]) { + *width = 1920; + *height = 1080; + } else if ([preset isEqualToString:AVCaptureSessionPresetPhoto]) { + // see AVCaptureSessionPresetLow + *width = 1920; + *height = 1080; + } else if ([preset isEqualToString:AVCaptureSessionPresetHigh]) { + // see AVCaptureSessionPresetLow + *width = 640; + *height = 480; + } else if ([preset isEqualToString:AVCaptureSessionPresetMedium]) { + // see AVCaptureSessionPresetLow + *width = 480; + *height = 360; + } else if ([preset isEqualToString:AVCaptureSessionPresetLow]) { + // WARNING: This is a guess. might be wrong for certain devices. + // We'll use updeateCaptureFormatWithWidth:height if actual output + // differs from expected value + *width = 192; + *height = 144; + } +} + ++ (NSSet *)keyPathsForValuesAffectingAvailableCaptureSessionPresets +{ + return [NSSet setWithObjects:@"captureSession", @"videoInput", nil]; +} + +- (NSArray *)availableCaptureSessionPresets +{ + NSArray *allSessionPresets = [NSArray arrayWithObjects: + AVCaptureSessionPreset352x288, + AVCaptureSessionPreset640x480, + AVCaptureSessionPreset1280x720, + AVCaptureSessionPreset1920x1080, + AVCaptureSessionPresetPhoto, + AVCaptureSessionPresetHigh, + AVCaptureSessionPresetMedium, + AVCaptureSessionPresetLow, + nil]; + + NSMutableArray *availableSessionPresets = + [NSMutableArray arrayWithCapacity:9]; + for (NSString *sessionPreset in allSessionPresets) { + if ([[self captureSession] canSetSessionPreset:sessionPreset]) + [availableSessionPresets addObject:sessionPreset]; + } + + return availableSessionPresets; +} + +- (void)updateCaptureFormatWithWidth:(int)width height:(int)height +{ + _captureWidth = width; + _captureHeight = height; + [_videoFrame setFormat:[OTVideoFormat + videoFormatNV12WithWidth:_captureWidth + height:_captureHeight]]; + +} + +- (NSString*)captureSessionPreset { + return _captureSession.sessionPreset; +} + + +- (void) setCaptureSessionPreset:(NSString*)preset { + AVCaptureSession *session = [self captureSession]; + + if ([session canSetSessionPreset:preset] && + ![preset isEqualToString:session.sessionPreset]) { + + [_captureSession beginConfiguration]; + _captureSession.sessionPreset = preset; + _capturePreset = preset; + + [_videoOutput setVideoSettings: + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt: + kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange], + kCVPixelBufferPixelFormatTypeKey, + nil]]; + + [_captureSession commitConfiguration]; + } +} + +- (BOOL) toggleCameraPosition { + AVCaptureDevicePosition currentPosition = _videoInput.device.position; + if (AVCaptureDevicePositionBack == currentPosition) { + [self setCameraPosition:AVCaptureDevicePositionFront]; + } else if (AVCaptureDevicePositionFront == currentPosition) { + [self setCameraPosition:AVCaptureDevicePositionBack]; + } + + // TODO: check for success + return YES; +} + +- (NSArray*)availableCameraPositions { + NSArray* devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]; + NSMutableSet* result = [NSMutableSet setWithCapacity:devices.count]; + for (AVCaptureDevice* device in devices) { + [result addObject:[NSNumber numberWithInt:device.position]]; + } + return [result allObjects]; +} + +- (AVCaptureDevicePosition)cameraPosition { + return _videoInput.device.position; +} + +- (void)setCameraPosition:(AVCaptureDevicePosition) position { + BOOL success = NO; + + NSString* preset = self.captureSession.sessionPreset; + + if ([self hasMultipleCameras]) { + NSError *error; + AVCaptureDeviceInput *newVideoInput; + + if (position == AVCaptureDevicePositionBack) { + newVideoInput = [AVCaptureDeviceInput deviceInputWithDevice: + [self backFacingCamera] error:&error]; + [self setTorchMode:AVCaptureTorchModeOff]; + _videoOutput.alwaysDiscardsLateVideoFrames = YES; + } else if (position == AVCaptureDevicePositionFront) { + newVideoInput = [AVCaptureDeviceInput deviceInputWithDevice: + [self frontFacingCamera] error:&error]; + _videoOutput.alwaysDiscardsLateVideoFrames = YES; + } else { + goto bail; + } + + AVCaptureSession *session = [self captureSession]; + if (newVideoInput != nil) { + [session beginConfiguration]; + [session removeInput:_videoInput]; + if ([session canAddInput:newVideoInput]) { + [session addInput:newVideoInput]; + _videoInput = newVideoInput; + } else { + success = NO; + [session addInput:_videoInput]; + } + [session commitConfiguration]; + success = YES; + } else if (error) { + success = NO; + //Handle error + } + } + + if (success) { + [self setCaptureSessionPreset:preset]; + } +bail: + return; +} + +-(void)releaseCapture { + [self stopCapture]; + [_videoOutput setSampleBufferDelegate:nil queue:NULL]; + dispatch_sync(_capture_queue, ^() { + [_captureSession stopRunning]; + }); + _captureSession = nil; + _videoOutput = nil; + + _videoInput = nil; +} + +- (void) initCapture { + //-- Setup Capture Session. + + _captureSession = [[AVCaptureSession alloc] init]; + [_captureSession beginConfiguration]; + + [_captureSession setSessionPreset:_capturePreset]; + + //Needs to be set in order to receive audio route/interruption events. + _captureSession.usesApplicationAudioSession = NO; + + //-- Create a video device and input from that Device. + // Add the input to the capture session. + AVCaptureDevice * videoDevice = [self backFacingCamera]; + if(videoDevice == nil) + assert(0); + + //-- Add the device to the session. + NSError *error; + _videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice + error:&error]; + + if(error) + assert(0); //TODO: Handle error + + [_captureSession addInput:_videoInput]; + + //-- Create the output for the capture session. + _videoOutput = [[AVCaptureVideoDataOutput alloc] init]; + [_videoOutput setAlwaysDiscardsLateVideoFrames:YES]; + + [_videoOutput setVideoSettings: + [NSDictionary dictionaryWithObject: + [NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] + forKey:(id)kCVPixelBufferPixelFormatTypeKey]]; + + [_videoOutput setSampleBufferDelegate:self queue:_capture_queue]; + + [_captureSession addOutput:_videoOutput]; + [_captureSession commitConfiguration]; + + [_captureSession startRunning]; + + [self setActiveFrameRate:OT_VIDEO_CAPTURE_IOS_DEFAULT_INITIAL_FRAMERATE]; + +} + +- (BOOL) isCaptureStarted { + return _captureSession && _capturing; +} + +- (int32_t) startCapture { + _capturing = YES; + return 0; +} + +- (int32_t) stopCapture { + _capturing = NO; + return 0; +} + +- (OTVideoOrientation)currentDeviceOrientation { + UIInterfaceOrientation orientation = + [[UIApplication sharedApplication] statusBarOrientation]; + // transforms are different for + if (AVCaptureDevicePositionFront == self.cameraPosition) + { + switch (orientation) { + case UIInterfaceOrientationLandscapeLeft: + return OTVideoOrientationUp; + case UIInterfaceOrientationLandscapeRight: + return OTVideoOrientationDown; + case UIInterfaceOrientationPortrait: + return OTVideoOrientationLeft; + case UIInterfaceOrientationPortraitUpsideDown: + return OTVideoOrientationRight; + } + } + else + { + switch (orientation) { + case UIInterfaceOrientationLandscapeLeft: + return OTVideoOrientationDown; + case UIInterfaceOrientationLandscapeRight: + return OTVideoOrientationUp; + case UIInterfaceOrientationPortrait: + return OTVideoOrientationLeft; + case UIInterfaceOrientationPortraitUpsideDown: + return OTVideoOrientationRight; + } + } + + return OTVideoOrientationUp; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput + didDropSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection +{ + +} + +/** + * Def: sanitary(n): A contiguous image buffer with no padding. All bytes in the + * store are actual pixel data. + */ +- (BOOL)imageBufferIsSanitary:(CVImageBufferRef)imageBuffer +{ + size_t planeCount = CVPixelBufferGetPlaneCount(imageBuffer); + // (Apple bug?) interleaved chroma plane measures in at half of actual size. + // No idea how many pixel formats this applys to, but we're specifically + // targeting 4:2:0 here, so there are some assuptions that must be made. + BOOL biplanar = (2 == planeCount); + + for (int i = 0; i < CVPixelBufferGetPlaneCount(imageBuffer); i++) { + size_t imageWidth = + CVPixelBufferGetWidthOfPlane(imageBuffer, i) * + CVPixelBufferGetHeightOfPlane(imageBuffer, i); + + if (biplanar && 1 == i) { + imageWidth *= 2; + } + + size_t dataWidth = + CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, i) * + CVPixelBufferGetHeightOfPlane(imageBuffer, i); + + if (imageWidth != dataWidth) { + return NO; + } + + BOOL hasNextAddress = CVPixelBufferGetPlaneCount(imageBuffer) > i + 1; + BOOL nextPlaneContiguous = YES; + + if (hasNextAddress) { + size_t planeLength = + dataWidth * CVPixelBufferGetHeightOfPlane(imageBuffer, i); + + uint8_t* baseAddress = + CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i); + + uint8_t* nextAddress = + CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i + 1); + + nextPlaneContiguous = &(baseAddress[planeLength]) == nextAddress; + } + + if (!nextPlaneContiguous) { + return NO; + } + } + + return YES; +} +- (size_t)sanitizeImageBuffer:(CVImageBufferRef)imageBuffer + data:(uint8_t**)data + planes:(NSPointerArray*)planes +{ + uint32_t pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer); + if (kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange == pixelFormat || + kCVPixelFormatType_420YpCbCr8BiPlanarFullRange == pixelFormat) + { + return [self sanitizeBiPlanarImageBuffer:imageBuffer + data:data + planes:planes]; + } else { + NSLog(@"No sanitization implementation for pixelFormat %d", + pixelFormat); + *data = NULL; + return 0; + } +} + +- (size_t)sanitizeBiPlanarImageBuffer:(CVImageBufferRef)imageBuffer + data:(uint8_t**)data + planes:(NSPointerArray*)planes +{ + size_t sanitaryBufferSize = 0; + for (int i = 0; i < CVPixelBufferGetPlaneCount(imageBuffer); i++) { + size_t planeImageWidth = + // TODO: (Apple bug?) biplanar pixel format reports 1/2 the width of + // what actually ends up in the pixel buffer for interleaved chroma. + // The only thing I could do about it is use image width for both plane + // calculations, in spite of this being technically wrong. + //CVPixelBufferGetWidthOfPlane(imageBuffer, i); + CVPixelBufferGetWidth(imageBuffer); + size_t planeImageHeight = + CVPixelBufferGetHeightOfPlane(imageBuffer, i); + sanitaryBufferSize += (planeImageWidth * planeImageHeight); + } + uint8_t* newImageBuffer = malloc(sanitaryBufferSize); + size_t bytesCopied = 0; + for (int i = 0; i < CVPixelBufferGetPlaneCount(imageBuffer); i++) { + [planes addPointer:&(newImageBuffer[bytesCopied])]; + void* planeBaseAddress = + CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i); + size_t planeDataWidth = + CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, i); + size_t planeImageWidth = + // Same as above. Use full image width for both luma and interleaved + // chroma planes. + //CVPixelBufferGetWidthOfPlane(imageBuffer, i); + CVPixelBufferGetWidth(imageBuffer); + size_t planeImageHeight = + CVPixelBufferGetHeightOfPlane(imageBuffer, i); + for (int rowIndex = 0; rowIndex < planeImageHeight; rowIndex++) { + memcpy(&(newImageBuffer[bytesCopied]), + &(planeBaseAddress[planeDataWidth * rowIndex]), + planeImageWidth); + bytesCopied += planeImageWidth; + } + } + assert(bytesCopied == sanitaryBufferSize); + *data = newImageBuffer; + return bytesCopied; +} + +- (void)captureOutput:(AVCaptureOutput *)captureOutput +didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer + fromConnection:(AVCaptureConnection *)connection { + + if (!(_capturing && _videoCaptureConsumer)) { + return; + } + + CMTime time = CMSampleBufferGetPresentationTimeStamp(sampleBuffer); + CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); + CVPixelBufferLockBaseAddress(imageBuffer, 0); + + _videoFrame.timestamp = time; + size_t height = CVPixelBufferGetHeight(imageBuffer); + size_t width = CVPixelBufferGetWidth(imageBuffer); + if (width != _captureWidth || height != _captureHeight) { + [self updateCaptureFormatWithWidth:width height:height]; + } + _videoFrame.format.imageWidth = width; + _videoFrame.format.imageHeight = height; + CMTime minFrameDuration; + minFrameDuration = _videoInput.device.activeVideoMinFrameDuration; + + _videoFrame.format.estimatedFramesPerSecond = + minFrameDuration.timescale / minFrameDuration.value; + // TODO: how do we measure this from AVFoundation? + _videoFrame.format.estimatedCaptureDelay = 100; + _videoFrame.orientation = [self currentDeviceOrientation]; + + [_videoFrame clearPlanes]; + uint8_t* sanitizedImageBuffer = NULL; + + if (!CVPixelBufferIsPlanar(imageBuffer)) + { + [_videoFrame.planes + addPointer:CVPixelBufferGetBaseAddress(imageBuffer)]; + } else if ([self imageBufferIsSanitary:imageBuffer]) { + for (int i = 0; i < CVPixelBufferGetPlaneCount(imageBuffer); i++) { + [_videoFrame.planes addPointer: + CVPixelBufferGetBaseAddressOfPlane(imageBuffer, i)]; + } + } else { + [self sanitizeImageBuffer:imageBuffer + data:&sanitizedImageBuffer + planes:_videoFrame.planes]; + } + + [_videoCaptureConsumer consumeFrame:_videoFrame]; + + free(sanitizedImageBuffer); + + CVPixelBufferUnlockBaseAddress(imageBuffer, 0); + +} + +@end diff --git a/BeMyEyes/Source/MKLocalizationKeys.h b/BeMyEyes/Source/MKLocalizationKeys.h index 945b8ac..d3f4037 100644 --- a/BeMyEyes/Source/MKLocalizationKeys.h +++ b/BeMyEyes/Source/MKLocalizationKeys.h @@ -2096,6 +2096,42 @@ static NSString * const BME_MAIN_ALERT_MORE_LANGAUGES_HELPER_TITLE = @"BME_MAIN_ */ static NSString * const BME_MAIN_SETTINGS_BUTTON_ACCESSIBILITY_LABEL = @"BME_MAIN_SETTINGS_BUTTON_ACCESSIBILITY_LABEL"; +/*! + * "Notifications" + + * All translations: + + * @b da@: "Notifikationer" + + * @b en@: "Notifications" + + */ +static NSString * const BME_MAIN_ALERT_NOTIFICATIONS_ERROR_TITLE = @"BME_MAIN_ALERT_NOTIFICATIONS_ERROR_TITLE"; + +/*! + * "Something went wrong and you might not receive notifications" + + * All translations: + + * @b da@: "Noget gik galt og du kan måske ikke modtage notifikationer" + + * @b en@: "Something went wrong and you might not receive notifications" + + */ +static NSString * const BME_MAIN_ALERT_NOTIFICATIONS_ERROR_MESSAGE = @"BME_MAIN_ALERT_NOTIFICATIONS_ERROR_MESSAGE"; + +/*! + * "Ok" + + * All translations: + + * @b da@: "Ok" + + * @b en@: "Ok" + + */ +static NSString * const BME_MAIN_ALERT_CANCEL = @"BME_MAIN_ALERT_CANCEL"; + /*! * "Back" diff --git a/BeMyEyes/da.lproj/BMEMainLocalizationTable.strings b/BeMyEyes/da.lproj/BMEMainLocalizationTable.strings index f0a0cfe..3efa8fc 100755 --- a/BeMyEyes/da.lproj/BMEMainLocalizationTable.strings +++ b/BeMyEyes/da.lproj/BMEMainLocalizationTable.strings @@ -32,3 +32,12 @@ /* Settings button accessibility label */ "BME_MAIN_SETTINGS_BUTTON_ACCESSIBILITY_LABEL" = "Indstillinger"; + +/* Title of notifications error title in alert view */ +"BME_MAIN_ALERT_NOTIFICATIONS_ERROR_TITLE" = "Notifikationer"; + +/* Title of cancel button in alert view */ +"BME_MAIN_ALERT_NOTIFICATIONS_ERROR_MESSAGE" = "Noget gik galt og du kan måske ikke modtage notifikationer"; + +/* Title of cancel button in alert view */ +"BME_MAIN_ALERT_CANCEL" = "Ok"; diff --git a/BeMyEyes/en.lproj/BMEMainLocalizationTable.strings b/BeMyEyes/en.lproj/BMEMainLocalizationTable.strings index 86cbaff..dd39765 100644 --- a/BeMyEyes/en.lproj/BMEMainLocalizationTable.strings +++ b/BeMyEyes/en.lproj/BMEMainLocalizationTable.strings @@ -32,3 +32,12 @@ /* Settings button accessibility label */ "BME_MAIN_SETTINGS_BUTTON_ACCESSIBILITY_LABEL" = "Settings"; + +/* Title of notifications error title in alert view */ +"BME_MAIN_ALERT_NOTIFICATIONS_ERROR_TITLE" = "Notifications"; + +/* Title of cancel button in alert view */ +"BME_MAIN_ALERT_NOTIFICATIONS_ERROR_MESSAGE" = "Something went wrong and you might not receive notifications"; + +/* Title of cancel button in alert view */ +"BME_MAIN_ALERT_CANCEL" = "Ok"; diff --git a/Podfile b/Podfile index df04c1b..d35d7d3 100644 --- a/Podfile +++ b/Podfile @@ -18,3 +18,4 @@ pod 'FormatterKit', '~> 1.7.1' pod 'KeepLayout', :git => 'https://github.com/iMartinKiss/KeepLayout.git' pod 'CrashlyticsFramework' pod 'SDWebImage', '~>3.6' +pod 'Reveal-iOS-SDK', :configurations => ['Debug'] diff --git a/Podfile.lock b/Podfile.lock index 7fe29ef..aeadaa6 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,7 +1,7 @@ PODS: - AFNetworking (1.3.4) - Appirater (2.0.4) - - CrashlyticsFramework (2.2.5) + - CrashlyticsFramework (2.2.5.1) - DCKeyValueObjectMapping (1.4) - FormatterKit (1.7.2): - FormatterKit/AddressFormatter @@ -60,6 +60,7 @@ PODS: - MRProgress/WeakProxy (0.4.3) - NewRelicAgent (4.83) - PSAlertView (1.1) + - Reveal-iOS-SDK (1.0.6) - SDWebImage (3.7.1): - SDWebImage/Core - SDWebImage/Core (3.7.1) @@ -78,6 +79,7 @@ DEPENDENCIES: - MRProgress (~> 0.4.1) - NewRelicAgent - PSAlertView (~> 1.1) + - Reveal-iOS-SDK - SDWebImage (~> 3.6) EXTERNAL SOURCES: @@ -89,7 +91,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AFNetworking: 80c4e0652b08eb34e25b9c0ff3c82556fe5967b4 Appirater: cbdb7f305562cfde585300bfff86da01ae559deb - CrashlyticsFramework: 82d124501d97d3b5d49bd6209dea21c6b7f0a33d + CrashlyticsFramework: 195dfef587ade7a3954ce8effd483a3879d35877 DCKeyValueObjectMapping: c3561709f296ddffba7768a9d7ccf234307fd3c9 FormatterKit: 230f6ae7a6e9ac468cb09b3ac2b7524effc8daaa GVUserDefaults: 66c524fc1709b2d95db774f4bb665dfad4671168 @@ -100,6 +102,7 @@ SPEC CHECKSUMS: MRProgress: e588fb8d0fba293715eaf0e485dd8402f4ce1de6 NewRelicAgent: 1adba1fb51631f598a2a130e7b1c3be1de715736 PSAlertView: e9c9bc8ef41d86012ca1f6839fd450a35c6f8a97 + Reveal-iOS-SDK: 74a39b426dbc915962704ed24f43ee32ae7797f9 SDWebImage: 116e88633b5b416ea0ca4b334a4ac59cf72dd38d COCOAPODS: 0.34.4