diff --git a/BVSDK.podspec b/BVSDK.podspec index bd31fd6e..72d07a7f 100644 --- a/BVSDK.podspec +++ b/BVSDK.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.name = "BVSDK" - s.version = '6.7.0' + s.version = '6.7.1' s.homepage = 'https://developer.bazaarvoice.com' s.license = { :type => 'Commercial', :text => 'See https://developer.bazaarvoice.com/API_Terms_of_Use' } s.author = { 'Bazaarvoice' => 'support@bazaarvoice.com' } diff --git a/BVSDK.xcodeproj/project.pbxproj b/BVSDK.xcodeproj/project.pbxproj index ea5c7bd2..cd117218 100644 --- a/BVSDK.xcodeproj/project.pbxproj +++ b/BVSDK.xcodeproj/project.pbxproj @@ -105,6 +105,8 @@ 8779DB251DD20D5D00E6CAF5 /* BVStoreReviewsTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8779DB241DD20D5D00E6CAF5 /* BVStoreReviewsTableView.m */; }; 8779DB271DD20D8D00E6CAF5 /* BVStoreReviewsTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 8779DB261DD20D8D00E6CAF5 /* BVStoreReviewsTableView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 8779DB6D1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8779DB6C1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m */; }; + 877AD2941EE1D134006C7070 /* BVBaseUGCSubmission.h in Headers */ = {isa = PBXBuildFile; fileRef = 877AD2921EE1D134006C7070 /* BVBaseUGCSubmission.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 877AD2951EE1D134006C7070 /* BVBaseUGCSubmission.m in Sources */ = {isa = PBXBuildFile; fileRef = 877AD2931EE1D134006C7070 /* BVBaseUGCSubmission.m */; }; 879A628B1E80502100F46ECA /* BVBasePIIEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 879A62891E80502100F46ECA /* BVBasePIIEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 879A628C1E80502100F46ECA /* BVBasePIIEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 879A628A1E80502100F46ECA /* BVBasePIIEvent.m */; }; 879D09111DCA8014006B51D3 /* testNotificationConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = 879D09101DCA8014006B51D3 /* testNotificationConfig.json */; }; @@ -178,6 +180,15 @@ 87E810EA1ECCA3CF0032C753 /* BVCommentFilterType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87E810E81ECCA3CF0032C753 /* BVCommentFilterType.m */; }; 87E810ED1ECCC4CD0032C753 /* BVCommentIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87E810EB1ECCC4CD0032C753 /* BVCommentIncludeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87E810EE1ECCC4CD0032C753 /* BVCommentIncludeType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87E810EC1ECCC4CD0032C753 /* BVCommentIncludeType.m */; }; + 87EDAFC91EDDC6A900FA07C0 /* CommentSubmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87EDAFC81EDDC6A900FA07C0 /* CommentSubmissionTests.swift */; }; + 87EDAFCC1EDDC9F400FA07C0 /* BVCommentSubmission.h in Headers */ = {isa = PBXBuildFile; fileRef = 87EDAFCA1EDDC9F400FA07C0 /* BVCommentSubmission.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87EDAFCD1EDDC9F400FA07C0 /* BVCommentSubmission.m in Sources */ = {isa = PBXBuildFile; fileRef = 87EDAFCB1EDDC9F400FA07C0 /* BVCommentSubmission.m */; }; + 87EDAFD11EDDD02F00FA07C0 /* BVCommentSubmissionResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 87EDAFCF1EDDD02F00FA07C0 /* BVCommentSubmissionResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87EDAFD21EDDD02F00FA07C0 /* BVCommentSubmissionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 87EDAFD01EDDD02F00FA07C0 /* BVCommentSubmissionResponse.m */; }; + 87EDAFD51EDDD1FD00FA07C0 /* BVSubmittedComment.h in Headers */ = {isa = PBXBuildFile; fileRef = 87EDAFD31EDDD1FD00FA07C0 /* BVSubmittedComment.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87EDAFD61EDDD1FD00FA07C0 /* BVSubmittedComment.m in Sources */ = {isa = PBXBuildFile; fileRef = 87EDAFD41EDDD1FD00FA07C0 /* BVSubmittedComment.m */; }; + 87EDAFD91EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 87EDAFD71EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87EDAFDA1EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 87EDAFD81EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.m */; }; 87F2DAB11DAD579D00FB43F3 /* BVSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87F2DAA71DAD579D00FB43F3 /* BVSDK.framework */; }; 87F2DAB81DAD579D00FB43F3 /* BVSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DAAA1DAD579D00FB43F3 /* BVSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87F2DC171DAD585E00FB43F3 /* BVAdsMessageInterceptor.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DAC31DAD585E00FB43F3 /* BVAdsMessageInterceptor.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -601,6 +612,8 @@ 8779DB241DD20D5D00E6CAF5 /* BVStoreReviewsTableView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BVStoreReviewsTableView.m; path = Stores/BVStoreReviewsTableView.m; sourceTree = ""; }; 8779DB261DD20D8D00E6CAF5 /* BVStoreReviewsTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVStoreReviewsTableView.h; path = Stores/BVStoreReviewsTableView.h; sourceTree = ""; }; 8779DB6C1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVInternalAnalyticsTest.m; path = Tests/Tests/BVInternalAnalyticsTest.m; sourceTree = SOURCE_ROOT; }; + 877AD2921EE1D134006C7070 /* BVBaseUGCSubmission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVBaseUGCSubmission.h; sourceTree = ""; }; + 877AD2931EE1D134006C7070 /* BVBaseUGCSubmission.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBaseUGCSubmission.m; sourceTree = ""; }; 879A62891E80502100F46ECA /* BVBasePIIEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVBasePIIEvent.h; path = BVPixelEvents/BVBasePIIEvent.h; sourceTree = ""; }; 879A628A1E80502100F46ECA /* BVBasePIIEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVBasePIIEvent.m; path = BVPixelEvents/BVBasePIIEvent.m; sourceTree = ""; }; 879A62DD1E80621900F46ECA /* BVPixelEvents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BVPixelEvents.h; path = BVPixelEvents/BVPixelEvents.h; sourceTree = ""; }; @@ -677,6 +690,15 @@ 87E810E81ECCA3CF0032C753 /* BVCommentFilterType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVCommentFilterType.m; sourceTree = ""; }; 87E810EB1ECCC4CD0032C753 /* BVCommentIncludeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVCommentIncludeType.h; sourceTree = ""; }; 87E810EC1ECCC4CD0032C753 /* BVCommentIncludeType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVCommentIncludeType.m; sourceTree = ""; }; + 87EDAFC81EDDC6A900FA07C0 /* CommentSubmissionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentSubmissionTests.swift; sourceTree = ""; }; + 87EDAFCA1EDDC9F400FA07C0 /* BVCommentSubmission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVCommentSubmission.h; path = Pod/BVConversations/Submission/Comment/BVCommentSubmission.h; sourceTree = SOURCE_ROOT; }; + 87EDAFCB1EDDC9F400FA07C0 /* BVCommentSubmission.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVCommentSubmission.m; path = Pod/BVConversations/Submission/Comment/BVCommentSubmission.m; sourceTree = SOURCE_ROOT; }; + 87EDAFCF1EDDD02F00FA07C0 /* BVCommentSubmissionResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVCommentSubmissionResponse.h; path = Comment/BVCommentSubmissionResponse.h; sourceTree = ""; }; + 87EDAFD01EDDD02F00FA07C0 /* BVCommentSubmissionResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVCommentSubmissionResponse.m; path = Comment/BVCommentSubmissionResponse.m; sourceTree = ""; }; + 87EDAFD31EDDD1FD00FA07C0 /* BVSubmittedComment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVSubmittedComment.h; path = Comment/BVSubmittedComment.h; sourceTree = ""; }; + 87EDAFD41EDDD1FD00FA07C0 /* BVSubmittedComment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVSubmittedComment.m; path = Comment/BVSubmittedComment.m; sourceTree = ""; }; + 87EDAFD71EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVCommentSubmissionErrorResponse.h; path = Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.h; sourceTree = SOURCE_ROOT; }; + 87EDAFD81EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVCommentSubmissionErrorResponse.m; path = Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.m; sourceTree = SOURCE_ROOT; }; 87F2DAA71DAD579D00FB43F3 /* BVSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BVSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 87F2DAAA1DAD579D00FB43F3 /* BVSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVSDK.h; sourceTree = ""; }; 87F2DAAB1DAD579D00FB43F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -1204,6 +1226,21 @@ name = BVPixelEvents; sourceTree = ""; }; + 87EDAFCE1EDDCEAD00FA07C0 /* Comment */ = { + isa = PBXGroup; + children = ( + 87EDAFCA1EDDC9F400FA07C0 /* BVCommentSubmission.h */, + 87EDAFCB1EDDC9F400FA07C0 /* BVCommentSubmission.m */, + 87EDAFCF1EDDD02F00FA07C0 /* BVCommentSubmissionResponse.h */, + 87EDAFD01EDDD02F00FA07C0 /* BVCommentSubmissionResponse.m */, + 87EDAFD71EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.h */, + 87EDAFD81EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.m */, + 87EDAFD31EDDD1FD00FA07C0 /* BVSubmittedComment.h */, + 87EDAFD41EDDD1FD00FA07C0 /* BVSubmittedComment.m */, + ); + name = Comment; + sourceTree = ""; + }; 87F2DA9D1DAD579D00FB43F3 = { isa = PBXGroup; children = ( @@ -1354,6 +1391,7 @@ 87F2DB121DAD585E00FB43F3 /* Model */ = { isa = PBXGroup; children = ( + 87F2DB4A1DAD585E00FB43F3 /* GenericConversationsResult */, 87C5FF5A1E36A8B7004EE6E8 /* BVAuthorResponse.h */, 87C5FF5B1E36A8B7004EE6E8 /* BVAuthorResponse.m */, 87F2DB131DAD585E00FB43F3 /* BVBadge.h */, @@ -1417,7 +1455,6 @@ 87F2DB471DAD585E00FB43F3 /* BVSecondaryRatingsAverages.m */, 87F2DB481DAD585E00FB43F3 /* BVVideo.h */, 87F2DB491DAD585E00FB43F3 /* BVVideo.m */, - 87F2DB4A1DAD585E00FB43F3 /* GenericConversationsResult */, 87F2DB581DAD585E00FB43F3 /* Stores */, ); path = Model; @@ -1551,6 +1588,8 @@ 87F2DB961DAD585E00FB43F3 /* BVFormFieldOptions.h */, 87F2DB971DAD585E00FB43F3 /* BVFormFieldOptions.m */, 87F2DB981DAD585E00FB43F3 /* BVSubmission.h */, + 877AD2921EE1D134006C7070 /* BVBaseUGCSubmission.h */, + 877AD2931EE1D134006C7070 /* BVBaseUGCSubmission.m */, 87F2DB991DAD585E00FB43F3 /* BVSubmission.m */, 87F2DB9A1DAD585E00FB43F3 /* BVSubmissionAction.h */, 87F2DB9B1DAD585E00FB43F3 /* BVSubmissionAction.m */, @@ -1559,6 +1598,7 @@ 87F2DB9E1DAD585E00FB43F3 /* BVSubmissionResponse.h */, 87F2DB9F1DAD585E00FB43F3 /* BVSubmissionResponse.m */, 87F2DB891DAD585E00FB43F3 /* Answer */, + 87EDAFCE1EDDCEAD00FA07C0 /* Comment */, 87F2DBA01DAD585E00FB43F3 /* Photo */, 87A02B5D1E0963020002701B /* Feedback */, 87F2DBA61DAD585E00FB43F3 /* Question */, @@ -1967,6 +2007,7 @@ isa = PBXGroup; children = ( 87F2DE031DAD945D00FB43F3 /* AnswerSubmissionTests.swift */, + 87EDAFC81EDDC6A900FA07C0 /* CommentSubmissionTests.swift */, 87F2DE041DAD945D00FB43F3 /* PhotoUploadTests.swift */, 15D4F83E1DF5EC3C00E6B30D /* ph.png */, 87F2DE051DAD945D00FB43F3 /* QuestionSubmissionTests.swift */, @@ -2199,6 +2240,11 @@ 87E810E91ECCA3CF0032C753 /* BVCommentFilterType.h in Headers */, 87E810ED1ECCC4CD0032C753 /* BVCommentIncludeType.h in Headers */, 87E810E51ECCA0190032C753 /* BVSortOptionsComments.h in Headers */, + 87EDAFD11EDDD02F00FA07C0 /* BVCommentSubmissionResponse.h in Headers */, + 87EDAFD51EDDD1FD00FA07C0 /* BVSubmittedComment.h in Headers */, + 87EDAFD91EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.h in Headers */, + 877AD2941EE1D134006C7070 /* BVBaseUGCSubmission.h in Headers */, + 87EDAFCC1EDDC9F400FA07C0 /* BVCommentSubmission.h in Headers */, 87F2DC241DAD585E00FB43F3 /* BVContextualInterests.h in Headers */, 87F2DC251DAD585E00FB43F3 /* BVGMBLSighting.h in Headers */, 87F2DC271DAD585E00FB43F3 /* BVGMBLVisit.h in Headers */, @@ -2451,6 +2497,7 @@ 87F2DD471DAD585E00FB43F3 /* BVShopperProfileRequestCache.m in Sources */, 87F2DCAC1DAD585E00FB43F3 /* BVProductDisplayPageRequest.m in Sources */, 87F2DC961DAD585E00FB43F3 /* BVProduct.m in Sources */, + 87EDAFDA1EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.m in Sources */, 87F2DC881DAD585E00FB43F3 /* BVReviewsResponse.m in Sources */, 873DC4921E7482840080FA03 /* BVConversionEvent.m in Sources */, 87F2DCCE1DAD585E00FB43F3 /* BVAnswerSubmissionErrorResponse.m in Sources */, @@ -2524,6 +2571,7 @@ 87F2DC7B1DAD585E00FB43F3 /* BVPhotoSizes.m in Sources */, 87F2DD021DAD585E00FB43F3 /* BVProductPageViews.m in Sources */, 87F2DC791DAD585E00FB43F3 /* BVPhoto.m in Sources */, + 877AD2951EE1D134006C7070 /* BVBaseUGCSubmission.m in Sources */, 87D424EA1E89C32E00147FDB /* BVReviewIncludeType.m in Sources */, 87F2DCD21DAD585E00FB43F3 /* BVSubmittedAnswer.m in Sources */, 87F2DCBC1DAD585E00FB43F3 /* BVProductFilterType.m in Sources */, @@ -2574,6 +2622,7 @@ 873DC49A1E7482840080FA03 /* BVPageViewEvent.m in Sources */, 87F2DCE81DAD585E00FB43F3 /* BVQuestionSubmissionErrorResponse.m in Sources */, 87F2DD401DAD585E00FB43F3 /* BVRecommendationsRequest.m in Sources */, + 87EDAFCD1EDDC9F400FA07C0 /* BVCommentSubmission.m in Sources */, 87C5FE9A1E22914C004EE6E8 /* BVStoreNotificationConfigurationLoader.m in Sources */, 87F2DC6B1DAD585E00FB43F3 /* BVConversationsInclude.m in Sources */, 87F2DCAE1DAD585E00FB43F3 /* BVQuestionsAndAnswersRequest.m in Sources */, @@ -2584,6 +2633,7 @@ 87F2DC6D1DAD585E00FB43F3 /* BVDimensionAndDistributionUtil.m in Sources */, 87F2DD441DAD585E00FB43F3 /* BVRecsAnalyticsHelper.m in Sources */, 8754117A1E1F201E006C5C6E /* BVProductReviewNotificationCenter.m in Sources */, + 87EDAFD21EDDD02F00FA07C0 /* BVCommentSubmissionResponse.m in Sources */, 150FC2C71E71C21700717041 /* BVBulkProductResponse.m in Sources */, 87F2DCF41DAD585E00FB43F3 /* BVSubmittedReview.m in Sources */, 87F2DC1F1DAD585E00FB43F3 /* BVPixel.m in Sources */, @@ -2618,6 +2668,7 @@ 87B6DFA61ECA484400B75835 /* BVComment.m in Sources */, 87F2DC8E1DAD585E00FB43F3 /* BVSecondaryRating.m in Sources */, 87F2DD191DAD585E00FB43F3 /* BVCurationsAddPostRequest.m in Sources */, + 87EDAFD61EDDD1FD00FA07C0 /* BVSubmittedComment.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2637,6 +2688,7 @@ 87F2DE111DAD945D00FB43F3 /* ProductIdEscapeTests.swift in Sources */, 87F2DD5E1DAD5E9400FB43F3 /* BVBaseStubTestCase.m in Sources */, 87F2DE0D1DAD945D00FB43F3 /* ConversationsStoresDisplayTests.swift in Sources */, + 87EDAFC91EDDC6A900FA07C0 /* CommentSubmissionTests.swift in Sources */, 87F2DE0B1DAD945D00FB43F3 /* QuestionDisplayTests.swift in Sources */, 8779DB6D1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m in Sources */, 87F2DE101DAD945D00FB43F3 /* FilterTests.swift in Sources */, diff --git a/BVSDK/BVSDK.h b/BVSDK/BVSDK.h index 30266a04..e3b396d9 100644 --- a/BVSDK/BVSDK.h +++ b/BVSDK/BVSDK.h @@ -46,6 +46,7 @@ FOUNDATION_EXPORT const unsigned char BVSDKVersionString[]; #import // Conversations +#import #import #import #import @@ -114,6 +115,10 @@ FOUNDATION_EXPORT const unsigned char BVSDKVersionString[]; #import #import +#import +#import +#import +#import // Curations #import diff --git a/BVSDK/Info.plist b/BVSDK/Info.plist index ad8809c8..a1b713f3 100644 --- a/BVSDK/Info.plist +++ b/BVSDK/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.7.0 + 6.7.1 CFBundleVersion - 6.7.0 + 6.7.1 LSApplicationCategoryType NSPrincipalClass diff --git a/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/project.pbxproj b/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/project.pbxproj index 1e5a7722..5062ac92 100644 --- a/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/project.pbxproj +++ b/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/project.pbxproj @@ -171,6 +171,10 @@ 8772167F1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortHighestRated.json in Resources */ = {isa = PBXBuildFile; fileRef = 8772167B1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortHighestRated.json */; }; 877216801D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortLowestRated.json in Resources */ = {isa = PBXBuildFile; fileRef = 8772167C1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortLowestRated.json */; }; 877216811D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortMostHelpful.json in Resources */ = {isa = PBXBuildFile; fileRef = 8772167D1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortMostHelpful.json */; }; + 877AD2711EDF4C1C006C7070 /* ReviewCommentsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877AD26F1EDF4C1C006C7070 /* ReviewCommentsViewController.swift */; }; + 877AD2721EDF4C1C006C7070 /* ReviewCommentsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 877AD2701EDF4C1C006C7070 /* ReviewCommentsViewController.xib */; }; + 877AD27C1EDF7051006C7070 /* ReviewCommentTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 877AD27A1EDF7051006C7070 /* ReviewCommentTableViewCell.swift */; }; + 877AD27D1EDF7051006C7070 /* ReviewCommentTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 877AD27B1EDF7051006C7070 /* ReviewCommentTableViewCell.xib */; }; 8783098C1CECF3F70097FC48 /* QuestionAnswerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8783098A1CECF3F70097FC48 /* QuestionAnswerViewController.swift */; }; 8783098D1CECF3F70097FC48 /* QuestionAnswerViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8783098B1CECF3F70097FC48 /* QuestionAnswerViewController.xib */; }; 878309901CECF4140097FC48 /* RatingsAndReviewsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8783098E1CECF4140097FC48 /* RatingsAndReviewsViewController.swift */; }; @@ -557,6 +561,10 @@ 8772167D1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortMostHelpful.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles_SortMostHelpful.json; path = ../../../Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortMostHelpful.json; sourceTree = ""; }; 8779DB031DD20BAE00E6CAF5 /* BVSDK.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BVSDK.xcodeproj; path = ../../BVSDK.xcodeproj; sourceTree = ""; }; 8779DB0E1DD20C7E00E6CAF5 /* BVSDK.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BVSDK.xcodeproj; path = ../../BVSDK.xcodeproj; sourceTree = ""; }; + 877AD26F1EDF4C1C006C7070 /* ReviewCommentsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ReviewCommentsViewController.swift; path = Conversations/ReviewCommentsViewController.swift; sourceTree = ""; }; + 877AD2701EDF4C1C006C7070 /* ReviewCommentsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = ReviewCommentsViewController.xib; path = Conversations/ReviewCommentsViewController.xib; sourceTree = ""; }; + 877AD27A1EDF7051006C7070 /* ReviewCommentTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReviewCommentTableViewCell.swift; sourceTree = ""; }; + 877AD27B1EDF7051006C7070 /* ReviewCommentTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReviewCommentTableViewCell.xib; sourceTree = ""; }; 8783098A1CECF3F70097FC48 /* QuestionAnswerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = QuestionAnswerViewController.swift; path = Conversations/QuestionAnswerViewController.swift; sourceTree = ""; }; 8783098B1CECF3F70097FC48 /* QuestionAnswerViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = QuestionAnswerViewController.xib; path = Conversations/QuestionAnswerViewController.xib; sourceTree = ""; }; 8783098E1CECF4140097FC48 /* RatingsAndReviewsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RatingsAndReviewsViewController.swift; path = Conversations/RatingsAndReviewsViewController.swift; sourceTree = ""; }; @@ -903,6 +911,10 @@ 730A64701CE56A1700580E9C /* RatingTableViewCell.xib */, 7338438F1CED58C800928760 /* WriteReviewViewController.swift */, 733843901CED58C800928760 /* WriteReviewViewController.xib */, + 877AD26F1EDF4C1C006C7070 /* ReviewCommentsViewController.swift */, + 877AD2701EDF4C1C006C7070 /* ReviewCommentsViewController.xib */, + 877AD27A1EDF7051006C7070 /* ReviewCommentTableViewCell.swift */, + 877AD27B1EDF7051006C7070 /* ReviewCommentTableViewCell.xib */, ); name = RatingsAndReviews; sourceTree = ""; @@ -1727,6 +1739,7 @@ 737FA0BC1CF8ADB100864B1B /* submitAnswer.json in Resources */, 87F24D851DAE8147002231D6 /* enduranceCyclesNY.gpx in Resources */, 8763B3191D1DC65B00C71F54 /* enduranceCyclesSanFrancisco.gpx in Resources */, + 877AD27D1EDF7051006C7070 /* ReviewCommentTableViewCell.xib in Resources */, 7356B23D1CF89FCA004B0121 /* AnswerListHeaderCell.xib in Resources */, 730A646E1CE5548400580E9C /* NewProductPageViewController.xib in Resources */, 73F020F51CED4E8D00FC7D7D /* QuestionAnswerTableViewCell.xib in Resources */, @@ -1741,6 +1754,7 @@ 15D5BECE1D7A0AB500D34EA0 /* minusBtn.png in Resources */, 87F5B9361DC38F37004D2297 /* testNotificationConfig.json in Resources */, 732E188F1CE65682000A3EC9 /* NewNativeAdCollectionViewCell.xib in Resources */, + 877AD2721EDF4C1C006C7070 /* ReviewCommentsViewController.xib in Resources */, 87A3192F1CF493AF000D3D0F /* recommendationsResult.json in Resources */, 15EA71A91DAC1E5000D9A706 /* videoIcon@2x.png in Resources */, 87F24D891DAE886E002231D6 /* StoreReviewsViewController.xib in Resources */, @@ -2045,6 +2059,7 @@ 87B97EB91DEF8CE4005B8C85 /* UIBarButtonItem+Badge.swift in Sources */, 736D4D001CDED0C40095EE6B /* CurationsYouTubePlayerTableViewCell.swift in Sources */, 15AE81AD1E01C919009C1478 /* PinCarouselCollectionViewCell.swift in Sources */, + 877AD27C1EDF7051006C7070 /* ReviewCommentTableViewCell.swift in Sources */, 738E724C1CEEE91F00D30617 /* CardView.swift in Sources */, 730A64711CE56A1700580E9C /* RatingTableViewCell.swift in Sources */, 7348C3721CE05A30000B6F3C /* SettingsViewController.swift in Sources */, @@ -2056,6 +2071,7 @@ 736D4CB11CDEC5730095EE6B /* SweetAlert.swift in Sources */, 87BB4C851D372B2600C860BE /* JPSThumbnailAnnotationView.m in Sources */, 73199AEE1CDD84E4006CC59D /* AppDelegate.swift in Sources */, + 877AD2711EDF4C1C006C7070 /* ReviewCommentsViewController.swift in Sources */, 739C0EF11CDDA0BD00CE771B /* BVDemoNavigationController.swift in Sources */, 15EA71A51DAC1BC200D9A706 /* AddContentTableViewCell.swift in Sources */, 878309901CECF4140097FC48 /* RatingsAndReviewsViewController.swift in Sources */, diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswerTableViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswerTableViewCell.swift index eb5894f3..5e548533 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswerTableViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswerTableViewCell.swift @@ -40,7 +40,7 @@ class AnswerTableViewCell: BVAnswerTableViewCell { override var answer : BVAnswer? { didSet { if (answer?.userNickname != nil){ - self.linkAuthorNameLabel(fullText: answer!.userNickname!, author: answer!.userNickname!) + self.authorNickname.linkAuthorNameLabel(fullText: answer!.userNickname!, author: answer!.userNickname!, target: self, selector: #selector(AnswerTableViewCell.tappedAuthor(_:))) } else { self.authorNickname.text = "" } @@ -81,23 +81,6 @@ class AnswerTableViewCell: BVAnswerTableViewCell { } } - func linkAuthorNameLabel(fullText : String, author : String) { - - let attributedString = NSMutableAttributedString(string: fullText) - attributedString.setAttributes([:], range: NSRange(0.. - + + + + + - + + + @@ -20,29 +25,29 @@ - + @@ -66,7 +71,7 @@ - + @@ -80,7 +85,7 @@ - + diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.swift index b3204b32..6e88aa37 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.swift @@ -104,7 +104,7 @@ class AnswersViewController: UIViewController, UITableViewDataSource, UITableVie cell.answer = question.answers[indexPath.row] cell.onAuthorNickNameTapped = { (authorId) -> Void in - let authorVC = AuthorProfileViewController(nibName: "AuthorProfileViewController", bundle: nil, authorId: authorId) + let authorVC = AuthorProfileViewController(authorId: authorId) self.navigationController?.pushViewController(authorVC, animated: true) } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.xib b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.xib index fa3fd035..d53644e2 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.xib +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.xib @@ -1,8 +1,11 @@ - - + + + + + - + @@ -22,6 +25,7 @@ + @@ -29,8 +33,10 @@ + + @@ -43,11 +49,13 @@ + @@ -64,6 +72,7 @@ + diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.swift index acf1820a..bd3ac398 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.swift @@ -9,7 +9,7 @@ import UIKit import BVSDK enum TableViewTag : Int { - case ReviewsTableView = 0, QuestionsTableView, AnswersTableView + case ReviewsTableView = 0, QuestionsTableView, AnswersTableView, ReviewCommentsTableView } class AuthorProfileViewController: UIViewController, UITableViewDataSource { @@ -29,11 +29,12 @@ class AuthorProfileViewController: UIViewController, UITableViewDataSource { @IBOutlet weak var reviewsTableView: UITableView! @IBOutlet weak var questionsTableView: UITableView! @IBOutlet weak var answersTableView: UITableView! + @IBOutlet weak var commentsTableView: UITableView! - init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, authorId : String) { + init(authorId : String) { self.authorId = authorId - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + super.init(nibName: nil, bundle: nil) } @@ -64,6 +65,12 @@ class AuthorProfileViewController: UIViewController, UITableViewDataSource { questionsTableView.rowHeight = UITableViewAutomaticDimension questionsTableView.allowsSelection = false + let nib4 = UINib(nibName: "ReviewCommentTableViewCell", bundle: nil) + commentsTableView.register(nib4, forCellReuseIdentifier: "ReviewCommentTableViewCell") + commentsTableView.estimatedRowHeight = 40 + commentsTableView.rowHeight = UITableViewAutomaticDimension + commentsTableView.allowsSelection = false + self.view.layoutIfNeeded() self.initDefaultUI() self.fetchAuthorProfile() @@ -104,6 +111,7 @@ class AuthorProfileViewController: UIViewController, UITableViewDataSource { request.include(.reviews, limit: 20) request.include(.questions, limit: 20) request.include(.answers, limit: 20) + request.include(.reviewComments, limit: 20) // sorts request.sortIncludedAnswers(.submissionTime, order: .descending) request.sortIncludedReviews(.submissionTime, order: .descending) @@ -141,18 +149,22 @@ class AuthorProfileViewController: UIViewController, UITableViewDataSource { let totalReviewCount = self.author?.reviewStatistics?.totalReviewCount?.intValue let totalQuestionCount = self.author?.qaStatistics?.totalQuestionCount?.intValue let totalAnswerCount = self.author?.qaStatistics?.totalAnswerCount?.intValue + let totalCommentCount = self.author!.includedComments.count let reviewButtonText = "Reviews (\(totalReviewCount!))" let questionButtonText = "Questions (\(totalQuestionCount!))" let answerButtonText = "Answers (\(totalAnswerCount!))" + let commentButtonText = "Comments (\(totalCommentCount))" self.ugcTypeSegmentedControl.setTitle(reviewButtonText, forSegmentAt: 0) self.ugcTypeSegmentedControl.setTitle(questionButtonText, forSegmentAt: 1) self.ugcTypeSegmentedControl.setTitle(answerButtonText, forSegmentAt: 2) + self.ugcTypeSegmentedControl.setTitle(commentButtonText, forSegmentAt: 3) self.reviewsTableView.reloadData() self.questionsTableView.reloadData() self.answersTableView.reloadData() + self.commentsTableView.reloadData() }) { (error) in @@ -185,6 +197,8 @@ class AuthorProfileViewController: UIViewController, UITableViewDataSource { return (self.author?.includedQuestions.count)! case TableViewTag.AnswersTableView.rawValue: return (self.author?.includedAnswers.count)! + case TableViewTag.ReviewCommentsTableView.rawValue: + return (self.author?.includedComments.count)! default: print("Bad TableView Tag Id in numberOfRowsInSection") } @@ -208,6 +222,10 @@ class AuthorProfileViewController: UIViewController, UITableViewDataSource { let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerTableViewCell") as! AnswerTableViewCell cell.answer = self.author?.includedAnswers[indexPath.row] return cell + case TableViewTag.ReviewCommentsTableView.rawValue: + let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewCommentTableViewCell") as! ReviewCommentTableViewCell + cell.comment = self.author?.includedComments[indexPath.row] + return cell default: print("Bad TableView Tag Id in cellForRowAt") } @@ -226,6 +244,8 @@ class AuthorProfileViewController: UIViewController, UITableViewDataSource { self.view.bringSubview(toFront: self.questionsTableView) case TableViewTag.AnswersTableView.rawValue: self.view.bringSubview(toFront: self.answersTableView) + case TableViewTag.ReviewCommentsTableView.rawValue: + self.view.bringSubview(toFront: self.commentsTableView) default: print("Bad index in segmented control") } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.xib b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.xib index 6a3a2521..dec19586 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.xib +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.xib @@ -1,14 +1,18 @@ - - + + + + + - + + @@ -25,8 +29,10 @@ + + @@ -34,21 +40,25 @@ - + + @@ -56,6 +66,7 @@ + @@ -69,7 +80,7 @@ - + @@ -84,6 +95,7 @@ + @@ -91,22 +103,32 @@ + + + + + + + + + + @@ -118,9 +140,12 @@ + + + diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.swift index 97655d2f..c1ce17e4 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.swift @@ -126,7 +126,7 @@ class QuestionAnswerViewController: UIViewController, UITableViewDelegate, UITab let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionAnswerTableViewCell") as! QuestionAnswerTableViewCell cell.question = question cell.onAuthorNickNameTapped = { (authorId) -> Void in - let authorVC = AuthorProfileViewController(nibName: "AuthorProfileViewController", bundle: nil, authorId: authorId) + let authorVC = AuthorProfileViewController(authorId: authorId) self.navigationController?.pushViewController(authorVC, animated: true) } return cell diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.xib b/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.xib index 747eb914..78da8edc 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.xib +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.xib @@ -1,8 +1,11 @@ - - + + + + + - + @@ -22,11 +25,14 @@ + + + @@ -39,11 +45,13 @@ + @@ -62,10 +70,11 @@ - + + - + @@ -92,7 +101,7 @@ - + diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.swift index a2f330f9..1f590e01 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.swift @@ -10,6 +10,7 @@ import BVSDK import FontAwesomeKit import XLActionController + class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet weak var tableView: BVReviewsTableView! @@ -17,6 +18,9 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) private var reviews : [BVReview] = [] + // For demo purposes we save the vote in each cell to rember as the table view cells recycle + // In production app, you'd want to save the votes for content id and user in a local db. + private var votesDictionary = [:] as! Dictionary let product : BVProduct private let numReviewToFetch : Int32 = 20 @@ -38,11 +42,12 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI case highestRating case lowestRating case mostHelpful - case location // This is a filter, not a sort + case mostComments + case location // This is a filter, not a sort } - private let filterActionTitles = ["Most Recent", "Highest Rating", "Lowest Rating", "Most Helpful", "Location Filter"] + private let filterActionTitles = ["Most Recent", "Highest Rating", "Lowest Rating", "Most Helpful", "Most Comments", "Location Filter"] private var selectedFilterOption : Int = FilterOptions.mostRecent.rawValue @@ -95,7 +100,7 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI reviewFetchPending = true let request = BVReviewsRequest(productId: product.identifier, limit: 20, offset: Int32(self.reviews.count)) - + request.addInclude(.comments) // Check sorting and filter FilterOptions if selectedFilterOption == FilterOptions.highestRating.rawValue { request.addReviewSort(.rating, order: .descending) @@ -103,6 +108,8 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI request.addReviewSort(.rating, order: .ascending) } else if selectedFilterOption == FilterOptions.mostHelpful.rawValue { request.addReviewSort(.helpfulness, order: .descending) + } else if selectedFilterOption == FilterOptions.mostComments.rawValue { + request.addReviewSort(.totalCommentCount, order: .descending) } else if selectedFilterOption == FilterOptions.location.rawValue { if let defaultCachedStore = LocationPreferenceUtils.getDefaultStore(){ request.addFilter(.userLocation, filterOperator: .equalTo, value: (defaultCachedStore.city)) @@ -121,6 +128,7 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI self.reviews.append(contentsOf: response.results) } + self.reviewFetchPending = false self.tableView.reloadData() @@ -136,11 +144,18 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI private func loadAuthorViewController(authorId: String) { - let authorVC = AuthorProfileViewController(nibName: "AuthorProfileViewController", bundle: nil, authorId: authorId) + let authorVC = AuthorProfileViewController(authorId: authorId) self.navigationController?.pushViewController(authorVC, animated: true) } + private func loadCommentsViewController(reviewComments: [BVComment]) { + + let reviewCommentsVC = ReviewCommentsViewController(reviewComments: reviewComments) + self.navigationController?.pushViewController(reviewCommentsVC, animated: true) + + } + // MARK: UITableViewDatasource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -202,7 +217,6 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI cell.button.removeTarget(nil, action: nil, for: .allEvents) cell.button.addTarget(self, action: #selector(RatingsAndReviewsViewController.filterReviewsButtonPressed), for: .touchUpInside) cell.setCustomLeftIcon(FAKFontAwesome.sortIcon(withSize:)) - cell.setCustomRightIcon(FAKFontAwesome.chevronRightIcon(withSize:)) let titlePrefix = selectedFilterOption == FilterOptions.location.rawValue ? "Filter:" : "Sort:" cell.button.setTitle("\(titlePrefix) \(filterActionTitles[selectedFilterOption])", for: UIControlState()) @@ -212,11 +226,35 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI case RatingsAndReviewsSections.reviews.rawValue: let cell = tableView.dequeueReusableCell(withIdentifier: "RatingTableViewCell") as! RatingTableViewCell - cell.review = reviews[(indexPath as NSIndexPath).row] + let review : BVReview = reviews[(indexPath as NSIndexPath).row] + cell.review = review + + // Check to see if there was a vote on this review id + var cellVote = Votes.NoVote + if let previosVote = self.votesDictionary[review.identifier!]{ + cellVote = previosVote + } + + cell.vote = cellVote + cell.onAuthorNickNameTapped = { (authorId) -> Void in self.loadAuthorViewController(authorId: authorId) } + + cell.onCommentIconTapped = { (reviewComments) -> Void in + self.loadCommentsViewController(reviewComments: reviewComments) + } + + cell.onVoteIconTapped = { (idVoteDict) -> Void in + if let key = idVoteDict.allKeys.first { + //self.votesDictionary[key] = idVoteDict.value(forKey: key as! String) + let value : Votes = idVoteDict[key as! String] as! Votes + self.votesDictionary[key as! String] = value + } + + } + return cell default: @@ -257,6 +295,9 @@ class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UI actionController.addAction(Action(filterActionTitles[FilterOptions.mostHelpful.rawValue], style: .default, handler: { action in self.didChangeFilterOption(FilterOptions.mostHelpful) })) + actionController.addAction(Action(filterActionTitles[FilterOptions.mostComments.rawValue], style: .default, handler: { action in + self.didChangeFilterOption(FilterOptions.mostComments) + })) actionController.addAction(Action(filterActionTitles[FilterOptions.location.rawValue], style: .default, handler: { action in self.didChangeFilterOption(FilterOptions.location) })) diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.xib b/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.xib index 28d6bfc3..d7dd5470 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.xib +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.xib @@ -1,8 +1,11 @@ - - + + + + + - + @@ -22,6 +25,7 @@ + @@ -29,8 +33,10 @@ + + @@ -42,12 +48,14 @@ - @@ -69,8 +70,8 @@ - - + + @@ -80,13 +81,15 @@ - + + + - + diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsFeedItemDetailCell.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsFeedItemDetailCell.swift index 4f1cc541..e5692943 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsFeedItemDetailCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsFeedItemDetailCell.swift @@ -47,7 +47,7 @@ class CurationsFeedItemDetailCell: UITableViewCell { self.postTimeLabel.text = dateTimeAgo(postDate) if (author.profile != nil && author.username != nil){ - self.linkAuthorNameLabel(author: author.username) + self.authorNameLabel.linkAuthorNameLabel(fullText: author.username, author: author.username, target: self, selector: #selector(CurationsFeedItemDetailCell.tappedAuthor(_:))) } else { self.authorNameLabel.text = author.username } @@ -57,18 +57,6 @@ class CurationsFeedItemDetailCell: UITableViewCell { } - func linkAuthorNameLabel(author : String) { - - let attributes = [ NSForegroundColorAttributeName: UIColor.blue ] - let attrText = NSAttributedString(string: author, attributes: attributes) - - self.authorNameLabel.attributedText = attrText - self.authorNameLabel.isUserInteractionEnabled = true - - let tapAuthorGesture = UITapGestureRecognizer(target: self, action: #selector(CurationsFeedItemDetailCell.tappedAuthor(_:))) - self.authorNameLabel.addGestureRecognizer(tapAuthorGesture) - } - func tappedAuthor(_ sender:UITapGestureRecognizer){ if let onSocialButtonTapped = self.onSocialButtonTapped { onSocialButtonTapped(SocialOutlet.userProfile, self.feedItem!) diff --git a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Util.swift b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Util.swift index 192af11e..e8fd6ce4 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Util.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Util.swift @@ -177,4 +177,27 @@ struct Platform { } +extension UILabel { + + func linkAuthorNameLabel(fullText : String, author : String, target: Any?, selector : Selector?) { + + let attributedString = NSMutableAttributedString(string: fullText) + attributedString.setAttributes([:], range: NSRange(0..(lhs: T?, rhs: T?) -> Bool { switch (lhs, rhs) { case let (l?, r?): @@ -28,6 +30,10 @@ private func > (lhs: T?, rhs: T?) -> Bool { } +enum Votes { + case NoVote, UpVote, DownVote +} + class RatingTableViewCell: BVReviewTableViewCell { @IBOutlet weak var reviewText : UILabel! @@ -36,12 +42,76 @@ class RatingTableViewCell: BVReviewTableViewCell { @IBOutlet weak var reviewAuthorLocation : UILabel! @IBOutlet weak var reviewStars : HCSStarRatingView! @IBOutlet weak var reviewPhoto : UIImageView! - @IBOutlet weak var usersFoundHelpfulLabel: UILabel! + + @IBOutlet weak var thumbUpButton: UIButton! + @IBOutlet weak var thumbDownButton: UIButton! + + @IBOutlet weak var commentsButton: UIButton! + + @IBOutlet weak var upVoteCountLabel: UILabel! + @IBOutlet weak var downVoteCountLabel: UILabel! + @IBOutlet weak var totalCommentsLabel: UILabel! + + var totalCommentCount = 0 + var totalUpVoteCount = 0 + var totalDownVoteCount = 0 var onAuthorNickNameTapped : ((_ authorId : String) -> Void)? = nil + var onCommentIconTapped : ((_ reviewComments : [BVComment]) -> Void)? = nil + var onVoteIconTapped : ((_ voteDict: NSDictionary) -> Void)? = nil override func awakeFromNib() { super.awakeFromNib() + self.updateStatisticIcons() + } + + private func updateStatisticIcons() { + + self.upVoteCountLabel.text = String(self.totalUpVoteCount) + self.downVoteCountLabel.text = String(self.totalDownVoteCount) + self.totalCommentsLabel.text = String(self.totalCommentCount) + + let commentsIconColor = self.totalCommentCount > 0 ? UIColor.bazaarvoiceTeal().withAlphaComponent(1) :UIColor.lightGray.withAlphaComponent(0.5) + + let thumbDownColor = self.vote == Votes.DownVote ? UIColor.bazaarvoiceTeal().withAlphaComponent(1) :UIColor.lightGray.withAlphaComponent(0.5) + let thumbUpColor = self.vote == Votes.UpVote ? UIColor.bazaarvoiceTeal().withAlphaComponent(1) :UIColor.lightGray.withAlphaComponent(0.5) + + self.thumbUpButton.setBackgroundImage(getIconImage(FAKFontAwesome.thumbsUpIcon(withSize:), color: thumbUpColor), for: .normal) + self.thumbDownButton.setBackgroundImage(getIconImage(FAKFontAwesome.thumbsDownIcon(withSize:), color: thumbDownColor), for: .normal) + self.commentsButton.setBackgroundImage(getIconImage(FAKFontAwesome.commentIcon(withSize:), color: commentsIconColor), for: .normal) + + } + + func getIconImage(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!), color: UIColor) -> UIImage { + + let size = CGFloat(22) + + let newIcon = icon(size) + newIcon?.addAttribute( + NSForegroundColorAttributeName, + value: color + ) + + return newIcon!.image(with: CGSize(width: size, height: size)) + + } + + var vote : Votes = Votes.NoVote { + + didSet { + + if vote == Votes.UpVote { + self.voteButtonTapped(self.thumbUpButton) + totalUpVoteCount += 1 + } else if vote == Votes.DownVote { + self.voteButtonTapped(self.thumbDownButton) + totalDownVoteCount += 1 + } + + updateStatisticIcons() + + } + } override var review : BVReview? { @@ -61,7 +131,6 @@ class RatingTableViewCell: BVReviewTableViewCell { reviewText.attributedText = attrString - reviewTitle.text = review!.title reviewStars.value = CGFloat(review!.rating) if let photoUrl = review?.photos.first?.sizes?.normalUrl { @@ -73,10 +142,10 @@ class RatingTableViewCell: BVReviewTableViewCell { if let submissionTime = review?.submissionTime, let nickname = review?.userNickname { let fullString = dateTimeAgo(submissionTime) + " by " + nickname - self.linkAuthorNameLabel(fullText: fullString, author: nickname) + self.reviewAuthor.linkAuthorNameLabel(fullText: fullString, author: nickname, target: self, selector: #selector(RatingTableViewCell.tappedAuthor(_:))) } else if let nickname = review?.userNickname { - self.linkAuthorNameLabel(fullText: nickname, author: nickname) + self.reviewAuthor.linkAuthorNameLabel(fullText: nickname, author: nickname, target: self, selector: #selector(RatingTableViewCell.tappedAuthor(_:))) } else if let submissionTime = review?.submissionTime { reviewAuthor.text = dateTimeAgo(submissionTime) + " by Anonymous" @@ -92,61 +161,63 @@ class RatingTableViewCell: BVReviewTableViewCell { reviewAuthorLocation.text = "" } - if review?.totalFeedbackCount?.int32Value > 0 { - - let totalFeedbackCountString = review?.totalFeedbackCount?.stringValue ?? "" - let totalPositiveFeedbackCountString = review?.totalPositiveFeedbackCount?.stringValue ?? "" - - let helpfulText = totalPositiveFeedbackCountString + " of " + totalFeedbackCountString + " users found this review helpful" - - let attributedString = NSMutableAttributedString(string: helpfulText as String, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 12.0)]) - - let boldFontAttribute = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: 12.0)] - let colorFontAttribute = [NSForegroundColorAttributeName: UIColor.darkGray] - - // Part of string to be bold - attributedString.addAttributes(boldFontAttribute, range: (helpfulText as NSString).range(of: totalFeedbackCountString)) - attributedString.addAttributes(boldFontAttribute, range: (helpfulText as NSString).range(of: totalPositiveFeedbackCountString)) - - // Make text black - attributedString.addAttributes(colorFontAttribute , range: (helpfulText as NSString).range(of: totalFeedbackCountString, options: .backwards)) - attributedString.addAttributes(colorFontAttribute , range: (helpfulText as NSString).range(of: totalPositiveFeedbackCountString)) - - usersFoundHelpfulLabel.attributedText = attributedString - - } else { - usersFoundHelpfulLabel.text = "" - } + self.totalUpVoteCount = (review?.totalPositiveFeedbackCount?.intValue)! + self.totalDownVoteCount = (review?.totalNegativeFeedbackCount?.intValue)! + self.totalCommentCount = (review?.totalCommentCount?.intValue)! + + self.updateStatisticIcons() self.setNeedsLayout() } } + + func tappedAuthor(_ sender:UITapGestureRecognizer){ + if let onAuthorNameTapped = self.onAuthorNickNameTapped { + onAuthorNameTapped((review?.authorId)!) + } + } + - func linkAuthorNameLabel(fullText : String, author : String) { + @IBAction func voteButtonTapped(_ sender: Any) { - let attributedString = NSMutableAttributedString(string: fullText) - attributedString.setAttributes([:], range: NSRange(0.. 0 { + onCommentTapped((review?.comments)!) } } + func tapVoteCallback(vote : Votes){ + + // Send callback if set + if let onVoteTapped = self.onVoteIconTapped { + if let reviewId = self.review?.identifier { + let result : NSDictionary = [reviewId:vote] + onVoteTapped(result) + } + } + + // TODO: Here we would record an API call for the feedback vote from the use + // However, Feedback API doesn't support a Preview functionality, so we'll skip that for now. + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/View Controllers/RatingTableViewCell.xib b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/View Controllers/RatingTableViewCell.xib index b857ad6f..55e01d65 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/View Controllers/RatingTableViewCell.xib +++ b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/View Controllers/RatingTableViewCell.xib @@ -1,51 +1,61 @@ - - + + + + + - + - - + + - + + + + @@ -69,23 +79,84 @@ - - + + - - + + + @@ -94,8 +165,9 @@ - - + + + @@ -109,6 +181,7 @@ + @@ -117,37 +190,52 @@ - - - - + + + + + + + + + + + + + + + + - + + + + - + diff --git a/Examples/BVSDKDemo/BVSDKDemo/QuestionAnswerTableViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/QuestionAnswerTableViewCell.swift index a282d4f9..925904e2 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/QuestionAnswerTableViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/QuestionAnswerTableViewCell.swift @@ -45,7 +45,7 @@ class QuestionAnswerTableViewCell: BVQuestionTableViewCell { questionBody.text = question?.questionDetails if (question?.userNickname != nil){ - self.linkAuthorNameLabel(fullText: "Asked by " + (question?.userNickname)!, author: (question?.userNickname)!) + self.questionMetaData.linkAuthorNameLabel(fullText: "Asked by " + (question?.userNickname)!, author: (question?.userNickname)!, target: self, selector: #selector(QuestionAnswerTableViewCell.tappedAuthor(_:))) } else { questionMetaData.text = "" } @@ -81,23 +81,6 @@ class QuestionAnswerTableViewCell: BVQuestionTableViewCell { } - func linkAuthorNameLabel(fullText : String, author : String) { - - let attributedString = NSMutableAttributedString(string: fullText) - attributedString.setAttributes([:], range: NSRange(0.. Void)? = nil + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + var comment : BVComment? { + didSet { + + if let nick = comment?.userNickname { + self.authorNickname.linkAuthorNameLabel(fullText: nick, author: nick, target:self, selector: #selector(RatingTableViewCell.tappedAuthor(_:))) + } else { + self.authorNickname.text = "" + } + commentText.text = comment!.commentText + if let submissionTime = comment!.submissionTime{ + writtenAtLabel.text = dateTimeAgo(submissionTime) + } + else { + writtenAtLabel.text = "" + } + + if (comment?.totalFeedbackCount?.int32Value)! > 0 { + + let totalFeedbackCountString = comment?.totalFeedbackCount?.stringValue ?? "" + let totalPositiveFeedbackCountString = comment?.totalPositiveFeedbackCount?.stringValue ?? "" + + let helpfulText = totalPositiveFeedbackCountString + " of " + totalFeedbackCountString + " users found this comment helpful" + + let attributedString = NSMutableAttributedString(string: helpfulText as String, attributes: [NSFontAttributeName:UIFont.systemFont(ofSize: 12.0)]) + + let boldFontAttribute = [NSFontAttributeName: UIFont.boldSystemFont(ofSize: 12.0)] + let colorFontAttribute = [NSForegroundColorAttributeName: UIColor.darkGray] + + // Part of string to be bold + attributedString.addAttributes(boldFontAttribute, range: (helpfulText as NSString).range(of: totalFeedbackCountString)) + attributedString.addAttributes(boldFontAttribute, range: (helpfulText as NSString).range(of: totalPositiveFeedbackCountString)) + + // Make text black + attributedString.addAttributes(colorFontAttribute , range: (helpfulText as NSString).range(of: totalFeedbackCountString, options: .backwards)) + attributedString.addAttributes(colorFontAttribute , range: (helpfulText as NSString).range(of: totalPositiveFeedbackCountString)) + + usersFoundHelpfulLabel.attributedText = attributedString + + } else { + usersFoundHelpfulLabel.text = "" + } + + } + } + + func tappedAuthor(_ sender:UITapGestureRecognizer){ + if let onAuthorNameTapped = self.onAuthorNickNameTapped { + onAuthorNameTapped((comment?.authorId)!) + } + } + +} diff --git a/Examples/BVSDKDemo/BVSDKDemo/ReviewCommentTableViewCell.xib b/Examples/BVSDKDemo/BVSDKDemo/ReviewCommentTableViewCell.xib new file mode 100644 index 00000000..36f093b9 --- /dev/null +++ b/Examples/BVSDKDemo/BVSDKDemo/ReviewCommentTableViewCell.xib @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Examples/BVSDKDemo/Podfile.lock b/Examples/BVSDKDemo/Podfile.lock index dcdb3f5d..62544bc7 100644 --- a/Examples/BVSDKDemo/Podfile.lock +++ b/Examples/BVSDKDemo/Podfile.lock @@ -5,16 +5,16 @@ PODS: - Bolts/AppLinks (1.8.4): - Bolts/Tasks - Bolts/Tasks (1.8.4) - - BVSDK/BVCurations (6.5.4): + - BVSDK/BVCurations (6.7.1): - BVSDK/Core - - BVSDK/BVLocation (6.5.4): + - BVSDK/BVLocation (6.7.1): - BVSDK/Core - - BVSDK/BVNotifications (6.5.4): + - BVSDK/BVNotifications (6.7.1): - BVSDK/BVLocation - BVSDK/BVPIN - - BVSDK/BVPIN (6.5.4): + - BVSDK/BVPIN (6.7.1): - BVSDK/Core - - BVSDK/Core (6.5.4) + - BVSDK/Core (6.7.1) - Crashlytics (3.8.3): - Fabric (~> 1.6.3) - Fabric (1.6.11) @@ -88,7 +88,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Bolts: 8a7995239dbe724f9cba2248b766d48b7ebdd322 - BVSDK: 55be541ea3e35526f9817d38418805c9d442892d + BVSDK: 5e06b8e158dd2e67cf09315185b33a62dcad15b7 Crashlytics: 2b6dbe138a42395577cfa73dfa1aa7248cadf39e Fabric: 5911403591946b8228ab1c51d98f1d7137e863c6 FBSDKCoreKit: 894b2b6eda6a4c8c4204e92e59cc355709ef045c diff --git a/Examples/Conversations/Obj-C/ConversationsExample/Base.lproj/Main.storyboard b/Examples/Conversations/Obj-C/ConversationsExample/Base.lproj/Main.storyboard index ad32941e..c79b7bb6 100644 --- a/Examples/Conversations/Obj-C/ConversationsExample/Base.lproj/Main.storyboard +++ b/Examples/Conversations/Obj-C/ConversationsExample/Base.lproj/Main.storyboard @@ -100,9 +100,17 @@ + + @@ -127,6 +135,7 @@ + diff --git a/Examples/Conversations/Obj-C/ConversationsExample/RootViewController.m b/Examples/Conversations/Obj-C/ConversationsExample/RootViewController.m index 744405fb..e20e0f0b 100644 --- a/Examples/Conversations/Obj-C/ConversationsExample/RootViewController.m +++ b/Examples/Conversations/Obj-C/ConversationsExample/RootViewController.m @@ -50,11 +50,13 @@ - (IBAction)submitQuestionTapped:(id)sender { BVQuestionSubmission* question = [[BVQuestionSubmission alloc] initWithProductId:@"test1"]; question.action = BVSubmissionActionPreview; + NSString *randomId = [NSString stringWithFormat:@"userId%u", arc4random()]; + question.questionSummary = @"Question Summary"; question.questionDetails = @"Question details..."; question.userEmail = @"foo@bar.com"; - question.userNickname = @"shazbat"; - question.userId = [NSString stringWithFormat:@"userId%d", arc4random()]; // add in a random user id for testing, avoids duplicate errors + question.userNickname = [NSString stringWithFormat:@"UserNick%@", randomId]; + question.userId = [NSString stringWithFormat:@"UserId%@", randomId]; question.sendEmailAlertWhenPublished = [NSNumber numberWithBool:YES]; question.agreedToTermsAndConditions = [NSNumber numberWithBool:YES]; @@ -72,9 +74,11 @@ - (IBAction)submitAnswerTapped:(id)sender { BVAnswerSubmission* answer = [[BVAnswerSubmission alloc] initWithQuestionId:@"14679" answerText:@"User answer text goes here...."]; answer.action = BVSubmissionActionPreview; + NSString *randomId = [NSString stringWithFormat:@"userId%u", arc4random()]; + answer.userEmail = @"foo@bar.com"; - answer.userNickname = @"shazbat"; - answer.userId = [NSString stringWithFormat:@"userId%d", arc4random()]; // add in a random user id for testing, avoids duplicate errors + answer.userNickname = [NSString stringWithFormat:@"UserNick%@", randomId]; + answer.userId = [NSString stringWithFormat:@"UserId%@", randomId]; answer.sendEmailAlertWhenPublished = [NSNumber numberWithBool:YES]; answer.agreedToTermsAndConditions = [NSNumber numberWithBool:YES]; @@ -88,7 +92,7 @@ - (IBAction)submitAnswerTapped:(id)sender { - (IBAction)submitFeedbackTapped:(id)sender { - BVFeedbackSubmission *feedback = [[BVFeedbackSubmission alloc] initWithContentId:@"192454" withContentType:BVFeedbackContentTypeReview withFeedbackType:BVFeedbackTypeHelpfulness]; + BVFeedbackSubmission *feedback = [[BVFeedbackSubmission alloc] initWithContentId:@"192451" withContentType:BVFeedbackContentTypeReview withFeedbackType:BVFeedbackTypeHelpfulness]; feedback.userId = [NSString stringWithFormat:@"userId%d", arc4random()]; // add in a random user id for testing, avoids duplicate errors feedback.vote = BVFeedbackVotePositive; @@ -103,6 +107,39 @@ - (IBAction)submitFeedbackTapped:(id)sender { } +- (IBAction)submitReviewCommentTapped:(id)sender { + + NSString *commentText = @"I love comments almost as much as Objective-C! They are just the most! Seriously!"; + NSString *commentTitle = @"Comments Can We Written In Objective-C"; + + BVCommentSubmission *submission = [[BVCommentSubmission alloc] initWithReviewId:@"192550" withCommentText:commentText]; + + NSString *randomId = [NSString stringWithFormat:@"userId%u", arc4random()]; + + //commentRequest.fingerPrint = // the iovation fingerprint would go here... + submission.action = BVSubmissionActionPreview; + submission.campaignId = @"BV_COMMENT_CAMPAIGN_ID"; + submission.commentTitle = commentTitle; + submission.locale = @"en_US"; + submission.sendEmailAlertWhenPublished = [NSNumber numberWithBool:YES]; + submission.userNickname = [NSString stringWithFormat:@"UserNick%@", randomId]; + submission.userId = [NSString stringWithFormat:@"UserId%@", randomId]; + submission.userEmail = @"developer@bazaarvoice.com"; + submission.agreedToTermsAndConditions = [NSNumber numberWithBool:YES]; + + // user added a photo to this review + //[submission addPhoto:[UIImage imageNamed:@"puppy"] withPhotoCaption:@"What a cute pupper!"]; + + [submission submit:^(BVCommentSubmissionResponse * _Nonnull response) { + // success + [self showSuccess:@"Success Submitting Feedback!"]; + } failure:^(NSArray * _Nonnull errors) { + // error + [self showError:errors.description]; + }]; + +} + - (void)showSuccess:(NSString *)message { diff --git a/Examples/Conversations/Obj-C/Podfile.lock b/Examples/Conversations/Obj-C/Podfile.lock index 8ae173e8..696ad88d 100644 --- a/Examples/Conversations/Obj-C/Podfile.lock +++ b/Examples/Conversations/Obj-C/Podfile.lock @@ -1,7 +1,7 @@ PODS: - - BVSDK/BVConversations (6.7.0): + - BVSDK/BVConversations (6.7.1): - BVSDK/Core - - BVSDK/Core (6.7.0) + - BVSDK/Core (6.7.1) DEPENDENCIES: - BVSDK/BVConversations (from `../../../`) @@ -11,7 +11,7 @@ EXTERNAL SOURCES: :path: "../../../" SPEC CHECKSUMS: - BVSDK: b7282f1b646d716c65c249d8e6f5a30d93e5419d + BVSDK: 5e06b8e158dd2e67cf09315185b33a62dcad15b7 PODFILE CHECKSUM: 9e1e2f031337aa6807bc35f4e4832b444a6e4d39 diff --git a/Examples/Conversations/Swift/ConversationsExample/Base.lproj/Main.storyboard b/Examples/Conversations/Swift/ConversationsExample/Base.lproj/Main.storyboard index e07ef36e..56bee87b 100644 --- a/Examples/Conversations/Swift/ConversationsExample/Base.lproj/Main.storyboard +++ b/Examples/Conversations/Swift/ConversationsExample/Base.lproj/Main.storyboard @@ -100,6 +100,13 @@ + @@ -115,6 +122,7 @@ + @@ -127,6 +135,7 @@ + diff --git a/Examples/Conversations/Swift/ConversationsExample/RootViewController.swift b/Examples/Conversations/Swift/ConversationsExample/RootViewController.swift index a2b9c8a9..38515405 100644 --- a/Examples/Conversations/Swift/ConversationsExample/RootViewController.swift +++ b/Examples/Conversations/Swift/ConversationsExample/RootViewController.swift @@ -26,7 +26,7 @@ class RootViewController: UIViewController { productId: Constants.TEST_PRODUCT_ID) // a working example of posting a review. - reviewSubmission.action = BVSubmissionAction.submit // Don't actually post, just run in preview mode! + reviewSubmission.action = BVSubmissionAction.preview // Don't actually post, just run in preview mode! // We need to use the same userId for both the photo post and review content let userId = "123abc\(arc4random())" @@ -36,7 +36,7 @@ class RootViewController: UIViewController { reviewSubmission.userId = userId reviewSubmission.isRecommended = true reviewSubmission.sendEmailAlertWhenPublished = true - + if let photo = UIImage(named: "puppy"){ reviewSubmission.addPhoto(photo, withPhotoCaption: "5 star pup!") } @@ -101,7 +101,7 @@ class RootViewController: UIViewController { @IBAction func submitFeedbackTapped(sender: AnyObject) { - let feedback = BVFeedbackSubmission(contentId: "192454", with: BVFeedbackContentType.review, with: BVFeedbackType.helpfulness) + let feedback = BVFeedbackSubmission(contentId: "192451", with: BVFeedbackContentType.review, with: BVFeedbackType.helpfulness) let randomId = String(arc4random()) @@ -117,6 +117,43 @@ class RootViewController: UIViewController { self.showAlertError(message: errors.description) } + } + + @IBAction func submitReviewCommentTapped(sender: AnyObject) { + + let commentText = "I love comments! They are just the most! Seriously!" + let commentTitle = "Best Comment Title Ever!" + let commentRequest = BVCommentSubmission(reviewId: "192548", withCommentText: commentText) + + commentRequest.action = .preview + + let randomId = String(arc4random()) // create a random id for testing only + + //commentRequest.fingerPrint = // the iovation fingerprint would go here... + commentRequest.campaignId = "BV_COMMENT_CAMPAIGN_ID" + commentRequest.commentTitle = commentTitle + commentRequest.locale = "en_US" + commentRequest.sendEmailAlertWhenPublished = true + commentRequest.userNickname = "UserNickname" + randomId + commentRequest.userId = "UserId" + randomId + commentRequest.userEmail = "developer@bazaarvoice.com" + commentRequest.agreedToTermsAndConditions = true + + // Some PRR clients may support adding photos, check your configuration +// if let photo = UIImage(named: "puppy"){ +// commentRequest.addPhoto(photo, withPhotoCaption: "Review Comment Pupper!") +// } + + commentRequest.submit({ (commentSubmission) in + + // success + self.showAlertSuccess(message: "Success Submitting Review Comment!") + + }, failure: { (errors) in + // error + self.showAlertError(message: errors.description) + + }) } diff --git a/Examples/Conversations/Swift/Podfile.lock b/Examples/Conversations/Swift/Podfile.lock index 522c9583..3261ce17 100644 --- a/Examples/Conversations/Swift/Podfile.lock +++ b/Examples/Conversations/Swift/Podfile.lock @@ -1,7 +1,7 @@ PODS: - - BVSDK/BVConversations (6.7.0): + - BVSDK/BVConversations (6.7.1): - BVSDK/Core - - BVSDK/Core (6.7.0) + - BVSDK/Core (6.7.1) DEPENDENCIES: - BVSDK/BVConversations (from `../../../`) @@ -11,8 +11,7 @@ EXTERNAL SOURCES: :path: "../../../" SPEC CHECKSUMS: - - BVSDK: b7282f1b646d716c65c249d8e6f5a30d93e5419d + BVSDK: 5e06b8e158dd2e67cf09315185b33a62dcad15b7 PODFILE CHECKSUM: ff51a28f7873cf9d401e0c25277fa303397c7510 diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.h b/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.h index a13fdf17..faf4dd51 100644 --- a/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.h +++ b/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.h @@ -15,6 +15,7 @@ typedef NS_ENUM(NSInteger, BVPixelFeatureUsedEventName) { BVPixelFeatureUsedEventNameWriteReview, BVPixelFeatureUsedEventNameAskQuestion, BVPixelFeatureUsedEventNameAnswerQuestion, + BVPixelFeatureUsedEventNameReviewComment, BVPixelFeatureUsedEventNameFeedback, BVPixelFeatureUsedEventNameInappropriate, BVPixelFeatureUsedEventNamePhoto, diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.m b/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.m index 41e2d56e..3a203842 100644 --- a/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.m +++ b/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.m @@ -20,6 +20,8 @@ +(NSString *)toString:(BVPixelFeatureUsedEventName)featuretype{ return @"Question"; case BVPixelFeatureUsedEventNameAnswerQuestion: return @"Answer"; + case BVPixelFeatureUsedEventNameReviewComment: + return @"Comment"; case BVPixelFeatureUsedEventNameFeedback: return @"Helpfulness"; case BVPixelFeatureUsedEventNameInappropriate: diff --git a/Pod/BVCommon/BVSDKConstants.h b/Pod/BVCommon/BVSDKConstants.h index 98f41706..51fbb6c1 100644 --- a/Pod/BVCommon/BVSDKConstants.h +++ b/Pod/BVCommon/BVSDKConstants.h @@ -11,11 +11,11 @@ /// Provides the master version of the SDK. -#define BV_SDK_VERSION @"6.7.0" +#define BV_SDK_VERSION @"6.7.1" /// Conversation SDK Version #define SDK_HEADER_NAME @"X-UA-BV-SDK" -#define SDK_HEADER_VALUE @"IOS_SDK_V670" +#define SDK_HEADER_VALUE @"IOS_SDK_V671" /// Error domain for NSError results, when present. #define BVErrDomain @"com.bvsdk.bazaarvoice" diff --git a/Pod/BVConversations/Display/BVAuthorContentType.h b/Pod/BVConversations/Display/BVAuthorContentType.h index ac23f442..770e3b2b 100644 --- a/Pod/BVConversations/Display/BVAuthorContentType.h +++ b/Pod/BVConversations/Display/BVAuthorContentType.h @@ -11,7 +11,8 @@ typedef NS_ENUM(NSInteger, BVAuthorContentType) { BVAuthorContentTypeReviews, BVAuthorContentTypeQuestions, - BVAuthorContentTypeAnswers + BVAuthorContentTypeAnswers, + BVAuthorContentTypeReviewComments }; diff --git a/Pod/BVConversations/Display/BVAuthorContentType.m b/Pod/BVConversations/Display/BVAuthorContentType.m index 6779c9a8..9339ddc2 100644 --- a/Pod/BVConversations/Display/BVAuthorContentType.m +++ b/Pod/BVConversations/Display/BVAuthorContentType.m @@ -17,6 +17,8 @@ +(NSString*)toString:(BVAuthorContentType)type { return @"Answers"; case BVAuthorContentTypeQuestions: return @"Questions"; + case BVAuthorContentTypeReviewComments: + return @"Comments"; } } diff --git a/Pod/BVConversations/Display/Model/BVModelUtil.h b/Pod/BVConversations/Display/Model/BVModelUtil.h index 99231382..472c58aa 100644 --- a/Pod/BVConversations/Display/Model/BVModelUtil.h +++ b/Pod/BVConversations/Display/Model/BVModelUtil.h @@ -1,5 +1,5 @@ // -// ModelUtil.h +// BVModelUtil.h // Conversations // // Copyright © 2016 Bazaarvoice. All rights reserved. diff --git a/Pod/BVConversations/Display/Model/BVModelUtil.m b/Pod/BVConversations/Display/Model/BVModelUtil.m index e289a6c9..55004294 100644 --- a/Pod/BVConversations/Display/Model/BVModelUtil.m +++ b/Pod/BVConversations/Display/Model/BVModelUtil.m @@ -1,5 +1,5 @@ // -// ModelUtil.m +// BVModelUtil.m // Conversations // // Copyright © 2016 Bazaarvoice. All rights reserved. diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.h b/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.h index 08c091ce..76ce8f5a 100644 --- a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.h +++ b/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.h @@ -37,6 +37,7 @@ @property NSArray* _Nonnull includedReviews; @property NSArray* _Nonnull includedQuestions; @property NSArray* _Nonnull includedAnswers; +@property NSArray* _Nonnull includedComments; @property BVReviewStatistics* _Nullable reviewStatistics; @property BVQAStatistics* _Nullable qaStatistics; diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.m b/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.m index c03d6f3f..76932c6d 100644 --- a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.m +++ b/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.m @@ -52,6 +52,15 @@ -(id)initWithApiResponse:(NSDictionary *)apiResponse includes:(BVConversationsIn } self.includedQuestions = tempQuestions; + NSArray* commentIds = apiResponse[@"CommentIds"]; + NSMutableArray* tempComments = [NSMutableArray array]; + for(NSString* commentId in commentIds) { + BVComment* comment = [includes getCommentById:commentId]; + [tempComments addObject:comment]; + + } + self.includedComments = tempComments; + NSArray* answerIds = apiResponse[@"AnswerIds"]; NSMutableArray* tempAnswers = [NSMutableArray array]; for(NSString* answerId in answerIds) { @@ -61,7 +70,6 @@ -(id)initWithApiResponse:(NSDictionary *)apiResponse includes:(BVConversationsIn } self.includedAnswers = tempAnswers; - } return self; } diff --git a/Pod/BVConversations/Display/Requests/BVAuthorRequest.m b/Pod/BVConversations/Display/Requests/BVAuthorRequest.m index f02bc94a..37fd60a4 100644 --- a/Pod/BVConversations/Display/Requests/BVAuthorRequest.m +++ b/Pod/BVConversations/Display/Requests/BVAuthorRequest.m @@ -61,6 +61,12 @@ - (nonnull instancetype)initWithAuthorId:(NSString * _Nonnull)authorId limit:(in } - (nonnull instancetype)includeStatistics:(BVAuthorContentType)contentType { + + if (contentType == BVAuthorContentTypeReviewComments){ + NSAssert(NO, @"Including Review Comment Statistics is not supported with an authors request."); + return self; + } + [self.authorContentTypeStatistics addObject:@(contentType)]; return self; } diff --git a/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.h b/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.h index a255f9f5..0ea12157 100644 --- a/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.h +++ b/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.h @@ -9,7 +9,7 @@ #import "BVSubmissionAction.h" #import "BVAnswerSubmissionResponse.h" #import "BVConversationsRequest.h" -#import "BVSubmission.h" +#import "BVBaseUGCSubmission.h" typedef void (^AnswerSubmissionCompletion)(BVAnswerSubmissionResponse* _Nonnull response); @@ -24,7 +24,7 @@ typedef void (^AnswerSubmissionCompletion)(BVAnswerSubmissionResponse* _Nonnull @availability 4.1.0 and later */ -@interface BVAnswerSubmission : BVSubmission +@interface BVAnswerSubmission : BVBaseUGCSubmission /** Create a new BVAnswerSubmission. @@ -35,13 +35,6 @@ typedef void (^AnswerSubmissionCompletion)(BVAnswerSubmissionResponse* _Nonnull -(nonnull instancetype)initWithQuestionId:(nonnull NSString*)questionId answerText:(nonnull NSString*)answerText; -(nonnull instancetype) __unavailable init; -/** - Submit a user-provided photo attached to this answer. - - @param image The user-provded image attached to this answer. - @param photoCaption The user-provided caption for the photo. - */ --(void)addPhoto:(nonnull UIImage*)image withPhotoCaption:(nullable NSString*)photoCaption; /** Submit this answer to the Bazaarvoice platform. If the `action` of this object is set to `BVSubmissionActionPreview` then the submission will NOT actually take place. @@ -53,23 +46,6 @@ typedef void (^AnswerSubmissionCompletion)(BVAnswerSubmissionResponse* _Nonnull */ -(void)submit:(nonnull AnswerSubmissionCompletion)success failure:(nonnull ConversationsFailureHandler)failure; -@property BVSubmissionAction action; - -@property NSString* _Nullable user; -@property NSString* _Nullable userEmail; -@property NSString* _Nullable userId; -@property NSString* _Nullable userLocation; -@property NSString* _Nullable userNickname; - -@property NSNumber* _Nullable agreedToTermsAndConditions; -@property NSNumber* _Nullable sendEmailAlertWhenPublished; - -@property NSString* _Nullable locale; -@property NSString* _Nullable campaignId; -@property NSString* _Nullable hostedAuthenticationEmail; -@property NSString* _Nullable hostedAuthenticationCallback; - -@property NSString* _Nullable fingerPrint; @property (readonly) NSString* _Nonnull questionId; diff --git a/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.m b/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.m index 9b432085..a0fff713 100644 --- a/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.m +++ b/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.m @@ -16,7 +16,6 @@ @interface BVAnswerSubmission() @property (readwrite) NSString* _Nonnull questionId; @property NSString* _Nonnull answerText; -@property NSMutableArray* _Nonnull photos; @property bool failureCalled; @end @@ -28,20 +27,13 @@ -(nonnull instancetype)initWithQuestionId:(nonnull NSString*)questionId answerTe if(self){ self.questionId = questionId; self.answerText = answerText; - self.photos = [NSMutableArray array]; } return self; } --(void)addPhoto:(nonnull UIImage*)image withPhotoCaption:(nullable NSString*)photoCaption { - BVUploadablePhoto* photo = [[BVUploadablePhoto alloc] initWithPhoto:image photoCaption:photoCaption]; - [self.photos addObject:photo]; -} - -(void)submit:(nonnull AnswerSubmissionCompletion)success failure:(nonnull ConversationsFailureHandler)failure { if (self.action == BVSubmissionActionPreview) { - //TODO send off warning that this doesn't actually submit shtuff [[BVLogger sharedLogger] warning:@"Submitting a 'BVAnswerSubmission' with action set to `BVSubmissionActionPreview` will not actially submit the answer! Set to `BVSubmissionActionSubmit` for real submission."]; [self submitPreview:success failure:failure]; } diff --git a/Pod/BVConversations/Submission/BVBaseUGCSubmission.h b/Pod/BVConversations/Submission/BVBaseUGCSubmission.h new file mode 100644 index 00000000..efc8eab6 --- /dev/null +++ b/Pod/BVConversations/Submission/BVBaseUGCSubmission.h @@ -0,0 +1,72 @@ +// +// BVBaseUGCSubmission.h +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVSubmission.h" +#import "BVSubmissionAction.h" +#import "BVUploadablePhoto.h" + +/// Base class used for defining common properties for a UGC content types of the Conversations API: Reviews, Review Comments, Questions and Answers. +@interface BVBaseUGCSubmission : BVSubmission + +/// Set whether or not to to perform a preview on the submission or submit for real. Default is BVSubmissionActionPreview. A preview is like a dry run, but validation of the fields will occur through the API call over the network but no data will be submitted. +@property BVSubmissionAction action; + +/// Value of the encrypted user. This parameter demonstrates that a user has been authenticated. Note that the UserId parameter does not contain authentication information and should not be used for hosted authentication. See the Authenticate User method for more information. +@property NSString* _Nullable user; + +/// User's email address +@property NSString* _Nullable userEmail; + +/// User's external ID. Do not use email addresses for this value. +@property NSString* _Nullable userId; + +/// User location text +@property NSString* _Nullable userLocation; + +/// User nickname display text +@property NSString* _Nullable userNickname; + +/// Boolean indicating whether or not the user agreed to the terms and conditions. Required depending on the client's settings. +@property NSNumber* _Nullable agreedToTermsAndConditions; + +// Boolean indicating whether or not the user wants to be notified when a comment is posted on the content. +@property NSNumber* _Nullable sendEmailAlertWhenCommented; + +/// Boolean indicating whether or not the user wants to be notified when his/her content is published. +@property NSNumber* _Nullable sendEmailAlertWhenPublished; + +/// Locale to display Labels, Configuration, Product Attributes and Category Attributes in. The default value is the locale defined in the display associated with the API key. +@property NSString* _Nullable locale; + +/// Arbitrary text that may be saved alongside content to indicate vehicle by which content was captured, e.g. “post-purchase email”. +@property NSString* _Nullable campaignId; + +/// Email address where the submitter will receive the confirmation email. If you are configured to use hosted email authentication, this parameter is required. See the Authenticate User method for more information on hosted authentication. +@property NSString* _Nullable hostedAuthenticationEmail; + +/// URL of the link contained in the user authentication email. This should point to a landing page where a web application exists to complete the user authentication process. The host for the URL must be one of the domains configured for the client. The link in the email will contain a user authentication token (authtoken) that is used to verify the submitter. If you are configured to use hosted email authentication, this parameter is required. See the hosted authentication tutorial for more information. +@property NSString* _Nullable hostedAuthenticationCallback; + +/** + Fingerprint of content author's device. See the Authenticity Tutorial for more information. + + Per the Bazaarvoice Authenticity Policy, you must send a device fingerprint attached to each submission. If you fail to send a device fingerprint with your submission, Bazaarvoice may take any action deemed necessary in Bazaarvoice’s sole discretion to protect the integrity of the network. Such actions may include but are not limited to: rejection of your content, halting syndication of your content on the Bazaarvoice network, revocation of your API key, or revocation of your API license. + */ +@property NSString* _Nullable fingerPrint; + +// An array of BVUploadablePhoto objects to attach to a review submission. +@property NSMutableArray* _Nonnull photos; + +/** + Submit a user-provided photo attached to this answer. + + @param image The user-provded image attached to this answer. + @param photoCaption The user-provided caption for the photo. + */ +-(void)addPhoto:(nonnull UIImage*)image withPhotoCaption:(nullable NSString*)photoCaption; + +@end diff --git a/Pod/BVConversations/Submission/BVBaseUGCSubmission.m b/Pod/BVConversations/Submission/BVBaseUGCSubmission.m new file mode 100644 index 00000000..34f3c581 --- /dev/null +++ b/Pod/BVConversations/Submission/BVBaseUGCSubmission.m @@ -0,0 +1,28 @@ +// +// BVBaseUGCSubmission.m +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVBaseUGCSubmission.h" + +@implementation BVBaseUGCSubmission + +- (nonnull instancetype)init { + + self = [super init]; + if (self){ + self.photos = [NSMutableArray array]; + } + + return self; + +} + +-(void)addPhoto:(nonnull UIImage*)image withPhotoCaption:(nullable NSString*)photoCaption { + BVUploadablePhoto* photo = [[BVUploadablePhoto alloc] initWithPhoto:image photoCaption:photoCaption]; + [self.photos addObject:photo]; +} + +@end diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmission.h b/Pod/BVConversations/Submission/Comment/BVCommentSubmission.h new file mode 100644 index 00000000..d9f97712 --- /dev/null +++ b/Pod/BVConversations/Submission/Comment/BVCommentSubmission.h @@ -0,0 +1,36 @@ +// +// BVCommentSubmission.h +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// +#import "BVCommentSubmission.h" +#import "BVBaseUGCSubmission.h" +#import "BVCommentSubmissionResponse.h" + +@class BVCommentSubmissionResponse; + +typedef void (^CommentSubmissionCompletion)(BVCommentSubmissionResponse* _Nonnull response); + +@interface BVCommentSubmission : BVBaseUGCSubmission + + +/** + Initialize a request object to add a review comment. Initialize the request with the supplied initializer, then add the additional parameters required by your API key. See also the Bazaarvoice Developer Reference: https://developer.bazaarvoice.com/conversations-api/reference/v5.4/comments/comment-submission + + @param reviewId The review ID to add the comment to + @param commentText The user supplied text + @return initialized BVCommentSubmission parameter + */ +- (nonnull instancetype)initWithReviewId:(nonnull NSString *)reviewId withCommentText:(nonnull NSString * )commentText; +- (nonnull instancetype) __unavailable init; + + +-(void)submit:(nonnull CommentSubmissionCompletion)success failure:(nonnull ConversationsFailureHandler)failure; + +@property (readonly) NSString* _Nonnull reviewId; +@property (readonly) NSString* _Nonnull commentText; + +@property NSString * _Nonnull commentTitle; + +@end diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmission.m b/Pod/BVConversations/Submission/Comment/BVCommentSubmission.m new file mode 100644 index 00000000..113df675 --- /dev/null +++ b/Pod/BVConversations/Submission/Comment/BVCommentSubmission.m @@ -0,0 +1,243 @@ +// +// BVCommentSubmission.m +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentSubmission.h" +#import "BVSDKConfiguration.h" +#import "BVSubmissionErrorResponse.h" +#import "BVCommentSubmissionErrorResponse.h" +#import "BVUploadablePhoto.h" + +@interface BVCommentSubmission () + +@property BOOL failureCalled; + +@end + + +@implementation BVCommentSubmission + +- (nonnull instancetype)initWithReviewId:(NSString *)reviewId withCommentText:(NSString *)commentText { + + self = [super init]; + + if (self){ + _reviewId = reviewId; + _commentText = commentText; + } + + return self; +} + + +-(void)submit:(nonnull CommentSubmissionCompletion)success failure:(nonnull ConversationsFailureHandler)failure { + + if (self.action == BVSubmissionActionPreview) { + [[BVLogger sharedLogger] warning:@"Submitting a 'BVCommentSubmission' with action set to `BVSubmissionActionPreview` will not actially submit the comment! Set to `BVSubmissionActionSubmit` for real submission."]; + [self submitPreview:success failure:failure]; + } + else { + [self submitPreview:^(BVCommentSubmissionResponse * _Nonnull response) { + [self submitForReal:success failure:failure]; + } failure:^(NSArray * _Nonnull errors) { + [self sendErrors:errors failureCallback:failure]; + }]; + } +} + +-(void)submitPreview:(CommentSubmissionCompletion)success failure:(ConversationsFailureHandler)failure { + + [self submitCommentWithPhotoUrls:BVSubmissionActionPreview + photoUrls:@[] + photoCaptions:@[] + success:success + failure:failure]; + +} + +-(void)submitForReal:(CommentSubmissionCompletion)success failure:(ConversationsFailureHandler)failure { + + if ([self.photos count] == 0) { + [self submitCommentWithPhotoUrls:BVSubmissionActionSubmit + photoUrls:@[] + photoCaptions:@[] + success:success + failure:failure]; + return; + } + + // upload photos before submitting comments (prr only) + NSMutableArray* photoUrls = [NSMutableArray array]; + NSMutableArray* photoCaptions = [NSMutableArray array]; + + for (BVUploadablePhoto* photo in self.photos) { + + [photo uploadForContentType:BVPhotoContentTypeComment success:^(NSString * _Nonnull photoUrl) { + + // Queue one event for each photo uploaded. + BVFeatureUsedEvent *photoUploadEvent = [[BVFeatureUsedEvent alloc] initWithProductId:self.reviewId + withBrand:nil + withProductType:BVPixelProductTypeConversationsReviews + withEventName:BVPixelFeatureUsedEventNamePhoto + withAdditionalParams:@{@"detail1":@"Comment"}]; + [BVPixel trackEvent:photoUploadEvent]; + + + [photoUrls addObject:photoUrl]; + [photoCaptions addObject:photo.photoCaption]; + + // all photos uploaded! submit comment + if ([photoUrls count] == [self.photos count]) { + [self submitCommentWithPhotoUrls:BVSubmissionActionSubmit + photoUrls:photoUrls + photoCaptions:photoCaptions + success:success + failure:failure]; + } + + } failure:^(NSArray * _Nonnull errors) { + + if (!self.failureCalled) { + self.failureCalled = true; // only call failure block once, if multiple photos failed. + [self sendErrors:errors failureCallback:failure]; + } + + }]; + + } + +} + +-(void)submitCommentWithPhotoUrls:(BVSubmissionAction)action photoUrls:(nonnull NSArray*)photoUrls photoCaptions:(nonnull NSArray*)photoCaptions success:(nonnull CommentSubmissionCompletion)success failure:(nonnull ConversationsFailureHandler)failure { + + + NSDictionary* parameters = [self createSubmissionParameters:action photoUrls:photoUrls photoCaptions:photoCaptions]; + NSData* postBody = [self transformToPostBody:parameters]; + + NSString* urlString = [NSString stringWithFormat:@"%@submitreviewcomment.json", [BVConversationsRequest commonEndpoint]]; + NSURL* url = [NSURL URLWithString:urlString]; + + NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url]; + [request setHTTPMethod:@"POST"]; + [request setHTTPBody:postBody]; + + [[BVLogger sharedLogger] verbose:[NSString stringWithFormat:@"POST: %@\n with BODY: %@", urlString, parameters]]; + + NSURLSession *session = [NSURLSession sharedSession]; + NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *httpError) { + + @try { + + NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; // This dataTask is used with only HTTP requests. + NSInteger statusCode = httpResponse.statusCode; + NSError* jsonParsingError; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonParsingError]; + BVCommentSubmissionErrorResponse* errorResponse = [[BVCommentSubmissionErrorResponse alloc] initWithApiResponse:json]; // fails gracefully + + [[BVLogger sharedLogger] verbose:[NSString stringWithFormat:@"RESPONSE: %@ (%ld)", json, (long)statusCode]]; + + if (httpError) { + // network error was generated + [self sendError:httpError failureCallback:failure]; + } + else if(statusCode >= 300){ + // HTTP status code indicates failure + NSError* statusError = [NSError errorWithDomain:@"com.bazaarvoice.bvsdk" code:BV_ERROR_NETWORK_FAILED userInfo:@{NSLocalizedDescriptionKey:@"Photo upload failed."}]; + [self sendError:statusError failureCallback:failure]; + } + else if (jsonParsingError) { + // json parsing failed + [self sendError:jsonParsingError failureCallback:failure]; + } + else if(errorResponse){ + // api returned successfully, but has bazaarvoice-specific errors. Example: 'invalid api key' + [self sendErrors:[errorResponse toNSErrors] failureCallback:failure]; + } + else { + // success! + + // Fire event now that we've confirmed the comment was successfully uploaded. + BVFeatureUsedEvent *commentQuestionEvent = [[BVFeatureUsedEvent alloc] initWithProductId:self.reviewId + withBrand:nil + withProductType:BVPixelProductTypeConversationsReviews + withEventName:BVPixelFeatureUsedEventNameReviewComment + withAdditionalParams:nil]; + + [BVPixel trackEvent:commentQuestionEvent]; + + BVCommentSubmissionResponse* response = [[BVCommentSubmissionResponse alloc] initWithApiResponse:json]; + dispatch_async(dispatch_get_main_queue(), ^{ + success(response); + }); + } + + } + @catch (NSException *exception) { + NSError* unknownError = [NSError errorWithDomain:BVErrDomain code:BV_ERROR_UNKNOWN userInfo:@{NSLocalizedDescriptionKey:@"An unknown parsing error occurred."}]; + [self sendError:unknownError failureCallback:failure]; + } + + }]; + + // start uploading comment + [postDataTask resume]; + +} + + +-(nonnull NSDictionary*)createSubmissionParameters:(BVSubmissionAction)action photoUrls:(nonnull NSArray*)photoUrls photoCaptions:(nonnull NSArray*)photoCaptions { + + NSMutableDictionary* parameters = [NSMutableDictionary dictionaryWithDictionary:@{ + @"apiversion": @"5.4", + @"commenttext": _commentText, + @"reviewid": _reviewId, + }]; + + parameters[@"passkey"] = [BVSDKManager sharedManager].configuration.apiKeyConversations; + parameters[@"action"] = [BVSubmissionActionUtil toString:action]; + + parameters[@"campaignid"] = self.campaignId; + parameters[@"locale"] = self.locale; + + parameters[@"title"] = self.commentTitle; + + parameters[@"hostedauthentication_authenticationemail"] = self.hostedAuthenticationEmail; + parameters[@"hostedauthentication_callbackurl"] = self.hostedAuthenticationCallback; + parameters[@"fp"] = self.fingerPrint; + parameters[@"user"] = self.user; + parameters[@"usernickname"] = self.userNickname; + parameters[@"useremail"] = self.userEmail; + parameters[@"userid"] = self.userId; + parameters[@"userlocation"] = self.userLocation; + + int photoIndex = 0; + for(NSString* url in photoUrls) { + NSString* key = [NSString stringWithFormat:@"photourl_%i", photoIndex]; + parameters[key] = url; + photoIndex += 1; + } + + int captionIndex = 0; + for(NSString* caption in photoCaptions) { + NSString* key = [NSString stringWithFormat:@"photocaption_%i", captionIndex]; + parameters[key] = caption; + captionIndex += 1; + } + + if (self.sendEmailAlertWhenPublished) { + parameters[@"sendemailalertwhenpublished"] = [self.sendEmailAlertWhenPublished boolValue] ? @"true" : @"false"; + } + + if (self.agreedToTermsAndConditions) { + parameters[@"agreedtotermsandconditions"] = [self.agreedToTermsAndConditions boolValue] ? @"true" : @"false"; + } + + return parameters; + +} + + +@end diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.h b/Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.h new file mode 100644 index 00000000..97d41081 --- /dev/null +++ b/Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.h @@ -0,0 +1,15 @@ +// +// BVCommentSubmissionErrorResponse.h +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVSubmissionErrorResponse.h" +#import "BVSubmittedComment.h" + +@interface BVCommentSubmissionErrorResponse : BVSubmissionErrorResponse + +@property BVSubmittedComment* _Nullable comment; + +@end diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.m b/Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.m new file mode 100644 index 00000000..d3b1a0b0 --- /dev/null +++ b/Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.m @@ -0,0 +1,25 @@ +// +// BVCommentSubmissionErrorResponse.m +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentSubmissionErrorResponse.h" + +@implementation BVCommentSubmissionErrorResponse + + +-(instancetype)initWithApiResponse:(nullable id)apiResponse { + + self = [super initWithApiResponse:apiResponse]; + + if(self){ + NSDictionary* apiObject = apiResponse; // [super initWithApiResponse:] checks that this is nonnull and a dictionary + self.comment = [[BVSubmittedComment alloc] initWithApiResponse:apiObject[@"Comment"]]; + } + + return self; +} + +@end diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.h b/Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.h new file mode 100644 index 00000000..fa976cf7 --- /dev/null +++ b/Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.h @@ -0,0 +1,15 @@ +// +// BVCommentSubmissionResponse.h +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVSubmissionResponse.h" +#import "BVSubmittedComment.h" + +@interface BVCommentSubmissionResponse : BVSubmissionResponse + +@property BVSubmittedComment* _Nullable comment; + +@end diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.m b/Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.m new file mode 100644 index 00000000..4b973dcd --- /dev/null +++ b/Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.m @@ -0,0 +1,24 @@ +// +// BVCommentSubmissionResponse.m +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentSubmissionResponse.h" + +@implementation BVCommentSubmissionResponse + +-(nonnull instancetype)initWithApiResponse:(NSDictionary*)apiResponse { + + self = [super initWithApiResponse:apiResponse]; + + if(self){ + self.comment = [[BVSubmittedComment alloc] initWithApiResponse:apiResponse[@"Comment"]]; + } + + return self; +} + + +@end diff --git a/Pod/BVConversations/Submission/Comment/BVSubmittedComment.h b/Pod/BVConversations/Submission/Comment/BVSubmittedComment.h new file mode 100644 index 00000000..76331dcb --- /dev/null +++ b/Pod/BVConversations/Submission/Comment/BVSubmittedComment.h @@ -0,0 +1,22 @@ +// +// BVSubmittedComment.h +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import + +@interface BVSubmittedComment : NSObject + +@property (readonly) NSString* _Nullable commentText; +@property (readonly) NSString* _Nullable title; +@property (readonly) BOOL sendEmailAlertWhenAnswered; +@property (readonly) NSDate* _Nullable submissionTime; +@property (readonly) NSNumber* _Nullable typicalHoursToPost; +@property (readonly) NSString* _Nullable submissionId; +@property (readonly) NSString* _Nullable commentId; + +-(nullable instancetype)initWithApiResponse:(nullable id)apiResponse; + +@end diff --git a/Pod/BVConversations/Submission/Comment/BVSubmittedComment.m b/Pod/BVConversations/Submission/Comment/BVSubmittedComment.m new file mode 100644 index 00000000..f1d3c0ff --- /dev/null +++ b/Pod/BVConversations/Submission/Comment/BVSubmittedComment.m @@ -0,0 +1,41 @@ +// +// BVSubmittedComment.m +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVSubmittedComment.h" +#import "BVNullHelper.h" +#import "BVModelUtil.h" + +@implementation BVSubmittedComment + +-(nullable instancetype)initWithApiResponse:(nullable id)apiResponse { + self = [super init]; + if(self){ + + if(apiResponse == nil || ![apiResponse isKindOfClass:[NSDictionary class]]){ + return nil; + } + + NSDictionary* apiObject = apiResponse; + + SET_IF_NOT_NULL(_commentText, apiObject[@"CommentText"]) + SET_IF_NOT_NULL(_title, apiObject[@"Title"]) + SET_IF_NOT_NULL(_submissionId, apiObject[@"SubmissionId"]) + SET_IF_NOT_NULL(_typicalHoursToPost, apiObject[@"TypicalHoursToPost"]) + SET_IF_NOT_NULL(_commentId, apiObject[@"CommentId"]) + + _submissionTime = [BVModelUtil convertTimestampToDatetime:apiObject[@"SubmissionTime"]]; + + NSNumber* emailAlert = apiObject[@"SendEmailAlertWhenAnswered"]; + if(emailAlert != nil) { + _sendEmailAlertWhenAnswered = [emailAlert boolValue]; + } + } + return self; +} + + +@end diff --git a/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.h b/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.h index da09bc33..98a50f8e 100644 --- a/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.h +++ b/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.h @@ -14,7 +14,8 @@ typedef void (^PhotoUploadFailure)(NSArray* _Nonnull errors); typedef NS_ENUM(NSInteger, BVPhotoContentType) { BVPhotoContentTypeReview, BVPhotoContentTypeQuestion, - BVPhotoContentTypeAnswer + BVPhotoContentTypeAnswer, + BVPhotoContentTypeComment // PRR only }; @interface BVUploadablePhoto : NSObject diff --git a/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.m b/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.m index dcab2847..e2272e9b 100644 --- a/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.m +++ b/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.m @@ -35,6 +35,7 @@ -(NSString*)BVPhotoContentTypeToString:(BVPhotoContentType)type { case BVPhotoContentTypeReview: return @"review"; case BVPhotoContentTypeAnswer: return @"answer"; case BVPhotoContentTypeQuestion: return @"question"; + case BVPhotoContentTypeComment: return @"review_comment"; } } diff --git a/Pod/BVConversations/Submission/Question/BVQuestionSubmission.h b/Pod/BVConversations/Submission/Question/BVQuestionSubmission.h index a66b0870..8a0532b3 100644 --- a/Pod/BVConversations/Submission/Question/BVQuestionSubmission.h +++ b/Pod/BVConversations/Submission/Question/BVQuestionSubmission.h @@ -10,7 +10,7 @@ #import "BVUploadablePhoto.h" #import "BVQuestionSubmissionResponse.h" #import "BVConversationsRequest.h" -#import "BVSubmission.h" +#import "BVBaseUGCSubmission.h" typedef void (^QuestionSubmissionCompletion)(BVQuestionSubmissionResponse* _Nonnull response); @@ -26,7 +26,7 @@ typedef void (^QuestionSubmissionCompletion)(BVQuestionSubmissionResponse* _Nonn @availability 4.1.0 and later */ -@interface BVQuestionSubmission : BVSubmission +@interface BVQuestionSubmission : BVBaseUGCSubmission /** Create a new BVQuestionSubmission. @@ -36,13 +36,6 @@ typedef void (^QuestionSubmissionCompletion)(BVQuestionSubmissionResponse* _Nonn -(nonnull instancetype)initWithProductId:(nonnull NSString*)productId; -(nonnull instancetype) __unavailable init; -/** - Submit a user-provided photo attached to this answer. - - @param image The user-provded image attached to this answer. - @param photoCaption The user-provided caption for the photo. - */ --(void)addPhoto:(nonnull UIImage*)image withPhotoCaption:(nullable NSString*)photoCaption; /** Submit this answer to the Bazaarvoice platform. If the `action` of this object is set to `BVSubmissionActionPreview` then the submission will NOT actually take place. @@ -54,26 +47,11 @@ typedef void (^QuestionSubmissionCompletion)(BVQuestionSubmissionResponse* _Nonn */ -(void)submit:(nonnull QuestionSubmissionCompletion)success failure:(nonnull ConversationsFailureHandler)failure; -@property BVSubmissionAction action; @property NSString* _Nullable questionSummary; @property NSString* _Nullable questionDetails; -@property NSString* _Nullable user; -@property NSString* _Nullable userEmail; -@property NSString* _Nullable userId; -@property NSString* _Nullable userLocation; -@property NSString* _Nullable userNickname; - -@property NSString* _Nullable campaignId; -@property NSString* _Nullable fingerPrint; -@property NSString* _Nullable hostedAuthenticationEmail; -@property NSString* _Nullable hostedAuthenticationCallback; -@property NSString* _Nullable locale; - @property NSNumber* _Nullable isUserAnonymous; -@property NSNumber* _Nullable agreedToTermsAndConditions; -@property NSNumber* _Nullable sendEmailAlertWhenPublished; @property NSNumber* _Nullable sendEmailAlertWhenAnswered; @property (readonly) NSString* _Nonnull productId; diff --git a/Pod/BVConversations/Submission/Question/BVQuestionSubmission.m b/Pod/BVConversations/Submission/Question/BVQuestionSubmission.m index c3228484..6498e109 100644 --- a/Pod/BVConversations/Submission/Question/BVQuestionSubmission.m +++ b/Pod/BVConversations/Submission/Question/BVQuestionSubmission.m @@ -13,7 +13,6 @@ @interface BVQuestionSubmission() @property (readwrite) NSString* _Nonnull productId; -@property NSMutableArray* _Nonnull photos; @property bool failureCalled; @end @@ -24,17 +23,10 @@ -(nonnull instancetype)initWithProductId:(nonnull NSString*)productId { self = [super init]; if(self){ self.productId = productId; - self.photos = [NSMutableArray array]; } return self; } --(void)addPhoto:(nonnull UIImage*)image withPhotoCaption:(nullable NSString*)photoCaption { - BVUploadablePhoto* photo = [[BVUploadablePhoto alloc] initWithPhoto:image photoCaption:photoCaption]; - [self.photos addObject:photo]; -} - - -(void)submit:(nonnull QuestionSubmissionCompletion)success failure:(nonnull ConversationsFailureHandler)failure { if (self.action == BVSubmissionActionPreview) { diff --git a/Pod/BVConversations/Submission/Review/BVReviewSubmission.h b/Pod/BVConversations/Submission/Review/BVReviewSubmission.h index 8e53141b..c1c47f90 100644 --- a/Pod/BVConversations/Submission/Review/BVReviewSubmission.h +++ b/Pod/BVConversations/Submission/Review/BVReviewSubmission.h @@ -7,10 +7,9 @@ #import #import "BVReviewSubmissionResponse.h" -#import "BVSubmissionAction.h" #import "BVConversationsRequest.h" #import "BVUploadablePhoto.h" -#import "BVSubmission.h" +#import "BVBaseUGCSubmission.h" typedef void (^ReviewSubmissionCompletion)(BVReviewSubmissionResponse* _Nonnull response); @@ -25,7 +24,7 @@ typedef void (^ReviewSubmissionCompletion)(BVReviewSubmissionResponse* _Nonnull @availability 4.1.0 and later */ -@interface BVReviewSubmission : BVSubmission +@interface BVReviewSubmission : BVBaseUGCSubmission /** Create a new BVReviewSubmission. @@ -38,13 +37,6 @@ typedef void (^ReviewSubmissionCompletion)(BVReviewSubmissionResponse* _Nonnull -(nonnull instancetype)initWithReviewTitle:(nonnull NSString*)reviewTitle reviewText:(nonnull NSString*)reviewText rating:(NSUInteger)rating productId:(nonnull NSString*)productId; -(nonnull instancetype) __unavailable init; -/** - Submit a user-provided photo attached to this answer. - - @param image The user-provded image attached to this answer. - @param photoCaption The user-provided caption for the photo. - */ --(void)addPhoto:(nonnull UIImage*)image withPhotoCaption:(nullable NSString*)photoCaption; /** Submit this answer to the Bazaarvoice platform. If the `action` of this object is set to `BVSubmissionActionPreview` then the submission will NOT actually take place. @@ -56,48 +48,6 @@ typedef void (^ReviewSubmissionCompletion)(BVReviewSubmissionResponse* _Nonnull */ -(void)submit:(nonnull ReviewSubmissionCompletion)success failure:(nonnull ConversationsFailureHandler)failure; -/** - Set whether or not to to perform a preview on the submission or submit for real. Default is BVSubmissionActionPreview. - A preview is like a dry run, but validation of the fields will occur through the API call over the network but no data will be submitted. - - */ -@property BVSubmissionAction action; - -/// Value of the encrypted user. This parameter demonstrates that a user has been authenticated. Note that the UserId parameter does not contain authentication information and should not be used for hosted authentication. See the Authenticate User method for more information. -@property NSString* _Nullable user; - -/// User's email address -@property NSString* _Nullable userEmail; - -/// User's external ID. Do not use email addresses for this value. -@property NSString* _Nullable userId; - -/// User location text -@property NSString* _Nullable userLocation; - -/// User nickname display text -@property NSString* _Nullable userNickname; - -/// Boolean indicating whether or not the user agreed to the terms and conditions. Required depending on the client's settings. -@property NSNumber* _Nullable agreedToTermsAndConditions; - -// Boolean indicating whether or not the user wants to be notified when a comment is posted on the content. -@property NSNumber* _Nullable sendEmailAlertWhenCommented; - -/// Boolean indicating whether or not the user wants to be notified when his/her content is published. -@property NSNumber* _Nullable sendEmailAlertWhenPublished; - -/// Locale to display Labels, Configuration, Product Attributes and Category Attributes in. The default value is the locale defined in the display associated with the API key. -@property NSString* _Nullable locale; - -/// Arbitrary text that may be saved alongside content to indicate vehicle by which content was captured, e.g. “post-purchase email”. -@property NSString* _Nullable campaignId; - -/// Email address where the submitter will receive the confirmation email. If you are configured to use hosted email authentication, this parameter is required. See the Authenticate User method for more information on hosted authentication. -@property NSString* _Nullable hostedAuthenticationEmail; - -/// URL of the link contained in the user authentication email. This should point to a landing page where a web application exists to complete the user authentication process. The host for the URL must be one of the domains configured for the client. The link in the email will contain a user authentication token (authtoken) that is used to verify the submitter. If you are configured to use hosted email authentication, this parameter is required. See the hosted authentication tutorial for more information. -@property NSString* _Nullable hostedAuthenticationCallback; /// Value is text representing a user comment to explain numerical Net Promoter score. @property NSString* _Nullable netPromoterComment; @@ -108,16 +58,6 @@ typedef void (^ReviewSubmissionCompletion)(BVReviewSubmissionResponse* _Nonnull /// Value is true or false; default is null – "true" or "false" answer to "I would recommend this to a friend". Required dependent on client settings. @property NSNumber* _Nullable isRecommended; -/** - Fingerprint of content author's device. See the Authenticity Tutorial for more information. - - Per the Bazaarvoice Authenticity Policy, you must send a device fingerprint attached to each submission. If you fail to send a device fingerprint with your submission, Bazaarvoice may take any action deemed necessary in Bazaarvoice’s sole discretion to protect the integrity of the network. Such actions may include but are not limited to: rejection of your content, halting syndication of your content on the Bazaarvoice network, revocation of your API key, or revocation of your API license. - */ -@property NSString* _Nullable fingerPrint; - -/// An array of BVUploadablePhoto objects to attach to a review submission. -@property NSMutableArray* _Nonnull photos; - /// The product id used to filter the review on. @property (readonly) NSString* _Nonnull productId; diff --git a/Pod/BVConversations/Submission/Review/BVReviewSubmission.m b/Pod/BVConversations/Submission/Review/BVReviewSubmission.m index c251c0e9..4cef9cbe 100644 --- a/Pod/BVConversations/Submission/Review/BVReviewSubmission.m +++ b/Pod/BVConversations/Submission/Review/BVReviewSubmission.m @@ -37,8 +37,7 @@ -(nonnull instancetype)initWithReviewTitle:(nonnull NSString*)reviewTitle review self.reviewText = reviewText; self.rating = rating; self.productId = productId; - self.photos = [NSMutableArray array]; - + self.additionalFields = [NSMutableDictionary dictionary]; self.contextDataValues = [NSMutableDictionary dictionary]; self.ratingQuestions = [NSMutableDictionary dictionary]; @@ -49,11 +48,6 @@ -(nonnull instancetype)initWithReviewTitle:(nonnull NSString*)reviewTitle review return self; } --(void)addPhoto:(nonnull UIImage*)image withPhotoCaption:(nullable NSString*)photoCaption { - BVUploadablePhoto* photo = [[BVUploadablePhoto alloc] initWithPhoto:image photoCaption:photoCaption]; - [self.photos addObject:photo]; -} - /// https://developer.bazaarvoice.com/apis/conversations/tutorials/field_types#additional-field -(void)addAdditionalField:(nonnull NSString*)fieldName value:(nonnull NSString*)value { diff --git a/Tests/Tests/ConversationsTests/DisplayTests/ProfileDisplayTests.swift b/Tests/Tests/ConversationsTests/DisplayTests/ProfileDisplayTests.swift index ab0f30dc..04f6a56b 100644 --- a/Tests/Tests/ConversationsTests/DisplayTests/ProfileDisplayTests.swift +++ b/Tests/Tests/ConversationsTests/DisplayTests/ProfileDisplayTests.swift @@ -16,7 +16,7 @@ class ProfileDisplayTests: XCTestCase { let configDict = ["clientId": "conciergeapidocumentation", "apiKeyConversations": "caB45h2jBqXFw1OE043qoMBD1gJC8EwFNCjktzgwncXY4"]; BVSDKManager.configure(withConfiguration: configDict, configType: .staging) - BVSDKManager.shared().setLogLevel(.error) + BVSDKManager.shared().setLogLevel(.verbose) } override func tearDown() { @@ -95,10 +95,14 @@ class ProfileDisplayTests: XCTestCase { .includeStatistics(.answers) .includeStatistics(.questions) .includeStatistics(.reviews) + //.includeStatistics(.reviewComments) // This is not supported by API, so will assert. + // other includes .include(.reviews, limit: 10) .include(.questions, limit: 10) .include(.answers, limit: 10) + .include(.reviewComments, limit: 10) + // sorts .sortIncludedAnswers(.submissionTime, order: .descending) .sortIncludedReviews(.submissionTime, order: .descending) @@ -136,6 +140,7 @@ class ProfileDisplayTests: XCTestCase { XCTAssertEqual(profile.includedReviews.count, 10) XCTAssertEqual(profile.includedQuestions.count, 10) XCTAssertEqual(profile.includedAnswers.count, 10) + XCTAssertEqual(profile.includedComments.count, 10) }) { (error) in diff --git a/Tests/Tests/ConversationsTests/SubmissionTests/AnswerSubmissionTests.swift b/Tests/Tests/ConversationsTests/SubmissionTests/AnswerSubmissionTests.swift index f0d26884..0d79f808 100644 --- a/Tests/Tests/ConversationsTests/SubmissionTests/AnswerSubmissionTests.swift +++ b/Tests/Tests/ConversationsTests/SubmissionTests/AnswerSubmissionTests.swift @@ -21,7 +21,7 @@ class AnswerSubmissionTests: XCTestCase { } func testSubmitAnswerWithPhoto() { - let expectation = self.expectation(description: "") + let expectation = self.expectation(description: "testSubmitAnswerWithPhoto") let answer = self.fillOutAnswer(.submit) answer.submit({ (answerSubmission) in @@ -36,7 +36,7 @@ class AnswerSubmissionTests: XCTestCase { } func testPreviewAnswerWithPhoto() { - let expectation = self.expectation(description: "") + let expectation = self.expectation(description: "testPreviewAnswerWithPhoto") let answer = self.fillOutAnswer(.preview) answer.submit({ (answerSubmission) in expectation.fulfill() diff --git a/Tests/Tests/ConversationsTests/SubmissionTests/CommentSubmissionTests.swift b/Tests/Tests/ConversationsTests/SubmissionTests/CommentSubmissionTests.swift new file mode 100644 index 00000000..04c58274 --- /dev/null +++ b/Tests/Tests/ConversationsTests/SubmissionTests/CommentSubmissionTests.swift @@ -0,0 +1,96 @@ +// +// CommentSubmissionTests.swift +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +import XCTest +@testable import BVSDK + +class CommentSubmissionTests: XCTestCase { + + override func setUp() { + super.setUp() + super.setUp() + let configDict = ["clientId": "conciergeapidocumentation", + "apiKeyConversations": "caB45h2jBqXFw1OE043qoMBD1gJC8EwFNCjktzgwncXY4"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + BVSDKManager.shared().setLogLevel(.error) + } + + override func tearDown() { + super.tearDown() + } + + func testSubmitReviewComment() { + + let expectation = self.expectation(description: "testSubmitReviewComment") + + let commentText = "Comment text Comment text Comment text Comment text Comment text Comment text Comment text Comment text" + let commentTitle = "Comment title" + let commentRequest = BVCommentSubmission(reviewId: "20134832", withCommentText: commentText) + commentRequest.action = .preview + + let randomId = String(arc4random()) // create a random id for testing only + + commentRequest.campaignId = "BV_COMMENT_CAMPAIGN_ID" + commentRequest.commentTitle = commentTitle + commentRequest.locale = "en_US" + commentRequest.sendEmailAlertWhenPublished = true + commentRequest.userNickname = "UserNickname" + randomId + commentRequest.userId = "UserId" + randomId + commentRequest.userEmail = "developer@bazaarvoice.com" + commentRequest.agreedToTermsAndConditions = true + +// if let image = PhotoUploadTests.createImage() { +// commentRequest.addPhoto(image, withPhotoCaption: "Yo dawg") +// } + + commentRequest.submit({ (commentSubmission) in + + XCTAssertTrue(commentSubmission.formFields?.keys.count == 11) + XCTAssertTrue(commentSubmission.comment?.title == commentTitle) + XCTAssertTrue(commentSubmission.comment?.commentText == commentText) + XCTAssertNil(commentSubmission.submissionId) + XCTAssertNotNil(commentSubmission.comment?.submissionTime) + XCTAssertTrue(commentSubmission.locale == "en_US") + + expectation.fulfill() + + }, failure: { (errors) in + expectation.fulfill() + XCTFail() + }) + + waitForExpectations(timeout: 10, handler: nil) + + + } + + func testSumbitCommentWithError() { + + let expectation = self.expectation(description: "testSubmitReviewComment") + + let commentText = "short text" + let commentRequest = BVCommentSubmission(reviewId: "12345", withCommentText: commentText) + commentRequest.action = .preview + + commentRequest.submit({ (commentSubmission) in + + XCTFail("Should not hit success block") + expectation.fulfill() + + }, failure: { (errors) in + + let firstError = errors.first! as NSError + XCTAssertNotNil(firstError.localizedDescription.range(of: "ERROR_PARAM_MISSING_USER_ID")) + expectation.fulfill() + + }) + + waitForExpectations(timeout: 10, handler: nil) + + } + +} diff --git a/Tests/Tests/resources/conversations/conversationsAuthorWithIncludes.json b/Tests/Tests/resources/conversations/conversationsAuthorWithIncludes.json index ba32c89a..3ad3256a 100644 --- a/Tests/Tests/resources/conversations/conversationsAuthorWithIncludes.json +++ b/Tests/Tests/resources/conversations/conversationsAuthorWithIncludes.json @@ -1,6 +1,127 @@ { "HasErrors": false, "Includes": { + "Comments": { + "11974": { + "UserNickname": "ub5bjb1Z6d6xS2VCYy6xALx6Q", + "CommentText": "TESTERRTESTERRTESTERRTESTERRTESTERRTESTERR", + "Videos": [], + "Photos": [], + "TotalNegativeFeedbackCount": 0, + "UserLocation": null, + "CampaignId": null, + "TotalPositiveFeedbackCount": 0, + "LastModificationTime": "2011-06-30T21:15:53.000+00:00", + "ProductRecommendationIds": [], + "BadgesOrder": [], + "AuthorId": "j52sdn51d88", + "SubmissionTime": "2011-03-29T10:53:10.000+00:00", + "TotalFeedbackCount": 0, + "Title": "TESTERR", + "ModerationStatus": "APPROVED", + "Badges": {}, + "IsSyndicated": false, + "SubmissionId": null, + "StoryId": null, + "LastModeratedTime": "2011-06-30T21:15:53.000+00:00", + "Id": "11974", + "ReviewId": "192451", + "IPAddress": null, + "ContentLocale": "fr_CA" + }, + "11975": { + "UserNickname": "ub5bjb1Z6d6xS2VCYy6xALx6Q", + "CommentText": "Pellentesque suscipit sollicitudin magna, sed fermentum ante vestibulumac.\n\nMaecenas quis nibh at arcu volutpat pharetra quis nontortor. Vestibulum arcu magna, pellentesque non pellentesque sit amet, varius nonmetus. Phasellus sit amet quam lectus, ac sagittiserat. Quisque nec velit sit amet arcu mattislaoreet.", + "Videos": [], + "Photos": [ + { + "Sizes": { + "normal": { + "Id": "normal", + "Url": "https://reviews.apitestcustomer.bazaarvoice.com/bvstaging/5556/79937/photo.jpg?client=apireadonlysandbox" + }, + "thumbnail": { + "Id": "thumbnail", + "Url": "https://reviews.apitestcustomer.bazaarvoice.com/bvstaging/5556/79937/photoThumb.jpg?client=apireadonlysandbox" + } + }, + "Id": "79937", + "SizesOrder": [ + "thumbnail", + "normal" + ], + "Caption": null + } + ], + "TotalNegativeFeedbackCount": 0, + "UserLocation": null, + "CampaignId": null, + "TotalPositiveFeedbackCount": 0, + "LastModificationTime": "2011-06-30T21:15:56.000+00:00", + "ProductRecommendationIds": [], + "BadgesOrder": [], + "AuthorId": "he5onxrlou8", + "SubmissionTime": "2011-04-15T21:14:13.000+00:00", + "TotalFeedbackCount": 0, + "Title": null, + "ModerationStatus": "APPROVED", + "Badges": {}, + "IsSyndicated": false, + "SubmissionId": null, + "StoryId": null, + "LastModeratedTime": "2011-06-30T21:15:56.000+00:00", + "Id": "11975", + "ReviewId": "192456", + "IPAddress": null, + "ContentLocale": "en_US" + }, + "11976": { + "UserNickname": "ub5bjb1Z6d6xS2VCYy6xALx6Q", + "CommentText": "Vestibulum luctus facilisis massa vestibulumscelerisque.\n\nDuis fermentum purus id leo consequat eget accumsan maurishendrerit. Mauris ut lacus non arcu faucibus scelerisque volutpat rhoncusdiam. Maecenas non sodalesdui.", + "Videos": [], + "Photos": [ + { + "Sizes": { + "normal": { + "Id": "normal", + "Url": "https://reviews.apitestcustomer.bazaarvoice.com/bvstaging/5556/79953/photo.jpg?client=apireadonlysandbox" + }, + "thumbnail": { + "Id": "thumbnail", + "Url": "https://reviews.apitestcustomer.bazaarvoice.com/bvstaging/5556/79953/photoThumb.jpg?client=apireadonlysandbox" + } + }, + "Id": "79953", + "SizesOrder": [ + "thumbnail", + "normal" + ], + "Caption": null + } + ], + "TotalNegativeFeedbackCount": 0, + "UserLocation": "Vestibulum et loremest.", + "CampaignId": null, + "TotalPositiveFeedbackCount": 0, + "LastModificationTime": "2011-06-30T21:15:59.000+00:00", + "ProductRecommendationIds": [], + "BadgesOrder": [], + "AuthorId": "he5onxrlou8", + "SubmissionTime": "2011-04-15T21:13:28.000+00:00", + "TotalFeedbackCount": 0, + "Title": null, + "ModerationStatus": "APPROVED", + "Badges": {}, + "IsSyndicated": false, + "SubmissionId": null, + "StoryId": null, + "LastModeratedTime": "2011-06-30T21:15:59.000+00:00", + "Id": "11976", + "ReviewId": "192463", + "IPAddress": null, + "ContentLocale": "en_US" + } + }, "Questions": { "14790": { "QuestionSummary": "Nunc lorem neque, condimentum id fermentum a, aliquam sit ametligula. Sed ullamcorper dignissim est idporttitor. Mauris ullamcorper", @@ -1567,6 +1688,11 @@ "192723", "192521" ], + "CommentsOrder": [ + "11976", + "11975", + "11974" + ], "AnswersOrder": [ "16259", "16265", @@ -1620,6 +1746,7 @@ }, "ContextDataValues": {}, "TotalReviewCount": 5, + "TotalCommentCount": 3, "Videos": [], "ContextDataValuesOrder": [], "ContributorRank": "TOP_25", @@ -1707,6 +1834,11 @@ "192723", "192521" ], + "CommentIds": [ + "11976", + "11975", + "11974" + ], "Id": "data-gen-user-c3k8hjvtpn03dupvxcui1rj3", "ThirdPartyIds": [], "SecondaryRatings": {}, diff --git a/Tests/Tests/resources/conversations/conversationsReviewsEnduranceCycles.json b/Tests/Tests/resources/conversations/conversationsReviewsEnduranceCycles.json index 70f3a6db..414a59f5 100644 --- a/Tests/Tests/resources/conversations/conversationsReviewsEnduranceCycles.json +++ b/Tests/Tests/resources/conversations/conversationsReviewsEnduranceCycles.json @@ -1,6 +1,37 @@ { "HasErrors": false, "Includes": { + "Comments": { + "1000149806": { + "Id": "1000149806", + "CID": "14d565e8-9f39-5080-a6bf-3cbd66d3b0c3", + "SourceClient": "branddemo", + "LastModificationTime": "2016-10-18T17:00:25.000+00:00", + "LastModeratedTime": "2016-10-18T17:00:25.000+00:00", + "ReviewId": "1014579910", + "CampaignId": "BV_PIE", + "AuthorId": "123abc", + "UserLocation": "Austin, TX", + "ContentLocale": "en_US", + "IsFeatured": false, + "TotalFeedbackCount": 6, + "TotalNegativeFeedbackCount": 5, + "TotalPositiveFeedbackCount": 1, + "ModerationStatus": "APPROVED", + "SubmissionId": "1zjgasdfasdfjmgb4ak7374haadyi", + "SubmissionTime": "2016-10-18T04:09:26.000+00:00", + "CommentText": "Sorry your setup experience wasn't ideal. We do offer build classes and the wheels could be swapped out during a free bike fit. If you don't like your bike call us, we want to make you happy!", + "UserNickname": "Strongbad", + "Badges": {}, + "BadgesOrder": [], + "Title": "We can help!", + "ProductRecommendationIds": [], + "IsSyndicated": false, + "StoryId": null, + "Photos": [], + "Videos": [] + } + }, "Products": { "6-bv": { "Brand": { @@ -173,7 +204,8 @@ "ModelNumbers": [] } }, - "ProductsOrder": ["6-bv"] + "ProductsOrder": ["6-bv"], + "CommentsOrder": ["1000149806"] }, "Offset": 0, "TotalResults": 40, @@ -239,12 +271,12 @@ "Rating": 1, "ContentLocale": "en_US", "RatingRange": 5, - "TotalCommentCount": 0, + "TotalCommentCount": 1, "ReviewText": "Lousy bike for the price. I have had Trek's, Giants, aluminum frames, carbon frames and this bike simply cannot compete with the bike shop brands. The Shimano 105 and BB30 crank is almost impossible to find in this price range. The assembly was lengthy and difficult and I was disappointed with the gear shift and the ride quality once I FINALLY got it set up. I swapped out the rims, which I think are the biggest downfall about the bike. Save yourself a headache and skip this bike.", "ModerationStatus": "APPROVED", "ClientResponses": [], "Id": "98247201", - "CommentIds": [], + "CommentIds": ["1000149806"], "SecondaryRatings": { "Value": { "Value": 1, @@ -2169,4 +2201,4 @@ "LastModeratedTime": "2016-06-16T07:40:26.000+00:00" }], "Limit": 20 -} \ No newline at end of file +}