summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2021-01-22 16:01:11 -0500
committerJoel Fischer <joeljfischer@gmail.com>2021-01-22 16:01:11 -0500
commit7332f5e99d6de045c37a69e008ffeadbcc54bb72 (patch)
treee247e57fc068db912b854d61a6c516045a52bf04
parent3a17e6a06c68d361294f4be58ac76178bc3cc5a1 (diff)
downloadsdl_ios-7332f5e99d6de045c37a69e008ffeadbcc54bb72.tar.gz
A ton of work on the menu replace operations
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj16
-rw-r--r--SmartDeviceLink/private/SDLError.m2
-rw-r--r--SmartDeviceLink/private/SDLMenuManager.m331
-rw-r--r--SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.h16
-rw-r--r--SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.m139
-rw-r--r--SmartDeviceLink/private/SDLMenuReplaceStaticOperation.h14
-rw-r--r--SmartDeviceLink/private/SDLMenuReplaceStaticOperation.m178
-rw-r--r--SmartDeviceLink/private/SDLMenuReplaceUtilities.h42
-rw-r--r--SmartDeviceLink/private/SDLMenuReplaceUtilities.m158
9 files changed, 562 insertions, 334 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index e472ee38a..0d45f7f98 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -519,6 +519,8 @@
4A93895425B9DACA0069F438 /* SDLMenuShowOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A93895225B9DACA0069F438 /* SDLMenuShowOperation.m */; };
4A93895925B9E5E40069F438 /* SDLMenuConfigurationUpdateOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A93895725B9E5E40069F438 /* SDLMenuConfigurationUpdateOperation.h */; };
4A93895A25B9E5E40069F438 /* SDLMenuConfigurationUpdateOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A93895825B9E5E40069F438 /* SDLMenuConfigurationUpdateOperation.m */; };
+ 4A93896625BB361C0069F438 /* SDLMenuReplaceUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A93896425BB361C0069F438 /* SDLMenuReplaceUtilities.h */; };
+ 4A93896725BB361C0069F438 /* SDLMenuReplaceUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A93896525BB361C0069F438 /* SDLMenuReplaceUtilities.m */; };
4ABB24BA24F592620061BF55 /* NSMutableArray+Safe.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB24B224F592620061BF55 /* NSMutableArray+Safe.h */; };
4ABB24BB24F592620061BF55 /* NSMutableArray+Safe.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB24B324F592620061BF55 /* NSMutableArray+Safe.m */; };
4ABB24BC24F592620061BF55 /* NSBundle+SDLBundle.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB24B424F592620061BF55 /* NSBundle+SDLBundle.m */; };
@@ -2313,6 +2315,8 @@
4A93895225B9DACA0069F438 /* SDLMenuShowOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLMenuShowOperation.m; path = private/SDLMenuShowOperation.m; sourceTree = "<group>"; };
4A93895725B9E5E40069F438 /* SDLMenuConfigurationUpdateOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLMenuConfigurationUpdateOperation.h; path = private/SDLMenuConfigurationUpdateOperation.h; sourceTree = "<group>"; };
4A93895825B9E5E40069F438 /* SDLMenuConfigurationUpdateOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLMenuConfigurationUpdateOperation.m; path = private/SDLMenuConfigurationUpdateOperation.m; sourceTree = "<group>"; };
+ 4A93896425BB361C0069F438 /* SDLMenuReplaceUtilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLMenuReplaceUtilities.h; path = private/SDLMenuReplaceUtilities.h; sourceTree = "<group>"; };
+ 4A93896525BB361C0069F438 /* SDLMenuReplaceUtilities.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLMenuReplaceUtilities.m; path = private/SDLMenuReplaceUtilities.m; sourceTree = "<group>"; };
4ABB24B224F592620061BF55 /* NSMutableArray+Safe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSMutableArray+Safe.h"; path = "private/NSMutableArray+Safe.h"; sourceTree = "<group>"; };
4ABB24B324F592620061BF55 /* NSMutableArray+Safe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSMutableArray+Safe.m"; path = "private/NSMutableArray+Safe.m"; sourceTree = "<group>"; };
4ABB24B424F592620061BF55 /* NSBundle+SDLBundle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSBundle+SDLBundle.m"; path = "private/NSBundle+SDLBundle.m"; sourceTree = "<group>"; };
@@ -4270,10 +4274,20 @@
4A93894425B8CC5F0069F438 /* SDLMenuReplaceStaticOperation.m */,
4A93895125B9DACA0069F438 /* SDLMenuShowOperation.h */,
4A93895225B9DACA0069F438 /* SDLMenuShowOperation.m */,
+ 4A93896325BB35FA0069F438 /* Menu Replace Utilities */,
);
name = Operations;
sourceTree = "<group>";
};
+ 4A93896325BB35FA0069F438 /* Menu Replace Utilities */ = {
+ isa = PBXGroup;
+ children = (
+ 4A93896425BB361C0069F438 /* SDLMenuReplaceUtilities.h */,
+ 4A93896525BB361C0069F438 /* SDLMenuReplaceUtilities.m */,
+ );
+ name = "Menu Replace Utilities";
+ sourceTree = "<group>";
+ };
4A9D02BB2497EEB400FBE99B /* Custom RPC Adapters */ = {
isa = PBXGroup;
children = (
@@ -7146,6 +7160,7 @@
4ABB2AF424F849CF0061BF55 /* SDLGenericResponse.h in Headers */,
4ABB280A24F824600061BF55 /* SDLServiceUpdateReason.h in Headers */,
4ABB2B5124F84EF50061BF55 /* SDLDisplayCapability.h in Headers */,
+ 4A93896625BB361C0069F438 /* SDLMenuReplaceUtilities.h in Headers */,
4ABB2ABD24F847FC0061BF55 /* SDLSliderResponse.h in Headers */,
4ABB28D924F82A6A0061BF55 /* SDLOnSyncPData.h in Headers */,
4A8BD2B924F935BC000945E3 /* SDLSoftButton.h in Headers */,
@@ -7909,6 +7924,7 @@
4ABB274F24F7FD9C0061BF55 /* SDLECallConfirmationStatus.m in Sources */,
4ABB27AF24F7FFDA0061BF55 /* SDLMaintenanceModeStatus.m in Sources */,
4ABB292D24F842A00061BF55 /* SDLDeleteWindow.m in Sources */,
+ 4A93896725BB361C0069F438 /* SDLMenuReplaceUtilities.m in Sources */,
4ABB299A24F845440061BF55 /* SDLSendHapticData.m in Sources */,
4ABB29DB24F846880061BF55 /* SDLUnregisterAppInterface.m in Sources */,
4A93895425B9DACA0069F438 /* SDLMenuShowOperation.m in Sources */,
diff --git a/SmartDeviceLink/private/SDLError.m b/SmartDeviceLink/private/SDLError.m
index 57fab7809..05338b72b 100644
--- a/SmartDeviceLink/private/SDLError.m
+++ b/SmartDeviceLink/private/SDLError.m
@@ -287,7 +287,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSError *)sdl_menuManager_openMenuOperationFailed:(nullable SDLMenuCell *)menuCell {
NSString *failureReason = nil;
if (menuCell != nil) {
- failureReason = @"Something went wrong attempting to open the menu."
+ failureReason = @"Something went wrong attempting to open the menu.";
} else {
failureReason = [NSString stringWithFormat:@"Something went wrong attempting to open the menu to the given subcell: %@", menuCell];
}
diff --git a/SmartDeviceLink/private/SDLMenuManager.m b/SmartDeviceLink/private/SDLMenuManager.m
index 8d79b22a4..673804f85 100644
--- a/SmartDeviceLink/private/SDLMenuManager.m
+++ b/SmartDeviceLink/private/SDLMenuManager.m
@@ -211,7 +211,7 @@ UInt32 const MenuCellIdMin = 1;
if ([self sdl_isDynamicMenuUpdateActive:self.dynamicMenuUpdatesMode]) {
[self sdl_startDynamicMenuUpdate];
} else {
- [self sdl_startNonDynamicMenuUpdate];
+ [self sdl_startStaticMenuUpdate];
}
}
@@ -254,10 +254,7 @@ UInt32 const MenuCellIdMin = 1;
#pragma mark - Build Deletes, Keeps, Adds
- (void)sdl_startSubMenuUpdatesWithOldKeptCells:(NSArray<SDLMenuCell *> *)oldKeptCells newKeptCells:(NSArray<SDLMenuCell *> *)newKeptCells atIndex:(NSUInteger)startIndex {
- if (oldKeptCells.count == 0 || startIndex >= oldKeptCells.count) {
- self.inProgressUpdate = nil;
- return;
- }
+ if (oldKeptCells.count == 0 || startIndex >= oldKeptCells.count) { return; }
if (oldKeptCells[startIndex].subCells.count > 0) {
SDLDynamicMenuUpdateRunScore *tempScore = [SDLDynamicMenuUpdateAlgorithm compareOldMenuCells:oldKeptCells[startIndex].subCells updatedMenuCells:newKeptCells[startIndex].subCells];
@@ -286,206 +283,20 @@ UInt32 const MenuCellIdMin = 1;
}
}
-- (NSArray<SDLMenuCell *> *)sdl_filterDeleteMenuItemsWithOldMenuItems:(NSArray<SDLMenuCell *> *)oldMenuCells basedOnStatusList:(NSArray<NSNumber *> *)oldStatusList {
- NSMutableArray<SDLMenuCell *> *deleteCells = [[NSMutableArray alloc] init];
- // The index of the status should corrleate 1-1 with the number of items in the menu
- // [2,0,2,0] => [A,B,C,D] = [B,D]
- for (NSUInteger index = 0; index < oldStatusList.count; index++) {
- if (oldStatusList[index].integerValue == MenuCellStateDelete) {
- [deleteCells addObject:oldMenuCells[index]];
- }
- }
- return [deleteCells copy];
-}
-
-- (NSArray<SDLMenuCell *> *)sdl_filterAddMenuItemsWithNewMenuItems:(NSArray<SDLMenuCell *> *)newMenuCells basedOnStatusList:(NSArray<NSNumber *> *)newStatusList {
- NSMutableArray<SDLMenuCell *> *addCells = [[NSMutableArray alloc] init];
- // The index of the status should corrleate 1-1 with the number of items in the menu
- // [2,1,2,1] => [A,B,C,D] = [B,D]
- for (NSUInteger index = 0; index < newStatusList.count; index++) {
- if (newStatusList[index].integerValue == MenuCellStateAdd) {
- [addCells addObject:newMenuCells[index]];
- }
- }
- return [addCells copy];
-}
-
-- (NSArray<SDLMenuCell *> *)sdl_filterKeepMenuItemsWithOldMenuItems:(NSArray<SDLMenuCell *> *)oldMenuCells basedOnStatusList:(NSArray<NSNumber *> *)keepStatusList {
- NSMutableArray<SDLMenuCell *> *keepMenuCells = [[NSMutableArray alloc] init];
-
- for (NSUInteger index = 0; index < keepStatusList.count; index++) {
- if (keepStatusList[index].integerValue == MenuCellStateKeep) {
- [keepMenuCells addObject:oldMenuCells[index]];
- }
- }
- return [keepMenuCells copy];
-}
-
-- (NSArray<SDLMenuCell *> *)sdl_filterKeepMenuItemsWithNewMenuItems:(NSArray<SDLMenuCell *> *)newMenuCells basedOnStatusList:(NSArray<NSNumber *> *)keepStatusList {
- NSMutableArray<SDLMenuCell *> *keepMenuCells = [[NSMutableArray alloc] init];
- for (NSUInteger index = 0; index < keepStatusList.count; index++) {
- if (keepStatusList[index].integerValue == MenuCellStateKeep) {
- [keepMenuCells addObject:newMenuCells[index]];
- }
- }
- return [keepMenuCells copy];
-}
-
-- (void)transferCellIDFromOldCells:(NSArray<SDLMenuCell *> *)oldCells toKeptCells:(NSArray<SDLMenuCell *> *)newCells {
- if (oldCells.count == 0) { return; }
- for (NSUInteger i = 0; i < newCells.count; i++) {
- newCells[i].cellId = oldCells[i].cellId;
- }
-}
-
#pragma mark - Updating System
- (void)sdl_startDynamicMenuUpdate {
- SDLDynamicMenuUpdateRunScore *runScore = [SDLDynamicMenuUpdateAlgorithm compareOldMenuCells:self.currentMenuCells updatedMenuCells:self.menuCells];
-
- NSArray<NSNumber *> *deleteMenuStatus = runScore.oldStatus;
- NSArray<NSNumber *> *addMenuStatus = runScore.updatedStatus;
-
- NSArray<SDLMenuCell *> *cellsToDelete = [self sdl_filterDeleteMenuItemsWithOldMenuItems:self.currentMenuCells basedOnStatusList:deleteMenuStatus];
- NSArray<SDLMenuCell *> *cellsToAdd = [self sdl_filterAddMenuItemsWithNewMenuItems:self.menuCells basedOnStatusList:addMenuStatus];
- // These arrays should ONLY contain KEEPS. These will be used for SubMenu compares
- NSArray<SDLMenuCell *> *oldKeeps = [self sdl_filterKeepMenuItemsWithOldMenuItems:self.currentMenuCells basedOnStatusList:deleteMenuStatus];
- NSArray<SDLMenuCell *> *newKeeps = [self sdl_filterKeepMenuItemsWithNewMenuItems:self.menuCells basedOnStatusList:addMenuStatus];
-
- // Cells that will be added need new ids
- [self sdl_updateIdsOnMenuCells:cellsToAdd parentId:ParentIdNotFound];
-
- // Since we are creating a new Menu but keeping old cells we must firt transfer the old cellIDs to the new menus kept cells.
- [self transferCellIDFromOldCells:oldKeeps toKeptCells:newKeeps];
-
- // Upload the artworks
- NSArray<SDLArtwork *> *artworksToBeUploaded = [self sdl_findAllArtworksToBeUploadedFromCells:cellsToAdd];
- if (artworksToBeUploaded.count > 0) {
- [self.fileManager uploadArtworks:artworksToBeUploaded completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
- if (error != nil) {
- SDLLogE(@"Error uploading menu artworks: %@", error);
- }
- SDLLogD(@"Menu artworks uploaded");
- // Update cells with artworks once they're uploaded
- __weak typeof(self) weakself = self;
- [self sdl_updateMenuWithCellsToDelete:cellsToDelete cellsToAdd:cellsToAdd completionHandler:^(NSError * _Nullable error) {
- [weakself sdl_startSubMenuUpdatesWithOldKeptCells:oldKeeps newKeptCells:newKeeps atIndex:0];
- }];
- }];
- } else {
- // Cells have no artwork to load
- __weak typeof(self) weakself = self;
- [self sdl_updateMenuWithCellsToDelete:cellsToDelete cellsToAdd:cellsToAdd completionHandler:^(NSError * _Nullable error) {
- [weakself sdl_startSubMenuUpdatesWithOldKeptCells:oldKeeps newKeptCells:newKeeps atIndex:0];
- }];
- }
-}
-- (void)sdl_startNonDynamicMenuUpdate {
- [self sdl_updateIdsOnMenuCells:self.menuCells parentId:ParentIdNotFound];
+}
- NSArray<SDLArtwork *> *artworksToBeUploaded = [self sdl_findAllArtworksToBeUploadedFromCells:self.menuCells];
- if (artworksToBeUploaded.count > 0) {
- [self.fileManager uploadArtworks:artworksToBeUploaded completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
- if (error != nil) {
- SDLLogE(@"Error uploading menu artworks: %@", error);
- }
+- (void)sdl_startStaticMenuUpdate {
- SDLLogD(@"Menu artworks uploaded");
- [self sdl_updateMenuWithCellsToDelete:self.currentMenuCells cellsToAdd:self.menuCells completionHandler:nil];
- }];
- } else {
- // Cells have no artwork to load
- [self sdl_updateMenuWithCellsToDelete:self.currentMenuCells cellsToAdd:self.menuCells completionHandler:nil];
- }
}
- (void)sdl_updateMenuWithCellsToDelete:(NSArray<SDLMenuCell *> *)deleteCells cellsToAdd:(NSArray<SDLMenuCell *> *)addCells completionHandler:(nullable SDLMenuUpdateCompletionHandler)completionHandler {
__weak typeof(self) weakself = self;
[self sdl_sendDeleteCurrentMenu:deleteCells withCompletionHandler:^(NSError * _Nullable error) {
[weakself sdl_sendUpdatedMenu:addCells usingMenu:weakself.menuCells withCompletionHandler:^(NSError * _Nullable error) { }];
-
- }];
-}
-
-#pragma mark Delete Old Menu Items
-
-- (void)sdl_sendDeleteCurrentMenu:(nullable NSArray<SDLMenuCell *> *)deleteMenuCells withCompletionHandler:(SDLMenuUpdateCompletionHandler)completionHandler {
- if (deleteMenuCells.count == 0) {
- completionHandler(nil);
- return;
- }
-
- NSArray<SDLRPCRequest *> *deleteMenuCommands = [self sdl_deleteCommandsForCells:deleteMenuCells];
- [self.connectionManager sendRequests:deleteMenuCommands progressHandler:nil completionHandler:^(BOOL success) {
- if (!success) {
- SDLLogW(@"Unable to delete all old menu commands");
- } else {
- SDLLogD(@"Finished deleting old menu");
- }
-
- completionHandler(nil);
- }];
-}
-
-#pragma mark Send New Menu Items
-
-/**
- Creates add commands
-
- @param updatedMenu The cells you will be adding
- @param menu The list of all cells. This may be different then self.menuCells since this function is called on subcell cells as well. When comparing 2 sub menu cells this function will be passed the list of all subcells on that cell.
- @param completionHandler handler
- */
-- (void)sdl_sendUpdatedMenu:(NSArray<SDLMenuCell *> *)updatedMenu usingMenu:(NSArray<SDLMenuCell *> *)menu withCompletionHandler:(SDLMenuUpdateCompletionHandler)completionHandler {
- if (self.menuCells.count == 0 || updatedMenu.count == 0) {
- SDLLogD(@"There are no cells to update.");
- completionHandler(nil);
- return;
- }
-
- NSArray<SDLRPCRequest *> *mainMenuCommands = nil;
- NSArray<SDLRPCRequest *> *subMenuCommands = nil;
-
- if (![self sdl_shouldRPCsIncludeImages:self.menuCells] || ![self.windowCapability hasImageFieldOfName:SDLImageFieldNameCommandIcon]) {
- // Send artwork-less menu
- mainMenuCommands = [self sdl_mainMenuCommandsForCells:updatedMenu withArtwork:NO usingIndexesFrom:menu];
- subMenuCommands = [self sdl_subMenuCommandsForCells:updatedMenu withArtwork:NO];
- } else {
- // Send full artwork menu
- mainMenuCommands = [self sdl_mainMenuCommandsForCells:updatedMenu withArtwork:YES usingIndexesFrom:menu];
- subMenuCommands = [self sdl_subMenuCommandsForCells:updatedMenu withArtwork:YES];
- }
-
- self.inProgressUpdate = [mainMenuCommands arrayByAddingObjectsFromArray:subMenuCommands];
-
- __block NSMutableDictionary<SDLRPCRequest *, NSError *> *errors = [NSMutableDictionary dictionary];
- __weak typeof(self) weakSelf = self;
- [self.connectionManager sendRequests:mainMenuCommands progressHandler:^void(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
- if (error != nil) {
- errors[request] = error;
- }
- } completionHandler:^(BOOL success) {
- if (!success) {
- SDLLogE(@"Failed to send main menu commands: %@", errors);
- completionHandler([NSError sdl_menuManager_failedToUpdateWithDictionary:errors]);
- return;
- }
-
- [weakSelf.connectionManager sendRequests:subMenuCommands progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
- if (error != nil) {
- errors[request] = error;
- }
- } completionHandler:^(BOOL success) {
- if (!success) {
- SDLLogE(@"Failed to send sub menu commands: %@", errors);
- completionHandler([NSError sdl_menuManager_failedToUpdateWithDictionary:errors]);
- return;
- }
-
- SDLLogD(@"Finished updating menu");
- completionHandler(nil);
- }];
}];
}
@@ -505,40 +316,6 @@ UInt32 const MenuCellIdMin = 1;
}
}
-#pragma mark Artworks
-
-- (NSArray<SDLArtwork *> *)sdl_findAllArtworksToBeUploadedFromCells:(NSArray<SDLMenuCell *> *)cells {
- if (![self.windowCapability hasImageFieldOfName:SDLImageFieldNameCommandIcon]) {
- return @[];
- }
-
- NSMutableSet<SDLArtwork *> *mutableArtworks = [NSMutableSet set];
- for (SDLMenuCell *cell in cells) {
- if ([self.fileManager fileNeedsUpload:cell.icon]) {
- [mutableArtworks addObject:cell.icon];
- }
-
- if (cell.subCells.count > 0) {
- [mutableArtworks addObjectsFromArray:[self sdl_findAllArtworksToBeUploadedFromCells:cell.subCells]];
- }
- }
-
- return [mutableArtworks allObjects];
-}
-
-- (BOOL)sdl_shouldRPCsIncludeImages:(NSArray<SDLMenuCell *> *)cells {
- for (SDLMenuCell *cell in cells) {
- SDLArtwork *artwork = cell.icon;
- if (artwork != nil && !artwork.isStaticIcon && ![self.fileManager hasUploadedFile:artwork]) {
- return NO;
- } else if (cell.subCells.count > 0) {
- return [self sdl_shouldRPCsIncludeImages:cell.subCells];
- }
- }
-
- return YES;
-}
-
#pragma mark IDs
- (void)sdl_updateIdsOnMenuCells:(NSArray<SDLMenuCell *> *)menuCells parentId:(UInt32)parentId {
@@ -551,106 +328,6 @@ UInt32 const MenuCellIdMin = 1;
}
}
-#pragma mark Deletes
-
-- (NSArray<SDLRPCRequest *> *)sdl_deleteCommandsForCells:(NSArray<SDLMenuCell *> *)cells {
- NSMutableArray<SDLRPCRequest *> *mutableDeletes = [NSMutableArray array];
- for (SDLMenuCell *cell in cells) {
- if (cell.subCells == nil) {
- SDLDeleteCommand *delete = [[SDLDeleteCommand alloc] initWithId:cell.cellId];
- [mutableDeletes addObject:delete];
- } else {
- SDLDeleteSubMenu *delete = [[SDLDeleteSubMenu alloc] initWithId:cell.cellId];
- [mutableDeletes addObject:delete];
- }
- }
-
- return [mutableDeletes copy];
-}
-
-#pragma mark Commands / SubMenu RPCs
-/**
- This method will receive the cells to be added and the updated menu array. It will then build an array of add commands using the correct index to position the new items in the correct location.
-
- @param cells that will be added to the menu, this array must contain only cells that are not already in the menu.
- @param shouldHaveArtwork artwork bool
- @param menu the new menu array, this array should contain all the values the develeoper has set to be included in the new menu. This is used for placing the newly added cells in the correct locaiton.
- e.g. If the new menu array is [A, B, C, D] but only [C, D] are new we need to pass [A, B , C , D] so C and D can be added to index 2 and 3 respectively.
- @return list of SDLRPCRequest addCommands
- */
-- (NSArray<SDLRPCRequest *> *)sdl_mainMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork usingIndexesFrom:(NSArray<SDLMenuCell *> *)menu {
- NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
-
- for (NSUInteger menuInteger = 0; menuInteger < menu.count; menuInteger++) {
- for (NSUInteger updateCellsIndex = 0; updateCellsIndex < cells.count; updateCellsIndex++) {
- if ([menu[menuInteger] isEqual:cells[updateCellsIndex]]) {
- if (cells[updateCellsIndex].subCells.count > 0) {
- [mutableCommands addObject:[self sdl_subMenuCommandForMenuCell:cells[updateCellsIndex] withArtwork:shouldHaveArtwork position:(UInt16)menuInteger]];
- } else {
- [mutableCommands addObject:[self sdl_commandForMenuCell:cells[updateCellsIndex] withArtwork:shouldHaveArtwork position:(UInt16)menuInteger]];
- }
- }
- }
- }
-
- return [mutableCommands copy];
-}
-
-- (NSArray<SDLRPCRequest *> *)sdl_subMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork {
- NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
- for (SDLMenuCell *cell in cells) {
- if (cell.subCells.count > 0) {
- [mutableCommands addObjectsFromArray:[self sdl_allCommandsForCells:cell.subCells withArtwork:shouldHaveArtwork]];
- }
- }
-
- return [mutableCommands copy];
-}
-
-- (NSArray<SDLRPCRequest *> *)sdl_allCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork {
- NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
-
- for (NSUInteger cellIndex = 0; cellIndex < cells.count; cellIndex++) {
- if (cells[cellIndex].subCells.count > 0) {
- [mutableCommands addObject:[self sdl_subMenuCommandForMenuCell:cells[cellIndex] withArtwork:shouldHaveArtwork position:(UInt16)cellIndex]];
- [mutableCommands addObjectsFromArray:[self sdl_allCommandsForCells:cells[cellIndex].subCells withArtwork:shouldHaveArtwork]];
- } else {
- [mutableCommands addObject:[self sdl_commandForMenuCell:cells[cellIndex] withArtwork:shouldHaveArtwork position:(UInt16)cellIndex]];
- }
- }
-
- return [mutableCommands copy];
-}
-
-- (SDLAddCommand *)sdl_commandForMenuCell:(SDLMenuCell *)cell withArtwork:(BOOL)shouldHaveArtwork position:(UInt16)position {
- SDLAddCommand *command = [[SDLAddCommand alloc] init];
-
- SDLMenuParams *params = [[SDLMenuParams alloc] init];
- params.menuName = cell.title;
- params.parentID = cell.parentCellId != UINT32_MAX ? @(cell.parentCellId) : nil;
- params.position = @(position);
-
- command.menuParams = params;
- command.vrCommands = (cell.voiceCommands.count == 0) ? nil : cell.voiceCommands;
- command.cmdIcon = (cell.icon && shouldHaveArtwork) ? cell.icon.imageRPC : nil;
- command.cmdID = @(cell.cellId);
-
- return command;
-}
-
-- (SDLAddSubMenu *)sdl_subMenuCommandForMenuCell:(SDLMenuCell *)cell withArtwork:(BOOL)shouldHaveArtwork position:(UInt16)position {
- SDLImage *icon = (shouldHaveArtwork && (cell.icon.name != nil)) ? cell.icon.imageRPC : nil;
-
- SDLMenuLayout submenuLayout = nil;
- if (cell.submenuLayout && [self.systemCapabilityManager.defaultMainWindowCapability.menuLayoutsAvailable containsObject:cell.submenuLayout]) {
- submenuLayout = cell.submenuLayout;
- } else {
- submenuLayout = self.menuConfiguration.defaultSubmenuLayout;
- }
-
- return [[SDLAddSubMenu alloc] initWithMenuID:cell.cellId menuName:cell.title position:@(position) menuIcon:icon menuLayout:submenuLayout parentID:nil];
-}
-
#pragma mark - Calling handlers
- (BOOL)sdl_callHandlerForCells:(NSArray<SDLMenuCell *> *)cells command:(SDLOnCommand *)onCommand {
diff --git a/SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.h b/SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.h
index c9fbb9b80..2a37d6c24 100644
--- a/SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.h
+++ b/SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.h
@@ -7,14 +7,24 @@
//
#import "SDLAsynchronousOperation.h"
-#import "SDLConnectionManagerType.h"
-#import "SDLFileManager.h"
+
+#import "SDLAsynchronousOperation.h"
+
+@protocol SDLConnectionManagerType;
+
+@class SDLFileManager;
+@class SDLMenuCell;
+@class SDLMenuConfiguration;
+@class SDLWindowCapability;
NS_ASSUME_NONNULL_BEGIN
@interface SDLMenuReplaceDynamicOperation : SDLAsynchronousOperation
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager;
+@property (strong, nonatomic) SDLWindowCapability *windowCapability;
+@property (strong, nonatomic) SDLMenuConfiguration *menuConfiguration;
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager windowCapability:(SDLWindowCapability *)windowCapability menuConfiguration:(SDLMenuConfiguration *)menuConfiguration currentMenu:(NSArray<SDLMenuCell *> *)currentMenu updatedMenu:(NSArray<SDLMenuCell *> *)updatedMenu;
@end
diff --git a/SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.m b/SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.m
index 18e09fd2c..1714cba0d 100644
--- a/SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.m
+++ b/SmartDeviceLink/private/SDLMenuReplaceDynamicOperation.m
@@ -8,6 +8,145 @@
#import "SDLMenuReplaceDynamicOperation.h"
+#import "SDLArtwork.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLDynamicMenuUpdateAlgorithm.h"
+#import "SDLDynamicMenuUpdateRunScore.h"
+#import "SDLError.h"
+#import "SDLFileManager.h"
+#import "SDLLogMacros.h"
+#import "SDLMenuCell.h"
+#import "SDLMenuConfiguration.h"
+#import "SDLMenuReplaceUtilities.h"
+#import "SDLTextFieldName.h"
+#import "SDLWindowCapability.h"
+#import "SDLWindowCapability+ScreenManagerExtensions.h"
+
+@interface SDLMenuReplaceDynamicOperation ()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (strong, nonatomic) NSArray<SDLMenuCell *> *currentMenu;
+@property (strong, nonatomic) NSArray<SDLMenuCell *> *updatedMenu;
+
+@property (copy, nonatomic, nullable) NSError *internalError;
+
+@end
+
@implementation SDLMenuReplaceDynamicOperation
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager windowCapability:(SDLWindowCapability *)windowCapability menuConfiguration:(SDLMenuConfiguration *)menuConfiguration currentMenu:(NSArray<SDLMenuCell *> *)currentMenu updatedMenu:(NSArray<SDLMenuCell *> *)updatedMenu {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+ _fileManager = fileManager;
+ _windowCapability = windowCapability;
+ _menuConfiguration = menuConfiguration;
+ _currentMenu = currentMenu;
+ _updatedMenu = updatedMenu;
+
+ return self;
+}
+
+- (void)start {
+ [super start];
+ if (self.isCancelled) {
+ [self finishOperation];
+ return;
+ }
+
+ SDLDynamicMenuUpdateRunScore *runScore = [SDLDynamicMenuUpdateAlgorithm compareOldMenuCells:self.currentMenu updatedMenuCells:self.updatedMenu];
+ NSArray<NSNumber *> *deleteMenuStatus = runScore.oldStatus;
+ NSArray<NSNumber *> *addMenuStatus = runScore.updatedStatus;
+
+ NSArray<SDLMenuCell *> *cellsToDelete = [self sdl_filterDeleteMenuItemsWithOldMenuItems:self.currentMenu basedOnStatusList:deleteMenuStatus];
+ NSArray<SDLMenuCell *> *cellsToAdd = [self sdl_filterAddMenuItemsWithNewMenuItems:self.updatedMenu basedOnStatusList:addMenuStatus];
+ // These arrays should ONLY contain KEEPS. These will be used for SubMenu compares
+ NSArray<SDLMenuCell *> *oldKeeps = [self sdl_filterKeepMenuItemsWithOldMenuItems:self.currentMenu basedOnStatusList:deleteMenuStatus];
+ NSArray<SDLMenuCell *> *newKeeps = [self sdl_filterKeepMenuItemsWithNewMenuItems:self.updatedMenu basedOnStatusList:addMenuStatus];
+
+ // Cells that will be added need new ids
+ [self sdl_updateIdsOnMenuCells:cellsToAdd parentId:ParentIdNotFound];
+
+ // Since we are creating a new Menu but keeping old cells we must firt transfer the old cellIDs to the new menus kept cells.
+ [self transferCellIDFromOldCells:oldKeeps toKeptCells:newKeeps];
+
+ // TODO: We don't check cancellation or finish
+ NSArray<SDLArtwork *> *artworksToBeUploaded = [SDLMenuReplaceUtilities findAllArtworksToBeUploadedFromCells:self.updatedMenu fileManager:self.fileManager windowCapability:self.windowCapability];
+ if (artworksToBeUploaded.count > 0) {
+ [self.fileManager uploadArtworks:artworksToBeUploaded completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error uploading menu artworks: %@", error);
+ }
+
+ SDLLogD(@"Menu artworks uploaded");
+ __weak typeof(self) weakself = self;
+ [self sdl_updateMenuWithCellsToDelete:cellsToDelete cellsToAdd:cellsToAdd completionHandler:^(NSError * _Nullable error) {
+ [weakself sdl_startSubMenuUpdatesWithOldKeptCells:oldKeeps newKeptCells:newKeeps atIndex:0];
+ }];
+ }];
+ } else {
+ // Cells have no artwork to load
+ __weak typeof(self) weakself = self;
+ [self sdl_updateMenuWithCellsToDelete:cellsToDelete cellsToAdd:cellsToAdd completionHandler:^(NSError * _Nullable error) {
+ [weakself sdl_startSubMenuUpdatesWithOldKeptCells:oldKeeps newKeptCells:newKeeps atIndex:0];
+ }];
+ }
+}
+
+#pragma mark Dynamic Menu Helpers
+
+- (NSArray<SDLMenuCell *> *)sdl_filterDeleteMenuItemsWithOldMenuItems:(NSArray<SDLMenuCell *> *)oldMenuCells basedOnStatusList:(NSArray<NSNumber *> *)oldStatusList {
+ NSMutableArray<SDLMenuCell *> *deleteCells = [[NSMutableArray alloc] init];
+ // The index of the status should corrleate 1-1 with the number of items in the menu
+ // [2,0,2,0] => [A,B,C,D] = [B,D]
+ for (NSUInteger index = 0; index < oldStatusList.count; index++) {
+ if (oldStatusList[index].integerValue == MenuCellStateDelete) {
+ [deleteCells addObject:oldMenuCells[index]];
+ }
+ }
+ return [deleteCells copy];
+}
+
+- (NSArray<SDLMenuCell *> *)sdl_filterAddMenuItemsWithNewMenuItems:(NSArray<SDLMenuCell *> *)newMenuCells basedOnStatusList:(NSArray<NSNumber *> *)newStatusList {
+ NSMutableArray<SDLMenuCell *> *addCells = [[NSMutableArray alloc] init];
+ // The index of the status should corrleate 1-1 with the number of items in the menu
+ // [2,1,2,1] => [A,B,C,D] = [B,D]
+ for (NSUInteger index = 0; index < newStatusList.count; index++) {
+ if (newStatusList[index].integerValue == MenuCellStateAdd) {
+ [addCells addObject:newMenuCells[index]];
+ }
+ }
+ return [addCells copy];
+}
+
+- (NSArray<SDLMenuCell *> *)sdl_filterKeepMenuItemsWithOldMenuItems:(NSArray<SDLMenuCell *> *)oldMenuCells basedOnStatusList:(NSArray<NSNumber *> *)keepStatusList {
+ NSMutableArray<SDLMenuCell *> *keepMenuCells = [[NSMutableArray alloc] init];
+
+ for (NSUInteger index = 0; index < keepStatusList.count; index++) {
+ if (keepStatusList[index].integerValue == MenuCellStateKeep) {
+ [keepMenuCells addObject:oldMenuCells[index]];
+ }
+ }
+ return [keepMenuCells copy];
+}
+
+- (NSArray<SDLMenuCell *> *)sdl_filterKeepMenuItemsWithNewMenuItems:(NSArray<SDLMenuCell *> *)newMenuCells basedOnStatusList:(NSArray<NSNumber *> *)keepStatusList {
+ NSMutableArray<SDLMenuCell *> *keepMenuCells = [[NSMutableArray alloc] init];
+ for (NSUInteger index = 0; index < keepStatusList.count; index++) {
+ if (keepStatusList[index].integerValue == MenuCellStateKeep) {
+ [keepMenuCells addObject:newMenuCells[index]];
+ }
+ }
+ return [keepMenuCells copy];
+}
+
+- (void)transferCellIDFromOldCells:(NSArray<SDLMenuCell *> *)oldCells toKeptCells:(NSArray<SDLMenuCell *> *)newCells {
+ if (oldCells.count == 0) { return; }
+ for (NSUInteger i = 0; i < newCells.count; i++) {
+ newCells[i].cellId = oldCells[i].cellId;
+ }
+}
+
@end
diff --git a/SmartDeviceLink/private/SDLMenuReplaceStaticOperation.h b/SmartDeviceLink/private/SDLMenuReplaceStaticOperation.h
index fd093c92b..25895f7c5 100644
--- a/SmartDeviceLink/private/SDLMenuReplaceStaticOperation.h
+++ b/SmartDeviceLink/private/SDLMenuReplaceStaticOperation.h
@@ -9,14 +9,22 @@
#import <Foundation/Foundation.h>
#import "SDLAsynchronousOperation.h"
-#import "SDLConnectionManagerType.h"
-#import "SDLFileManager.h"
+
+@protocol SDLConnectionManagerType;
+
+@class SDLFileManager;
+@class SDLMenuCell;
+@class SDLMenuConfiguration;
+@class SDLWindowCapability;
NS_ASSUME_NONNULL_BEGIN
@interface SDLMenuReplaceStaticOperation : SDLAsynchronousOperation
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager;
+@property (strong, nonatomic) SDLWindowCapability *windowCapability;
+@property (strong, nonatomic) SDLMenuConfiguration *menuConfiguration;
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager windowCapability:(SDLWindowCapability *)windowCapability menuConfiguration:(SDLMenuConfiguration *)menuConfiguration currentMenu:(NSArray<SDLMenuCell *> *)currentMenu updatedMenu:(NSArray<SDLMenuCell *> *)updatedMenu;
@end
diff --git a/SmartDeviceLink/private/SDLMenuReplaceStaticOperation.m b/SmartDeviceLink/private/SDLMenuReplaceStaticOperation.m
index f1d42c14e..740f871c0 100644
--- a/SmartDeviceLink/private/SDLMenuReplaceStaticOperation.m
+++ b/SmartDeviceLink/private/SDLMenuReplaceStaticOperation.m
@@ -8,6 +8,184 @@
#import "SDLMenuReplaceStaticOperation.h"
+#import "SDLArtwork.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLError.h"
+#import "SDLFileManager.h"
+#import "SDLLogMacros.h"
+#import "SDLMenuCell.h"
+#import "SDLMenuConfiguration.h"
+#import "SDLMenuReplaceUtilities.h"
+#import "SDLTextFieldName.h"
+#import "SDLWindowCapability.h"
+#import "SDLWindowCapability+ScreenManagerExtensions.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void(^SDLMenuUpdateCompletionHandler)(NSError *__nullable error);
+
+@interface SDLMenuReplaceStaticOperation ()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (strong, nonatomic) NSArray<SDLMenuCell *> *currentMenu;
+@property (strong, nonatomic) NSArray<SDLMenuCell *> *updatedMenu;
+
+@property (copy, nonatomic, nullable) NSError *internalError;
+
+@end
+
@implementation SDLMenuReplaceStaticOperation
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager windowCapability:(SDLWindowCapability *)windowCapability menuConfiguration:(SDLMenuConfiguration *)menuConfiguration currentMenu:(NSArray<SDLMenuCell *> *)currentMenu updatedMenu:(NSArray<SDLMenuCell *> *)updatedMenu {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+ _fileManager = fileManager;
+ _windowCapability = windowCapability;
+ _menuConfiguration = menuConfiguration;
+ _currentMenu = currentMenu;
+ _updatedMenu = updatedMenu;
+
+ return self;
+}
+
+- (void)start {
+ [super start];
+ if (self.isCancelled) {
+ [self finishOperation];
+ return;
+ }
+
+ // TODO: We don't check cancellation or finish
+
+ NSArray<SDLArtwork *> *artworksToBeUploaded = [SDLMenuReplaceUtilities findAllArtworksToBeUploadedFromCells:self.updatedMenu fileManager:self.fileManager windowCapability:self.windowCapability];
+ if (artworksToBeUploaded.count > 0) {
+ [self.fileManager uploadArtworks:artworksToBeUploaded completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error uploading menu artworks: %@", error);
+ }
+
+ SDLLogD(@"Menu artworks uploaded");
+ [self sdl_updateMenuWithCellsToDelete:self.currentMenu cellsToAdd:self.updatedMenu completionHandler:nil];
+ }];
+ } else {
+ // Cells have no artwork to load
+ [self sdl_updateMenuWithCellsToDelete:self.currentMenu cellsToAdd:self.updatedMenu completionHandler:nil];
+ }
+}
+
+#pragma mark - Private Helpers
+
+#pragma mark Sending Items
+
+- (void)sdl_updateMenuWithCellsToDelete:(NSArray<SDLMenuCell *> *)deleteCells cellsToAdd:(NSArray<SDLMenuCell *> *)addCells completionHandler:(nullable SDLMenuUpdateCompletionHandler)completionHandler {
+ __weak typeof(self) weakself = self;
+ [self sdl_sendDeleteCurrentMenu:deleteCells withCompletionHandler:^(NSError * _Nullable error) {
+ [weakself sdl_sendNewMenuCells:addCells withCompletionHandler:^(NSError * _Nullable error) { }];
+ }];
+}
+
+/**
+ Creates add commands
+
+ @param newMenuCells The cells you will be adding
+ @param completionHandler handler
+ */
+- (void)sdl_sendNewMenuCells:(NSArray<SDLMenuCell *> *)newMenuCells withCompletionHandler:(SDLMenuUpdateCompletionHandler)completionHandler {
+ if (self.updatedMenu.count == 0 || newMenuCells.count == 0) {
+ SDLLogD(@"There are no cells to update.");
+ completionHandler(nil);
+ return;
+ }
+
+ NSArray<SDLRPCRequest *> *mainMenuCommands = nil;
+ NSArray<SDLRPCRequest *> *subMenuCommands = nil;
+
+ if (![SDLMenuReplaceUtilities shouldRPCsIncludeImages:self.updatedMenu fileManager:self.fileManager] || ![self.windowCapability hasImageFieldOfName:SDLImageFieldNameCommandIcon]) {
+ // Send artwork-less menu
+ mainMenuCommands = [SDLMenuReplaceUtilities mainMenuCommandsForCells:newMenuCells withArtwork:NO usingIndexesFrom:self.updatedMenu availableMenuLayouts:self.windowCapability.menuLayoutsAvailable defaultSubmenuLayout:self.menuConfiguration.defaultSubmenuLayout];
+ subMenuCommands = [SDLMenuReplaceUtilities subMenuCommandsForCells:newMenuCells withArtwork:NO availableMenuLayouts:self.windowCapability.menuLayoutsAvailable defaultSubmenuLayout:self.menuConfiguration.defaultSubmenuLayout];
+ } else {
+ // Send full artwork menu
+ mainMenuCommands = [SDLMenuReplaceUtilities mainMenuCommandsForCells:newMenuCells withArtwork:YES usingIndexesFrom:self.updatedMenu availableMenuLayouts:self.windowCapability.menuLayoutsAvailable defaultSubmenuLayout:self.menuConfiguration.defaultSubmenuLayout];
+ subMenuCommands = [SDLMenuReplaceUtilities subMenuCommandsForCells:newMenuCells withArtwork:YES availableMenuLayouts:self.windowCapability.menuLayoutsAvailable defaultSubmenuLayout:self.menuConfiguration.defaultSubmenuLayout];
+ }
+
+ __block NSMutableDictionary<SDLRPCRequest *, NSError *> *errors = [NSMutableDictionary dictionary];
+ __weak typeof(self) weakSelf = self;
+ [self.connectionManager sendRequests:mainMenuCommands progressHandler:^void(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ if (error != nil) {
+ errors[request] = error;
+ }
+ } completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogE(@"Failed to send main menu commands: %@", errors);
+ completionHandler([NSError sdl_menuManager_failedToUpdateWithDictionary:errors]);
+ return;
+ }
+
+ [weakSelf.connectionManager sendRequests:subMenuCommands progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ if (error != nil) {
+ errors[request] = error;
+ }
+ } completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogE(@"Failed to send sub menu commands: %@", errors);
+ completionHandler([NSError sdl_menuManager_failedToUpdateWithDictionary:errors]);
+ return;
+ }
+
+ SDLLogD(@"Finished updating menu");
+ completionHandler(nil);
+ }];
+ }];
+}
+
+#pragma mark Delete Old Menu Items
+
+- (void)sdl_sendDeleteCurrentMenu:(nullable NSArray<SDLMenuCell *> *)deleteMenuCells withCompletionHandler:(SDLMenuUpdateCompletionHandler)completionHandler {
+ if (deleteMenuCells.count == 0) {
+ completionHandler(nil);
+ return;
+ }
+
+ NSArray<SDLRPCRequest *> *deleteMenuCommands = [SDLMenuReplaceUtilities deleteCommandsForCells:deleteMenuCells];
+ [self.connectionManager sendRequests:deleteMenuCommands progressHandler:nil completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogW(@"Unable to delete all old menu commands");
+ } else {
+ SDLLogD(@"Finished deleting old menu");
+ }
+
+ completionHandler(nil);
+ }];
+}
+
+#pragma mark - Operation Overrides
+
+- (void)finishOperation {
+ SDLLogV(@"Finishing menu manager configuration update operation");
+ if (self.isCancelled) {
+ self.internalError = [NSError sdl_menuManager_openMenuOperationCancelled];
+ }
+
+ [super finishOperation];
+}
+
+- (nullable NSString *)name {
+ return @"com.sdl.menuManager.replaceMenu.static";
+}
+
+- (NSOperationQueuePriority)queuePriority {
+ return NSOperationQueuePriorityNormal;
+}
+
+- (nullable NSError *)error {
+ return self.internalError;
+}
+
@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLMenuReplaceUtilities.h b/SmartDeviceLink/private/SDLMenuReplaceUtilities.h
new file mode 100644
index 000000000..19eb28799
--- /dev/null
+++ b/SmartDeviceLink/private/SDLMenuReplaceUtilities.h
@@ -0,0 +1,42 @@
+//
+// SDLMenuReplaceUtilities.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 1/22/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLMenuLayout.h"
+
+@class SDLArtwork;
+@class SDLFileManager;
+@class SDLMenuCell;
+@class SDLRPCRequest;
+@class SDLWindowCapability;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLMenuReplaceUtilities : NSObject
+
++ (NSArray<SDLRPCRequest *> *)deleteCommandsForCells:(NSArray<SDLMenuCell *> *)cells;
+
+/**
+ This method will receive the cells to be added and the updated menu array. It will then build an array of add commands using the correct index to position the new items in the correct location.
+
+ @param cells that will be added to the menu, this array must contain only cells that are not already in the menu.
+ @param shouldHaveArtwork artwork bool
+ @param menu the new menu array, this array should contain all the values the develeoper has set to be included in the new menu. This is used for placing the newly added cells in the correct locaiton.
+ e.g. If the new menu array is [A, B, C, D] but only [C, D] are new we need to pass [A, B , C , D] so C and D can be added to index 2 and 3 respectively.
+ @return list of SDLRPCRequest addCommands
+ */
++ (NSArray<SDLRPCRequest *> *)mainMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork usingIndexesFrom:(NSArray<SDLMenuCell *> *)menu availableMenuLayouts:(NSArray<SDLMenuLayout> *)availableMenuLayouts defaultSubmenuLayout:(SDLMenuLayout)defaultSubmenuLayout;
++ (NSArray<SDLRPCRequest *> *)subMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork availableMenuLayouts:(NSArray<SDLMenuLayout> *)availableMenuLayouts defaultSubmenuLayout:(SDLMenuLayout)defaultSubmenuLayout;
+
++ (NSArray<SDLArtwork *> *)findAllArtworksToBeUploadedFromCells:(NSArray<SDLMenuCell *> *)cells fileManager:(SDLFileManager *)fileManager windowCapability:(SDLWindowCapability *)windowCapability;
++ (BOOL)shouldRPCsIncludeImages:(NSArray<SDLMenuCell *> *)cells fileManager:(SDLFileManager *)fileManager;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLMenuReplaceUtilities.m b/SmartDeviceLink/private/SDLMenuReplaceUtilities.m
new file mode 100644
index 000000000..4c3d47aee
--- /dev/null
+++ b/SmartDeviceLink/private/SDLMenuReplaceUtilities.m
@@ -0,0 +1,158 @@
+//
+// SDLMenuReplaceUtilities.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 1/22/21.
+// Copyright © 2021 smartdevicelink. All rights reserved.
+//
+
+#import "SDLMenuReplaceUtilities.h"
+
+#import "SDLAddCommand.h"
+#import "SDLAddSubMenu.h"
+#import "SDLArtwork.h"
+#import "SDLDeleteCommand.h"
+#import "SDLDeleteSubMenu.h"
+#import "SDLFileManager.h"
+#import "SDLImage.h"
+#import "SDLImageFieldName.h"
+#import "SDLMenuCell.h"
+#import "SDLMenuParams.h"
+#import "SDLRPCRequest.h"
+#import "SDLWindowCapability.h"
+#import "SDLWindowCapability+ScreenManagerExtensions.h"
+
+@interface SDLMenuCell()
+
+@property (assign, nonatomic) UInt32 parentCellId;
+@property (assign, nonatomic) UInt32 cellId;
+
+@end
+
+@implementation SDLMenuReplaceUtilities
+
+#pragma mark Delete Commands
+
++ (NSArray<SDLRPCRequest *> *)deleteCommandsForCells:(NSArray<SDLMenuCell *> *)cells {
+ NSMutableArray<SDLRPCRequest *> *mutableDeletes = [NSMutableArray array];
+ for (SDLMenuCell *cell in cells) {
+ if (cell.subCells == nil) {
+ SDLDeleteCommand *delete = [[SDLDeleteCommand alloc] initWithId:cell.cellId];
+ [mutableDeletes addObject:delete];
+ } else {
+ SDLDeleteSubMenu *delete = [[SDLDeleteSubMenu alloc] initWithId:cell.cellId];
+ [mutableDeletes addObject:delete];
+ }
+ }
+
+ return [mutableDeletes copy];
+}
+
+#pragma mark Artworks
+
++ (NSArray<SDLArtwork *> *)findAllArtworksToBeUploadedFromCells:(NSArray<SDLMenuCell *> *)cells fileManager:(SDLFileManager *)fileManager windowCapability:(SDLWindowCapability *)windowCapability {
+ if (![windowCapability hasImageFieldOfName:SDLImageFieldNameCommandIcon]) {
+ return @[];
+ }
+
+ NSMutableSet<SDLArtwork *> *mutableArtworks = [NSMutableSet set];
+ for (SDLMenuCell *cell in cells) {
+ if ([fileManager fileNeedsUpload:cell.icon]) {
+ [mutableArtworks addObject:cell.icon];
+ }
+
+ if (cell.subCells.count > 0) {
+ [mutableArtworks addObjectsFromArray:[self findAllArtworksToBeUploadedFromCells:cell.subCells fileManager:fileManager windowCapability:windowCapability]];
+ }
+ }
+
+ return [mutableArtworks allObjects];
+}
+
++ (BOOL)shouldRPCsIncludeImages:(NSArray<SDLMenuCell *> *)cells fileManager:(SDLFileManager *)fileManager {
+ for (SDLMenuCell *cell in cells) {
+ SDLArtwork *artwork = cell.icon;
+ if (artwork != nil && !artwork.isStaticIcon && ![fileManager hasUploadedFile:artwork]) {
+ return NO;
+ } else if (cell.subCells.count > 0) {
+ return [self shouldRPCsIncludeImages:cell.subCells fileManager:fileManager];
+ }
+ }
+
+ return YES;
+}
+
++ (NSArray<SDLRPCRequest *> *)mainMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork usingIndexesFrom:(NSArray<SDLMenuCell *> *)menu availableMenuLayouts:(NSArray<SDLMenuLayout> *)availableMenuLayouts defaultSubmenuLayout:(SDLMenuLayout)defaultSubmenuLayout {
+ NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
+
+ for (NSUInteger menuInteger = 0; menuInteger < menu.count; menuInteger++) {
+ for (NSUInteger updateCellsIndex = 0; updateCellsIndex < cells.count; updateCellsIndex++) {
+ if ([menu[menuInteger] isEqual:cells[updateCellsIndex]]) {
+ if (cells[updateCellsIndex].subCells.count > 0) {
+ [mutableCommands addObject:[self sdl_subMenuCommandForMenuCell:cells[updateCellsIndex] withArtwork:shouldHaveArtwork position:(UInt16)menuInteger availableMenuLayouts:availableMenuLayouts defaultSubmenuLayout:defaultSubmenuLayout]];
+ } else {
+ [mutableCommands addObject:[self sdl_commandForMenuCell:cells[updateCellsIndex] withArtwork:shouldHaveArtwork position:(UInt16)menuInteger]];
+ }
+ }
+ }
+ }
+
+ return [mutableCommands copy];
+}
+
++ (NSArray<SDLRPCRequest *> *)subMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork availableMenuLayouts:(NSArray<SDLMenuLayout> *)availableMenuLayouts defaultSubmenuLayout:(SDLMenuLayout)defaultSubmenuLayout {
+ NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
+ for (SDLMenuCell *cell in cells) {
+ if (cell.subCells.count > 0) {
+ [mutableCommands addObjectsFromArray:[self sdl_allCommandsForCells:cell.subCells withArtwork:shouldHaveArtwork availableMenuLayouts:availableMenuLayouts defaultSubmenuLayout:defaultSubmenuLayout]];
+ }
+ }
+
+ return [mutableCommands copy];
+}
+
++ (NSArray<SDLRPCRequest *> *)sdl_allCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork availableMenuLayouts:(NSArray<SDLMenuLayout> *)availableMenuLayouts defaultSubmenuLayout:(SDLMenuLayout)defaultSubmenuLayout {
+ NSMutableArray<SDLRPCRequest *> *mutableCommands = [NSMutableArray array];
+
+ for (NSUInteger cellIndex = 0; cellIndex < cells.count; cellIndex++) {
+ if (cells[cellIndex].subCells.count > 0) {
+ [mutableCommands addObject:[self sdl_subMenuCommandForMenuCell:cells[cellIndex] withArtwork:shouldHaveArtwork position:(UInt16)cellIndex availableMenuLayouts:availableMenuLayouts defaultSubmenuLayout:defaultSubmenuLayout]];
+ [mutableCommands addObjectsFromArray:[self sdl_allCommandsForCells:cells[cellIndex].subCells withArtwork:shouldHaveArtwork availableMenuLayouts:availableMenuLayouts defaultSubmenuLayout:defaultSubmenuLayout]];
+ } else {
+ [mutableCommands addObject:[self sdl_commandForMenuCell:cells[cellIndex] withArtwork:shouldHaveArtwork position:(UInt16)cellIndex]];
+ }
+ }
+
+ return [mutableCommands copy];
+}
+
++ (SDLAddCommand *)sdl_commandForMenuCell:(SDLMenuCell *)cell withArtwork:(BOOL)shouldHaveArtwork position:(UInt16)position {
+ SDLAddCommand *command = [[SDLAddCommand alloc] init];
+
+ SDLMenuParams *params = [[SDLMenuParams alloc] init];
+ params.menuName = cell.title;
+ params.parentID = cell.parentCellId != UINT32_MAX ? @(cell.parentCellId) : nil;
+ params.position = @(position);
+
+ command.menuParams = params;
+ command.vrCommands = (cell.voiceCommands.count == 0) ? nil : cell.voiceCommands;
+ command.cmdIcon = (cell.icon && shouldHaveArtwork) ? cell.icon.imageRPC : nil;
+ command.cmdID = @(cell.cellId);
+
+ return command;
+}
+
++ (SDLAddSubMenu *)sdl_subMenuCommandForMenuCell:(SDLMenuCell *)cell withArtwork:(BOOL)shouldHaveArtwork position:(UInt16)position availableMenuLayouts:(NSArray<SDLMenuLayout> *)availableMenuLayouts defaultSubmenuLayout:(SDLMenuLayout)defaultSubmenuLayout {
+ SDLImage *icon = (shouldHaveArtwork && (cell.icon.name != nil)) ? cell.icon.imageRPC : nil;
+
+ SDLMenuLayout submenuLayout = nil;
+ if (cell.submenuLayout && [availableMenuLayouts containsObject:cell.submenuLayout]) {
+ submenuLayout = cell.submenuLayout;
+ } else {
+ submenuLayout = defaultSubmenuLayout;
+ }
+
+ return [[SDLAddSubMenu alloc] initWithMenuID:cell.cellId menuName:cell.title position:@(position) menuIcon:icon menuLayout:submenuLayout parentID:nil];
+}
+
+@end