From 4ddc59892a2aec84741bac138f410f3b3bb7223b Mon Sep 17 00:00:00 2001 From: Birkir Gudjonsson Date: Fri, 16 Oct 2020 17:01:31 +0000 Subject: [PATCH] feat: carplay ios file --- ios/RCTConvert+RNCarPlay.h | 2 + ios/RCTConvert+RNCarPlay.m | 24 ++++- ios/RNCarPlay.h | 2 +- ios/RNCarPlay.m | 207 ++++++++++++++++++++++++++++++++----- 4 files changed, 206 insertions(+), 29 deletions(-) diff --git a/ios/RCTConvert+RNCarPlay.h b/ios/RCTConvert+RNCarPlay.h index 3e4e23a6..82a0807e 100644 --- a/ios/RCTConvert+RNCarPlay.h +++ b/ios/RCTConvert+RNCarPlay.h @@ -22,4 +22,6 @@ + (CPMapButton*)CPMapButton:(id)json withHandler:(void (^)(CPMapButton * _Nonnull mapButton))handler; + (CPRouteChoice*)CPRouteChoice:(id)json; + (MKMapItem*)MKMapItem:(id)json; ++ (CPPointOfInterest*)CPPointOfInterest:(id)json; ++ (CPAlertActionStyle)CPAlertActionStyle:(id)json; @end diff --git a/ios/RCTConvert+RNCarPlay.m b/ios/RCTConvert+RNCarPlay.m index 29994a31..702d046e 100644 --- a/ios/RCTConvert+RNCarPlay.m +++ b/ios/RCTConvert+RNCarPlay.m @@ -44,7 +44,7 @@ + (CPMapButton*)CPMapButton:(id)json withHandler:(void (^)(CPMapButton * _Nonnul if ([json objectForKey:@"hidden"]) { [mapButton setHidden:[RCTConvert BOOL:json[@"hidden"]]]; } - + return mapButton; } @@ -59,4 +59,26 @@ + (CPRouteChoice*)CPRouteChoice:(id)json { return [[CPRouteChoice alloc] initWithSummaryVariants:[RCTConvert NSStringArray:json[@"additionalInformationVariants"]] additionalInformationVariants:[RCTConvert NSStringArray:json[@"selectionSummaryVariants"]] selectionSummaryVariants:[RCTConvert NSStringArray:json[@"summaryVariants"]]]; } ++ (CPPointOfInterest*)CPPointOfInterest:(id)json { + MKMapItem *location = [RCTConvert MKMapItem:json[@"location"]]; + NSString *title = [RCTConvert NSString:json[@"title"]]; + NSString *subtitle = [RCTConvert NSString:json[@"subtitle"]]; + NSString *summary = [RCTConvert NSString:json[@"summary"]]; + NSString *detailTitle = [RCTConvert NSString:json[@"detailTitle"]]; + NSString *detailSubtitle = [RCTConvert NSString:json[@"detailSubtitle"]]; + NSString *detailSummary = [RCTConvert NSString:json[@"detailSummary"]]; + + CPPointOfInterest *poi = [[CPPointOfInterest alloc] initWithLocation:location title:title subtitle:subtitle summary:summary detailTitle:detailTitle detailSubtitle:detailSubtitle detailSummary:detailSummary pinImage:nil]; + return poi; +} + ++ (CPAlertActionStyle)CPAlertActionStyle:(NSString*) json { + if ([json isEqualToString:@"cancel"]) { + return CPAlertActionStyleCancel; + } else if ([json isEqualToString:@"destructive"]) { + return CPAlertActionStyleDestructive; + } + return CPAlertActionStyleDefault; +} + @end diff --git a/ios/RNCarPlay.h b/ios/RNCarPlay.h index 77ec656c..6f3fb057 100644 --- a/ios/RNCarPlay.h +++ b/ios/RNCarPlay.h @@ -16,7 +16,7 @@ typedef void(^SearchResultUpdateBlock)(NSArray * _Nonnull); typedef void(^SelectedResultBlock)(void); -@interface RNCarPlay : RCTEventEmitter { +@interface RNCarPlay : RCTEventEmitter { CPInterfaceController *interfaceController; CPWindow *window; SearchResultUpdateBlock searchResultBlock; diff --git a/ios/RNCarPlay.m b/ios/RNCarPlay.m index 809f4e60..d8eb6636 100644 --- a/ios/RNCarPlay.m +++ b/ios/RNCarPlay.m @@ -59,12 +59,16 @@ + (id)allocWithZone:(NSZone *)zone { @"willDisappear", // grid @"gridButtonPressed", + // information + @"actionButtonPressed", // list @"didSelectListItem", // search @"updatedSearchText", @"searchButtonPressed", @"selectedResult", + // tabbar + @"didSelectTemplate", // map @"mapButtonPressed", @"didUpdatePanGestureWithTranslation", @@ -97,6 +101,11 @@ - (dispatch_queue_t)methodQueue NSArray *leadingNavigationBarButtons = [self parseBarButtons:[RCTConvert NSArray:config[@"leadingNavigationBarButtons"]] templateId:templateId]; NSArray *trailingNavigationBarButtons = [self parseBarButtons:[RCTConvert NSArray:config[@"trailingNavigationBarButtons"]] templateId:templateId]; + NSLog(@"id: %@", templateId); + NSLog(@"type: %@", type); + NSLog(@"title %@", title); + NSLog(@"config %@", config); + CPTemplate *template = [[CPTemplate alloc] init]; if ([type isEqualToString:@"search"]) { @@ -128,27 +137,97 @@ - (dispatch_queue_t)methodQueue mapTemplate.mapDelegate = self; template = mapTemplate; - } else if ([type isEqualToString:@"voice"]) { + } else if ([type isEqualToString:@"voicecontrol"]) { CPVoiceControlTemplate *voiceTemplate = [[CPVoiceControlTemplate alloc] initWithVoiceControlStates: [self parseVoiceControlStates:config[@"voiceControlStates"]]]; template = voiceTemplate; } else if ([type isEqualToString:@"nowplaying"]) { - NSCoder *myCoder = [[NSCoder alloc] init]; - CPNowPlayingTemplate *nowPlayingTemplate = [[CPNowPlayingTemplate alloc] initWithCoder:myCoder]; - - [nowPlayingTemplate setAlbumArtistButtonEnabled:YES]; - [nowPlayingTemplate setUpNextTitle:@""]; - [nowPlayingTemplate setUpNextButtonEnabled:YES]; - // [nowPlayingTemplate updateNowPlayingButtons:@{}]; - - template = nowPlayingTemplate; + if (@available(iOS 14.0, *)) { + CPNowPlayingTemplate *nowPlayingTemplate = [CPNowPlayingTemplate sharedTemplate]; + [nowPlayingTemplate setAlbumArtistButtonEnabled:[RCTConvert BOOL:config[@"albumArtistButton"]]]; + [nowPlayingTemplate setUpNextTitle:[RCTConvert NSString:config[@"upNextTitle"]]]; + [nowPlayingTemplate setUpNextButtonEnabled:[RCTConvert BOOL:config[@"upNextButton"]]]; + template = nowPlayingTemplate; + } } else if ([type isEqualToString:@"tabbar"]) { - - NSArray *arr = [[NSArray alloc] init]; - - [arr arrayByAddingObject:[store findTemplateById:@"abc"]]; - - CPTabBarTemplate *tabBarTemplate = [[CPTabBarTemplate alloc] initWithTemplates:arr]; - template = tabBarTemplate; + if (@available(iOS 14.0, *)) { + CPTabBarTemplate *tabBarTemplate = [[CPTabBarTemplate alloc] initWithTemplates:[self parseTemplatesFrom:config]]; + tabBarTemplate.delegate = self; + template = tabBarTemplate; + } + } else if ([type isEqualToString:@"contact"]) { + if (@available(iOS 14.0, *)) { + CPContact *contact = [[CPContact alloc] init]; + [contact setName:config[@"name"]]; + [contact setSubtitle:config[@"subtitle"]]; + [contact setActions:[self parseButtons:config[@"actions"] templateId:templateId]]; + CPContactTemplate *contactTemplate = [[CPContactTemplate alloc] initWithContact:contact]; + template = contactTemplate; + } + } else if ([type isEqualToString:@"actionsheet"]) { + NSString *title = [RCTConvert NSString:config[@"title"]]; + NSString *message = [RCTConvert NSString:config[@"message"]]; + NSMutableArray *actions = [NSMutableArray new]; + NSArray *_actions = [RCTConvert NSDictionaryArray:config[@"actions"]]; + for (NSDictionary *_action in _actions) { + CPAlertAction *action = [[CPAlertAction alloc] initWithTitle:[RCTConvert NSString:_action[@"title"]] style:[RCTConvert CPAlertActionStyle:_action[@"style"]] handler:^(CPAlertAction *a) { + [self sendEventWithName:@"actionButtonPressed" body:@{@"templateId":templateId, @"id": _action[@"id"] }]; + }]; + [actions addObject:action]; + } + CPActionSheetTemplate *actionSheetTemplate = [[CPActionSheetTemplate alloc] initWithTitle:title message:message actions:actions]; + template = actionSheetTemplate; + } else if ([type isEqualToString:@"alert"]) { + NSMutableArray *actions = [NSMutableArray new]; + NSArray *_actions = [RCTConvert NSDictionaryArray:config[@"actions"]]; + for (NSDictionary *_action in _actions) { + CPAlertAction *action = [[CPAlertAction alloc] initWithTitle:[RCTConvert NSString:_action[@"title"]] style:[RCTConvert CPAlertActionStyle:_action[@"style"]] handler:^(CPAlertAction *a) { + [self sendEventWithName:@"actionButtonPressed" body:@{@"templateId":templateId, @"id": _action[@"id"] }]; + }]; + [actions addObject:action]; + } + NSArray* titleVariants = [RCTConvert NSArray:config[@"titleVariants"]]; + CPAlertTemplate *alertTemplate = [[CPAlertTemplate alloc] initWithTitleVariants:titleVariants actions:actions]; + template = alertTemplate; + } else if ([type isEqualToString:@"poi"]) { + if (@available(iOS 14.0, *)) { + NSString *title = [RCTConvert NSString:config[@"title"]]; + NSMutableArray<__kindof CPPointOfInterest *> * items = [NSMutableArray new]; + NSUInteger selectedIndex = 0; + + NSArray *_items = [RCTConvert NSDictionaryArray:config[@"items"]]; + for (NSDictionary *_item in _items) { + CPPointOfInterest *poi = [RCTConvert CPPointOfInterest:_item]; + [poi setUserInfo:_item]; + [items addObject:poi]; + } + + CPPointOfInterestTemplate *poiTemplate = [[CPPointOfInterestTemplate alloc] initWithTitle:title pointsOfInterest:items selectedIndex:selectedIndex]; + poiTemplate.pointOfInterestDelegate = self; + template = poiTemplate; + } + } else if ([type isEqualToString:@"information"]) { + if (@available(iOS 14.0, *)) { + NSString *title = [RCTConvert NSString:config[@"title"]]; + CPInformationTemplateLayout layout = [RCTConvert BOOL:config[@"leading"]] ? CPInformationTemplateLayoutLeading : CPInformationTemplateLayoutTwoColumn; + NSMutableArray<__kindof CPInformationItem *> * items = [NSMutableArray new]; + NSMutableArray<__kindof CPTextButton *> * actions = [NSMutableArray new]; + + NSArray *_items = [RCTConvert NSDictionaryArray:config[@"items"]]; + for (NSDictionary *_item in _items) { + [items addObject:[[CPInformationItem alloc] initWithTitle:_item[@"title"] detail:_item[@"detail"]]]; + } + + NSArray *_actions = [RCTConvert NSDictionaryArray:config[@"actions"]]; + for (NSDictionary *_action in _actions) { + CPTextButton *action = [[CPTextButton alloc] initWithTitle:_action[@"title"] textStyle:CPTextButtonStyleNormal handler:^(__kindof CPTextButton * _Nonnull contactButton) { + [self sendEventWithName:@"actionButtonPressed" body:@{@"templateId":templateId, @"id": _action[@"id"] }]; + }]; + [actions addObject:action]; + } + + CPInformationTemplate *informationTemplate = [[CPInformationTemplate alloc] initWithTitle:title layout:layout items:items actions:actions]; + template = informationTemplate; + } } [template setUserInfo:@{ @"templateId": templateId }]; @@ -156,6 +235,17 @@ - (dispatch_queue_t)methodQueue [store setTemplate:templateId template:template]; } +RCT_EXPORT_METHOD(updateTemplates:(NSString*)templateId config:(NSDictionary*)config) { + if (@available(iOS 14.0, *)) { + RNCPStore *store = [RNCPStore sharedManager]; + CPTemplate *template = [store findTemplateById:templateId]; + if (template) { + CPTabBarTemplate *tabBarTemplate = (CPTabBarTemplate*) template; + [tabBarTemplate updateTemplates:[self parseTemplatesFrom:config]]; + } + } +} + RCT_EXPORT_METHOD(createTrip:(NSString*)tripId config:(NSDictionary*)config) { RNCPStore *store = [RNCPStore sharedManager]; CPTrip *trip = [self parseTrip:config]; @@ -257,7 +347,14 @@ - (dispatch_queue_t)methodQueue RNCPStore *store = [RNCPStore sharedManager]; CPTemplate *template = [store findTemplateById:templateId]; if (template) { - [store.interfaceController pushTemplate:template animated:animated]; + if (@available(iOS 14.0, *)) { + [store.interfaceController pushTemplate:template animated:animated completion:^(BOOL done, NSError * _Nullable err) { + NSLog(@"error %@", err); + // noop + }]; + } else { + [store.interfaceController pushTemplate:template animated:animated]; + } } else { NSLog(@"Failed to find template %@", template); } @@ -407,11 +504,11 @@ - (void) applyConfigForMapTemplate:(CPMapTemplate*)mapTemplate templateId:(NSStr if ([config objectForKey:@"guidanceBackgroundColor"]) { [mapTemplate setGuidanceBackgroundColor:[RCTConvert UIColor:config[@"guidanceBackgroundColor"]]]; } - + if ([config objectForKey:@"tripEstimateStyle"]) { [mapTemplate setTripEstimateStyle:[RCTConvert CPTripEstimateStyle:config[@"tripEstimateStyle"]]]; } - + if ([config objectForKey:@"mapButtons"]) { NSArray *mapButtons = [RCTConvert NSArray:config[@"mapButtons"]]; NSMutableArray *result = [NSMutableArray array]; @@ -427,7 +524,7 @@ - (void) applyConfigForMapTemplate:(CPMapTemplate*)mapTemplate templateId:(NSStr if ([config objectForKey:@"automaticallyHidesNavigationBar"]) { [mapTemplate setAutomaticallyHidesNavigationBar:[RCTConvert BOOL:config[@"automaticallyHidesNavigationBar"]]]; } - + if ([config objectForKey:@"hidesButtonsWithNavigationBar"]) { [mapTemplate setHidesButtonsWithNavigationBar:[RCTConvert BOOL:config[@"hidesButtonsWithNavigationBar"]]]; } @@ -440,6 +537,47 @@ - (void) applyConfigForMapTemplate:(CPMapTemplate*)mapTemplate templateId:(NSStr } } +- (NSArray<__kindof CPTemplate*>*) parseTemplatesFrom:(NSDictionary*)config { + RNCPStore *store = [RNCPStore sharedManager]; + NSMutableArray<__kindof CPTemplate*> *templates = [NSMutableArray new]; + NSArray *tpls = [RCTConvert NSDictionaryArray:config[@"templates"]]; + for (NSDictionary *tpl in tpls) { + CPTemplate *templ = [store findTemplateById:tpl[@"id"]]; + // @todo UITabSystemItem + [templates addObject:templ]; + } + return templates; +} + +- (NSArray*) parseButtons:(NSArray*)buttons templateId:(NSString *)templateId API_AVAILABLE(ios(14.0)){ + NSMutableArray *result = [NSMutableArray array]; + for (NSDictionary *button in buttons) { + CPButton *_button; + NSString *_id = [button objectForKey:@"id"]; + NSString *type = [button objectForKey:@"type"]; + if ([type isEqualToString:@"call"]) { + _button = [[CPContactCallButton alloc] initWithHandler:^(__kindof CPButton * _Nonnull contactButton) { + [self sendEventWithName:@"buttonPressed" body:@{@"id": _id, @"templateId":templateId}]; + }]; + } else if ([type isEqualToString:@"message"]) { + _button = [[CPContactMessageButton alloc] initWithPhoneOrEmail:[button objectForKey:@"phoneOrEmail"]]; + } else if ([type isEqualToString:@"directions"]) { + _button = [[CPContactDirectionsButton alloc] initWithHandler:^(__kindof CPButton * _Nonnull contactButton) { + [self sendEventWithName:@"buttonPressed" body:@{@"id": _id, @"templateId":templateId}]; + }]; + } + + BOOL _disabled = [button objectForKey:@"disabled"]; + [_button setEnabled:!_disabled]; + + NSString *_title = [button objectForKey:@"title"]; + [_button setTitle:_title]; + + [result addObject:_button]; + } + return result; +} + - (NSArray*) parseBarButtons:(NSArray*)barButtons templateId:(NSString *)templateId { NSMutableArray *result = [NSMutableArray array]; for (NSDictionary *barButton in barButtons) { @@ -527,25 +665,25 @@ - (CPTravelEstimates*)parseTravelEstimates: (NSDictionary*)json { - (CPManeuver*)parseManeuver:(NSDictionary*)json { CPManeuver* maneuver = [[CPManeuver alloc] init]; - + if ([json objectForKey:@"junctionImage"]) { [maneuver setJunctionImage:[RCTConvert UIImage:json[@"junctionImage"]]]; } - + if ([json objectForKey:@"initialTravelEstimates"]) { CPTravelEstimates* travelEstimates = [self parseTravelEstimates:json[@"initialTravelEstimates"]]; [maneuver setInitialTravelEstimates:travelEstimates]; } - + if ([json objectForKey:@"symbolLight"] && [json objectForKey:@"symbolDark"]) { CPImageSet *symbolSet = [[CPImageSet alloc] initWithLightContentImage:[RCTConvert UIImage:json[@"symbolLight"]] darkContentImage:[RCTConvert UIImage:json[@"symbolDark"]]]; [maneuver setSymbolSet:symbolSet]; } - + if ([json objectForKey:@"instructionVariants"]) { [maneuver setInstructionVariants:[RCTConvert NSStringArray:json[@"instructionVariants"]]]; } - + return maneuver; } @@ -624,7 +762,7 @@ - (NSDictionary*)navigationAlertToJson:(CPNavigationAlert*)navigationAlert dismi break; } } - + return @{ @"todo": @(YES), @"reason": dismissalCtx @@ -739,6 +877,21 @@ - (void)listTemplate:(CPListTemplate *)listTemplate didSelectListItem:(CPListIte self.selectedResultBlock = completionHandler; } +# pragma TabBarTemplate +- (void)tabBarTemplate:(CPTabBarTemplate *)tabBarTemplate didSelectTemplate:(__kindof CPTemplate *)selectedTemplate API_AVAILABLE(ios(14.0)){ + NSString* selectedTemplateId = [[selectedTemplate userInfo] objectForKey:@"templateId"]; + [self sendTemplateEventWithName:tabBarTemplate name:@"didSelectTemplate" json:@{@"selectedTemplateId":selectedTemplateId}]; +} + +# pragma PointOfInterest +-(void)pointOfInterestTemplate:(CPPointOfInterestTemplate *)pointOfInterestTemplate didChangeMapRegion:(MKCoordinateRegion)region API_AVAILABLE(ios(14.0)){ + // noop +} + +-(void)pointOfInterestTemplate:(CPPointOfInterestTemplate *)pointOfInterestTemplate didSelectPointOfInterest:(CPPointOfInterest *)pointOfInterest API_AVAILABLE(ios(14.0)){ + [self sendTemplateEventWithName:pointOfInterestTemplate name:@"didSelectPointOfInterest" json:[pointOfInterest userInfo]]; +} + # pragma InterfaceController - (void)templateDidAppear:(CPTemplate *)aTemplate animated:(BOOL)animated {