diff --git a/.format.sh b/.format.sh index d0b55bd6..c796673f 100755 --- a/.format.sh +++ b/.format.sh @@ -1,6 +1,6 @@ #!/bin/bash -for DIRECTORY in Pod Examples Tests/Tests +for DIRECTORY in BVSDK Examples BVSDKTests do echo "Formatting code under $DIRECTORY/" find "$DIRECTORY" \( -name '*.h' -or -name '*.m' -or -name '*.mm' \) -print0 | xargs -0 "clang-format" -i diff --git a/.gitignore b/.gitignore index b56df7aa..6b26439e 100644 --- a/.gitignore +++ b/.gitignore @@ -41,5 +41,9 @@ Tests/installation_tests/carthage/Cartfile Tests/installation_tests/carthage/Cartfile.resolved Tests/installation_tests/carthage/Carthage/* +# ignore appledoc +misc/docset +misc/html + # BV Specific BVSDK.framework.zip diff --git a/BVSDK.podspec b/BVSDK.podspec index fd83b5cc..2c72a0e9 100644 --- a/BVSDK.podspec +++ b/BVSDK.podspec @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.name = "BVSDK" - s.version = '6.9.0' + s.version = '7.0.0' 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' } @@ -26,11 +26,13 @@ Pod::Spec.new do |s| s.default_subspec = 'BVCommon' s.subspec 'BVCommon' do |common| - common.source_files = 'Pod/BVCommon/**/*.{h,m}', 'Pod/BVAnalytics/**/*.{h,m}' + common.source_files = 'BVSDK/BVCommon/**/*.{h,m}', 'BVSDK/BVAnalytics/**/*.{h,m}' + common.private_header_files = 'BVSDK/BVCommon/**/Private/*.{h}', 'BVSDK/BVAnalytics/**/Private/*.{h}' end s.subspec 'BVCommonUI' do |commonui| - commonui.source_files = 'Pod/BVCommonUI/**/*.{h,m}' + commonui.source_files = 'BVSDK/BVCommonUI/**/*.{h,m}' + commonui.private_header_files = 'BVSDK/BVCommonUI/**/Private/*.{h}' end s.subspec 'BVAnalytics' do |analytics| @@ -38,43 +40,46 @@ Pod::Spec.new do |s| end s.subspec 'BVConversations' do |conversations| - conversations.source_files = 'Pod/BVConversations/**/*.{h,m}' + conversations.source_files = 'BVSDK/BVConversations/**/*.{h,m}' + conversations.private_header_files = 'BVSDK/BVConversations/**/Private/*.{h}' conversations.dependency 'BVSDK/BVCommon' end s.subspec 'BVConversationsStores' do |conversationsstores| - conversationsstores.source_files = 'Pod/BVConversationsStores/**/*.{h,m}' + conversationsstores.source_files = 'BVSDK/BVConversationsStores/**/*.{h,m}', 'BVSDK/BVConversations/**/Private/*.{h,m}' + conversationsstores.private_header_files = 'BVSDK/BVConversationsStores/**/Private/*.{h}' conversationsstores.dependency 'BVSDK/BVConversations' end s.subspec 'BVConversationsUI' do |conversationsui| - conversationsui.source_files = 'Pod/BVConversationsUI/**/*.{h,m}' + conversationsui.source_files = 'BVSDK/BVConversationsUI/**/*.{h,m}' conversationsui.dependency 'BVSDK/BVCommonUI' conversationsui.dependency 'BVSDK/BVConversationsStores' end s.subspec 'BVCurations' do |curations| - curations.source_files = 'Pod/BVCurations/**/*.{h,m}' + curations.source_files = 'BVSDK/BVCurations/**/*.{h,m}' curations.dependency 'BVSDK/BVCommon' end s.subspec 'BVCurationsUI' do |curationsui| - curationsui.source_files = 'Pod/BVCurationsUI/**/*.{h,m}' + curationsui.source_files = 'BVSDK/BVCurationsUI/**/*.{h,m}' curationsui.dependency 'BVSDK/BVCurations' curationsui.dependency 'BVSDK/BVCommonUI' - curationsui.resources = ["Pod/BVCurationsUI/SocialMediaIcons/*.xcassets"] + curationsui.resources = ["BVSDK/BVCurationsUI/SocialMediaIcons/*.xcassets"] end s.subspec 'BVNotifications' do |notifications| - notifications.source_files = 'Pod/BVNotifications/**/*.{h,m}', 'Pod/BVCommon/Private/*.{h,m}' - notifications.resources = ['Pod/BVNotifications/mapThumbnail.png'] + notifications.source_files = 'BVSDK/BVNotifications/**/*.{h,m}', 'BVSDK/BVCommon/Private/*.{h,m}' + notifications.resources = ['BVSDK/BVNotifications/mapThumbnail.png'] notifications.dependency 'BVSDK/BVConversationsUI' end s.subspec 'BVRecommendations' do |recs| - recs.source_files = 'Pod/BVRecommendations/**/*.{h,m}' + recs.source_files = 'BVSDK/BVRecommendations/**/*.{h,m}' + recs.private_header_files = 'BVSDK/BVRecommendations/**/Private/*.{h}' recs.dependency 'BVSDK/BVCommon' end diff --git a/BVSDK.xcodeproj/project.pbxproj b/BVSDK.xcodeproj/project.pbxproj index d936ed39..ec0685dd 100644 --- a/BVSDK.xcodeproj/project.pbxproj +++ b/BVSDK.xcodeproj/project.pbxproj @@ -125,7 +125,6 @@ 8777BB451EBA4F26008C4715 /* testSyndicationSource.json in Resources */ = {isa = PBXBuildFile; fileRef = 8777BB441EBA4F26008C4715 /* testSyndicationSource.json */; }; 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, ); }; }; @@ -137,7 +136,7 @@ 87A02B671E096B300002701B /* BVFeedbackSubmissionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 87A02B651E096B300002701B /* BVFeedbackSubmissionResponse.m */; }; 87A02B6A1E0971AC0002701B /* BVSubmittedFeedback.h in Headers */ = {isa = PBXBuildFile; fileRef = 87A02B681E0971AC0002701B /* BVSubmittedFeedback.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87A02B6B1E0971AC0002701B /* BVSubmittedFeedback.m in Sources */ = {isa = PBXBuildFile; fileRef = 87A02B691E0971AC0002701B /* BVSubmittedFeedback.m */; }; - 87A9ECB81E1DA87E00666636 /* productsToReviewResult.json in Resources */ = {isa = PBXBuildFile; fileRef = 87A9ECB71E1DA87E00666636 /* productsToReviewResult.json */; }; + 87A9ECB81E1DA87E00666636 /* malformedJSON.json in Resources */ = {isa = PBXBuildFile; fileRef = 87A9ECB71E1DA87E00666636 /* malformedJSON.json */; }; 87B6DF9C1ECA3A0F00B75835 /* CommentsDisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B6DF9B1ECA3A0F00B75835 /* CommentsDisplayTests.swift */; }; 87B6DFA11ECA424300B75835 /* BVCommentsRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 87B6DF9F1ECA424300B75835 /* BVCommentsRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87B6DFA21ECA424300B75835 /* BVCommentsRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 87B6DFA01ECA424300B75835 /* BVCommentsRequest.m */; }; @@ -174,22 +173,18 @@ 87C5FF5F1E36AB70004EE6E8 /* ProfileDisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C5FF5E1E36AB70004EE6E8 /* ProfileDisplayTests.swift */; }; 87C5FF621E36B416004EE6E8 /* BVAuthor.h in Headers */ = {isa = PBXBuildFile; fileRef = 87C5FF601E36B416004EE6E8 /* BVAuthor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87C5FF631E36B416004EE6E8 /* BVAuthor.m in Sources */ = {isa = PBXBuildFile; fileRef = 87C5FF611E36B416004EE6E8 /* BVAuthor.m */; }; - 87C5FF661E393ED3004EE6E8 /* BVAuthorContentType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87C5FF641E393ED3004EE6E8 /* BVAuthorContentType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87C5FF671E393ED3004EE6E8 /* BVAuthorContentType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87C5FF651E393ED3004EE6E8 /* BVAuthorContentType.m */; }; - 87C5FF6A1E394C73004EE6E8 /* BVAuthorInclude.h in Headers */ = {isa = PBXBuildFile; fileRef = 87C5FF681E394C73004EE6E8 /* BVAuthorInclude.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87C5FF6B1E394C73004EE6E8 /* BVAuthorInclude.m in Sources */ = {isa = PBXBuildFile; fileRef = 87C5FF691E394C73004EE6E8 /* BVAuthorInclude.m */; }; - 87D424E91E89C32E00147FDB /* BVReviewIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87D424E71E89C32E00147FDB /* BVReviewIncludeType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87D424E91E89C32E00147FDB /* BVReviewIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87D424E71E89C32E00147FDB /* BVReviewIncludeType.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87D424EA1E89C32E00147FDB /* BVReviewIncludeType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87D424E81E89C32E00147FDB /* BVReviewIncludeType.m */; }; 87D424F31E8AD45400147FDB /* BVCurationsUICollectionView.h in Headers */ = {isa = PBXBuildFile; fileRef = 870F133D1E490B5200D46BE6 /* BVCurationsUICollectionView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87D424F41E8AD4A500147FDB /* BVCurationsPostViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 870F133B1E490B5200D46BE6 /* BVCurationsPostViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87D425271E8EE39700147FDB /* BVViewedCGCEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 87D425251E8EE39700147FDB /* BVViewedCGCEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87D425281E8EE39700147FDB /* BVViewedCGCEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 87D425261E8EE39700147FDB /* BVViewedCGCEvent.m */; }; 87E28C831E5F88FE00915FDB /* BVPixelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 87E28C821E5F88FE00915FDB /* BVPixelTests.m */; }; - 87E810E51ECCA0190032C753 /* BVSortOptionsComments.h in Headers */ = {isa = PBXBuildFile; fileRef = 87E810E31ECCA0190032C753 /* BVSortOptionsComments.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87E810E61ECCA0190032C753 /* BVSortOptionsComments.m in Sources */ = {isa = PBXBuildFile; fileRef = 87E810E41ECCA0190032C753 /* BVSortOptionsComments.m */; }; - 87E810E91ECCA3CF0032C753 /* BVCommentFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87E810E71ECCA3CF0032C753 /* BVCommentFilterType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87E810E51ECCA0190032C753 /* BVCommentsSortOption.h in Headers */ = {isa = PBXBuildFile; fileRef = 87E810E31ECCA0190032C753 /* BVCommentsSortOption.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 87E810E61ECCA0190032C753 /* BVCommentsSortOption.m in Sources */ = {isa = PBXBuildFile; fileRef = 87E810E41ECCA0190032C753 /* BVCommentsSortOption.m */; }; + 87E810E91ECCA3CF0032C753 /* BVCommentFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87E810E71ECCA3CF0032C753 /* BVCommentFilterType.h */; settings = {ATTRIBUTES = (Private, ); }; }; 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, ); }; }; + 87E810ED1ECCC4CD0032C753 /* BVCommentIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87E810EB1ECCC4CD0032C753 /* BVCommentIncludeType.h */; settings = {ATTRIBUTES = (Private, ); }; }; 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, ); }; }; @@ -202,12 +197,12 @@ 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, ); }; }; - 87F2DC181DAD585E00FB43F3 /* BVAnalyticsManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DAC51DAD585E00FB43F3 /* BVAnalyticsManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DC181DAD585E00FB43F3 /* BVAnalyticsManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DAC51DAD585E00FB43F3 /* BVAnalyticsManager.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DC191DAD585E00FB43F3 /* BVAnalyticsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DAC61DAD585E00FB43F3 /* BVAnalyticsManager.m */; }; 87F2DC1E1DAD585E00FB43F3 /* BVPixel.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DACB1DAD585E00FB43F3 /* BVPixel.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87F2DC1F1DAD585E00FB43F3 /* BVPixel.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DACC1DAD585E00FB43F3 /* BVPixel.m */; }; 87F2DC241DAD585E00FB43F3 /* BVContextualInterests.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DAD21DAD585E00FB43F3 /* BVContextualInterests.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 87F2DC581DAD585E00FB43F3 /* BVCommaUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB0E1DAD585E00FB43F3 /* BVCommaUtil.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DC581DAD585E00FB43F3 /* BVCommaUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB0E1DAD585E00FB43F3 /* BVCommaUtil.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DC591DAD585E00FB43F3 /* BVCommaUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB0F1DAD585E00FB43F3 /* BVCommaUtil.m */; }; 87F2DC5C1DAD585E00FB43F3 /* BVBadge.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB131DAD585E00FB43F3 /* BVBadge.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87F2DC5D1DAD585E00FB43F3 /* BVBadge.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB141DAD585E00FB43F3 /* BVBadge.m */; }; @@ -271,10 +266,6 @@ 87F2DC981DAD585E00FB43F3 /* BVQuestion.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB501DAD585E00FB43F3 /* BVQuestion.m */; }; 87F2DC991DAD585E00FB43F3 /* BVReview.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB511DAD585E00FB43F3 /* BVReview.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87F2DC9A1DAD585E00FB43F3 /* BVReview.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB521DAD585E00FB43F3 /* BVReview.m */; }; - 87F2DCA31DAD585E00FB43F3 /* PDPContentType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB5D1DAD585E00FB43F3 /* PDPContentType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87F2DCA41DAD585E00FB43F3 /* PDPContentType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB5E1DAD585E00FB43F3 /* PDPContentType.m */; }; - 87F2DCA51DAD585E00FB43F3 /* PDPInclude.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB5F1DAD585E00FB43F3 /* PDPInclude.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87F2DCA61DAD585E00FB43F3 /* PDPInclude.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB601DAD585E00FB43F3 /* PDPInclude.m */; }; 87F2DCA71DAD585E00FB43F3 /* BVBulkRatingsRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB621DAD585E00FB43F3 /* BVBulkRatingsRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87F2DCA81DAD585E00FB43F3 /* BVBulkRatingsRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB631DAD585E00FB43F3 /* BVBulkRatingsRequest.m */; }; 87F2DCA91DAD585E00FB43F3 /* BVConversationsRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB641DAD585E00FB43F3 /* BVConversationsRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -285,28 +276,28 @@ 87F2DCAE1DAD585E00FB43F3 /* BVQuestionsAndAnswersRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB691DAD585E00FB43F3 /* BVQuestionsAndAnswersRequest.m */; }; 87F2DCAF1DAD585E00FB43F3 /* BVReviewsRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB6A1DAD585E00FB43F3 /* BVReviewsRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87F2DCB01DAD585E00FB43F3 /* BVReviewsRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB6B1DAD585E00FB43F3 /* BVReviewsRequest.m */; }; - 87F2DCB51DAD585E00FB43F3 /* BVBulkRatingsFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB721DAD585E00FB43F3 /* BVBulkRatingsFilterType.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87F2DCB61DAD585E00FB43F3 /* BVBulkRatingsFilterType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB731DAD585E00FB43F3 /* BVBulkRatingsFilterType.m */; }; - 87F2DCB71DAD585E00FB43F3 /* BVFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB741DAD585E00FB43F3 /* BVFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DCB51DAD585E00FB43F3 /* BVBulkRatingFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB721DAD585E00FB43F3 /* BVBulkRatingFilterType.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 87F2DCB61DAD585E00FB43F3 /* BVBulkRatingFilterType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB731DAD585E00FB43F3 /* BVBulkRatingFilterType.m */; }; + 87F2DCB71DAD585E00FB43F3 /* BVFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB741DAD585E00FB43F3 /* BVFilter.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DCB81DAD585E00FB43F3 /* BVFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB751DAD585E00FB43F3 /* BVFilter.m */; }; - 87F2DCB91DAD585E00FB43F3 /* BVFilterOperator.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB761DAD585E00FB43F3 /* BVFilterOperator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DCB91DAD585E00FB43F3 /* BVFilterOperator.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB761DAD585E00FB43F3 /* BVFilterOperator.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DCBA1DAD585E00FB43F3 /* BVFilterOperator.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB771DAD585E00FB43F3 /* BVFilterOperator.m */; }; - 87F2DCBB1DAD585E00FB43F3 /* BVProductFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB781DAD585E00FB43F3 /* BVProductFilterType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DCBB1DAD585E00FB43F3 /* BVProductFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB781DAD585E00FB43F3 /* BVProductFilterType.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DCBC1DAD585E00FB43F3 /* BVProductFilterType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB791DAD585E00FB43F3 /* BVProductFilterType.m */; }; - 87F2DCBD1DAD585E00FB43F3 /* BVQuestionFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB7A1DAD585E00FB43F3 /* BVQuestionFilterType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DCBD1DAD585E00FB43F3 /* BVQuestionFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB7A1DAD585E00FB43F3 /* BVQuestionFilterType.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DCBE1DAD585E00FB43F3 /* BVQuestionFilterType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB7B1DAD585E00FB43F3 /* BVQuestionFilterType.m */; }; - 87F2DCBF1DAD585E00FB43F3 /* BVReviewFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB7C1DAD585E00FB43F3 /* BVReviewFilterType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DCBF1DAD585E00FB43F3 /* BVReviewFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB7C1DAD585E00FB43F3 /* BVReviewFilterType.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DCC01DAD585E00FB43F3 /* BVReviewFilterType.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB7D1DAD585E00FB43F3 /* BVReviewFilterType.m */; }; - 87F2DCC11DAD585E00FB43F3 /* BVSort.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB7E1DAD585E00FB43F3 /* BVSort.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DCC11DAD585E00FB43F3 /* BVSort.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB7E1DAD585E00FB43F3 /* BVSort.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DCC21DAD585E00FB43F3 /* BVSort.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB7F1DAD585E00FB43F3 /* BVSort.m */; }; - 87F2DCC31DAD585E00FB43F3 /* BVSortOptionAnswers.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB801DAD585E00FB43F3 /* BVSortOptionAnswers.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87F2DCC41DAD585E00FB43F3 /* BVSortOptionAnswers.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB811DAD585E00FB43F3 /* BVSortOptionAnswers.m */; }; - 87F2DCC51DAD585E00FB43F3 /* BVSortOptionProducts.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB821DAD585E00FB43F3 /* BVSortOptionProducts.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87F2DCC61DAD585E00FB43F3 /* BVSortOptionProducts.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB831DAD585E00FB43F3 /* BVSortOptionProducts.m */; }; - 87F2DCC71DAD585E00FB43F3 /* BVSortOptionQuestions.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB841DAD585E00FB43F3 /* BVSortOptionQuestions.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87F2DCC81DAD585E00FB43F3 /* BVSortOptionQuestions.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB851DAD585E00FB43F3 /* BVSortOptionQuestions.m */; }; - 87F2DCC91DAD585E00FB43F3 /* BVSortOptionReviews.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB861DAD585E00FB43F3 /* BVSortOptionReviews.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 87F2DCCA1DAD585E00FB43F3 /* BVSortOptionReviews.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB871DAD585E00FB43F3 /* BVSortOptionReviews.m */; }; + 87F2DCC31DAD585E00FB43F3 /* BVAnswersSortOption.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB801DAD585E00FB43F3 /* BVAnswersSortOption.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 87F2DCC41DAD585E00FB43F3 /* BVAnswersSortOption.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB811DAD585E00FB43F3 /* BVAnswersSortOption.m */; }; + 87F2DCC51DAD585E00FB43F3 /* BVProductsSortOption.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB821DAD585E00FB43F3 /* BVProductsSortOption.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 87F2DCC61DAD585E00FB43F3 /* BVProductsSortOption.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB831DAD585E00FB43F3 /* BVProductsSortOption.m */; }; + 87F2DCC71DAD585E00FB43F3 /* BVQuestionsSortOption.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB841DAD585E00FB43F3 /* BVQuestionsSortOption.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 87F2DCC81DAD585E00FB43F3 /* BVQuestionsSortOption.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB851DAD585E00FB43F3 /* BVQuestionsSortOption.m */; }; + 87F2DCC91DAD585E00FB43F3 /* BVReviewsSortOption.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB861DAD585E00FB43F3 /* BVReviewsSortOption.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 87F2DCCA1DAD585E00FB43F3 /* BVReviewsSortOption.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB871DAD585E00FB43F3 /* BVReviewsSortOption.m */; }; 87F2DCCB1DAD585E00FB43F3 /* BVAnswerSubmission.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB8A1DAD585E00FB43F3 /* BVAnswerSubmission.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87F2DCCC1DAD585E00FB43F3 /* BVAnswerSubmission.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DB8B1DAD585E00FB43F3 /* BVAnswerSubmission.m */; }; 87F2DCCD1DAD585E00FB43F3 /* BVAnswerSubmissionErrorResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DB8C1DAD585E00FB43F3 /* BVAnswerSubmissionErrorResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -398,7 +389,7 @@ 87F2DD451DAD585E00FB43F3 /* BVShopperProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DC121DAD585E00FB43F3 /* BVShopperProfile.h */; settings = {ATTRIBUTES = (Public, ); }; }; 87F2DD461DAD585E00FB43F3 /* BVShopperProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DC131DAD585E00FB43F3 /* BVShopperProfile.m */; }; 87F2DD471DAD585E00FB43F3 /* BVShopperProfileRequestCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DC141DAD585E00FB43F3 /* BVShopperProfileRequestCache.m */; }; - 87F2DD481DAD585E00FB43F3 /* BVShopperProfileRequestCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DC161DAD585E00FB43F3 /* BVShopperProfileRequestCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 87F2DD481DAD585E00FB43F3 /* BVShopperProfileRequestCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 87F2DC161DAD585E00FB43F3 /* BVShopperProfileRequestCache.h */; settings = {ATTRIBUTES = (Private, ); }; }; 87F2DD4B1DAD589800FB43F3 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87F2DD4A1DAD589800FB43F3 /* UIKit.framework */; }; 87F2DD4D1DAD589E00FB43F3 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 87F2DD4C1DAD589E00FB43F3 /* Foundation.framework */; }; 87F2DD5E1DAD5E9400FB43F3 /* BVBaseStubTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DD551DAD5E9400FB43F3 /* BVBaseStubTestCase.m */; }; @@ -438,7 +429,6 @@ 87F2DDB51DAD698500FB43F3 /* testShowStory.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD851DAD698400FB43F3 /* testShowStory.json */; }; 87F2DDB61DAD698500FB43F3 /* testShowStorySparse.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD861DAD698500FB43F3 /* testShowStorySparse.json */; }; 87F2DDB71DAD698500FB43F3 /* curations500Error.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD881DAD698500FB43F3 /* curations500Error.json */; }; - 87F2DDB81DAD698500FB43F3 /* curationsEnduranceCycles.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD891DAD698500FB43F3 /* curationsEnduranceCycles.json */; }; 87F2DDB91DAD698500FB43F3 /* curationsFeedTest1.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD8A1DAD698500FB43F3 /* curationsFeedTest1.json */; }; 87F2DDBA1DAD698500FB43F3 /* post_ErrorParsingBody.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD8B1DAD698500FB43F3 /* post_ErrorParsingBody.json */; }; 87F2DDBB1DAD698500FB43F3 /* post_MissingRequiredKey.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD8C1DAD698500FB43F3 /* post_MissingRequiredKey.json */; }; @@ -446,7 +436,7 @@ 87F2DDBD1DAD698500FB43F3 /* puppy.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD8E1DAD698500FB43F3 /* puppy.jpg */; }; 87F2DDBE1DAD698500FB43F3 /* test_pattern.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD8F1DAD698500FB43F3 /* test_pattern.jpg */; }; 87F2DDBF1DAD698500FB43F3 /* emptyJSON.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD901DAD698500FB43F3 /* emptyJSON.json */; }; - 87F2DDC01DAD698500FB43F3 /* malformedJSON.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD911DAD698500FB43F3 /* malformedJSON.json */; }; + 87F2DDC01DAD698500FB43F3 /* productsToReviewResult.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD911DAD698500FB43F3 /* productsToReviewResult.json */; }; 87F2DDC11DAD698500FB43F3 /* recommendationsByCategoryId.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD931DAD698500FB43F3 /* recommendationsByCategoryId.json */; }; 87F2DDC21DAD698500FB43F3 /* recommendationsByProductId.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD941DAD698500FB43F3 /* recommendationsByProductId.json */; }; 87F2DDC31DAD698500FB43F3 /* recommendationsNullJSON.json in Resources */ = {isa = PBXBuildFile; fileRef = 87F2DD951DAD698500FB43F3 /* recommendationsNullJSON.json */; }; @@ -470,6 +460,9 @@ 87F2DE171DAD945D00FB43F3 /* StoreReviewSubmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87F2DE071DAD945D00FB43F3 /* StoreReviewSubmissionTests.swift */; }; 87FEC8CA1DB1145D007DD44E /* integrate-dynamic-framework.sh in Resources */ = {isa = PBXBuildFile; fileRef = 87FEC8C91DB1145D007DD44E /* integrate-dynamic-framework.sh */; }; 8D1764414ABEFA876BE81636 /* Pods_BVSDKTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7892A8713545E74147C58ECB /* Pods_BVSDKTests.framework */; }; + B56D717E2031F79F002EC872 /* BVLocaleServiceManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B56D717C2031F79F002EC872 /* BVLocaleServiceManager.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B56D717F2031F79F002EC872 /* BVLocaleServiceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B56D717D2031F79F002EC872 /* BVLocaleServiceManager.m */; }; + B56D71822032476A002EC872 /* BVLocaleManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B56D71812032476A002EC872 /* BVLocaleManagerTests.m */; }; B56E1F921FBF86360025D5B4 /* UASSubmissionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56E1F911FBF86360025D5B4 /* UASSubmissionTests.swift */; }; B575C3861FBDFD6F000F890B /* BVUASSubmission.h in Headers */ = {isa = PBXBuildFile; fileRef = B575C3841FBDFD6F000F890B /* BVUASSubmission.h */; settings = {ATTRIBUTES = (Public, ); }; }; B575C3871FBDFD6F000F890B /* BVUASSubmission.m in Sources */ = {isa = PBXBuildFile; fileRef = B575C3851FBDFD6F000F890B /* BVUASSubmission.m */; }; @@ -479,6 +472,35 @@ B575C38F1FBE1A4F000F890B /* BVUASSubmissionResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B575C38D1FBE1A4F000F890B /* BVUASSubmissionResponse.m */; }; B575C3921FBE2479000F890B /* BVSubmittedUAS.h in Headers */ = {isa = PBXBuildFile; fileRef = B575C3901FBE2479000F890B /* BVSubmittedUAS.h */; settings = {ATTRIBUTES = (Public, ); }; }; B575C3931FBE2479000F890B /* BVSubmittedUAS.m in Sources */ = {isa = PBXBuildFile; fileRef = B575C3911FBE2479000F890B /* BVSubmittedUAS.m */; }; + B583AED12006B4AA001E9548 /* BVRelationalFilterOperator.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AECF2006B4AA001E9548 /* BVRelationalFilterOperator.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AED22006B4AA001E9548 /* BVRelationalFilterOperator.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AED02006B4AA001E9548 /* BVRelationalFilterOperator.m */; }; + B583AED52006C18F001E9548 /* BVFilterType.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AED32006C18F001E9548 /* BVFilterType.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AED62006C18F001E9548 /* BVFilterType.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AED42006C18F001E9548 /* BVFilterType.m */; }; + B583AED92007F80A001E9548 /* BVSortOrder.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AED72007F80A001E9548 /* BVSortOrder.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AEDA2007F80A001E9548 /* BVSortOrder.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AED82007F80A001E9548 /* BVSortOrder.m */; }; + B583AEDD2007FB59001E9548 /* BVMonotonicSortOrder.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AEDB2007FB59001E9548 /* BVMonotonicSortOrder.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AEDE2007FB59001E9548 /* BVMonotonicSortOrder.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AEDC2007FB59001E9548 /* BVMonotonicSortOrder.m */; }; + B583AEE1200816A4001E9548 /* BVSortOption.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AEDF200816A4001E9548 /* BVSortOption.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AEE2200816A4001E9548 /* BVSortOption.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AEE0200816A4001E9548 /* BVSortOption.m */; }; + B583AEE5200940FB001E9548 /* BVInclude.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AEE3200940FB001E9548 /* BVInclude.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AEE6200940FB001E9548 /* BVInclude.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AEE4200940FB001E9548 /* BVInclude.m */; }; + B583AEE92009486C001E9548 /* BVIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AEE72009486C001E9548 /* BVIncludeType.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AEEA2009486C001E9548 /* BVIncludeType.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AEE82009486C001E9548 /* BVIncludeType.m */; }; + B583AEED20094A58001E9548 /* BVProductIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AEEB20094A58001E9548 /* BVProductIncludeType.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AEEE20094A58001E9548 /* BVProductIncludeType.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AEEC20094A58001E9548 /* BVProductIncludeType.m */; }; + B583AEF1200956D0001E9548 /* BVAuthorIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = B583AEEF200956D0001E9548 /* BVAuthorIncludeType.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B583AEF2200956D0001E9548 /* BVAuthorIncludeType.m in Sources */ = {isa = PBXBuildFile; fileRef = B583AEF0200956D0001E9548 /* BVAuthorIncludeType.m */; }; + B583B125200E8ABE001E9548 /* BVConversationDisplay.h in Headers */ = {isa = PBXBuildFile; fileRef = B583B11F200E8ABD001E9548 /* BVConversationDisplay.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B583B126200E8ABE001E9548 /* BVIncludeTypeValues.h in Headers */ = {isa = PBXBuildFile; fileRef = B583B120200E8ABD001E9548 /* BVIncludeTypeValues.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B583B127200E8ABE001E9548 /* BVSortOrderValues.h in Headers */ = {isa = PBXBuildFile; fileRef = B583B121200E8ABD001E9548 /* BVSortOrderValues.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B583B128200E8ABE001E9548 /* BVFilterTypeValues.h in Headers */ = {isa = PBXBuildFile; fileRef = B583B122200E8ABD001E9548 /* BVFilterTypeValues.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B583B129200E8ABE001E9548 /* BVFilterOperatorValues.h in Headers */ = {isa = PBXBuildFile; fileRef = B583B123200E8ABE001E9548 /* BVFilterOperatorValues.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B583B12A200E8ABE001E9548 /* BVSortOptionValues.h in Headers */ = {isa = PBXBuildFile; fileRef = B583B124200E8ABE001E9548 /* BVSortOptionValues.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B59185962040DADC006BE717 /* BVInternalAnalyticsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8779DB6C1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m */; }; + B59F8F1B203490CA00087EF0 /* BVNetworkingManager.h in Headers */ = {isa = PBXBuildFile; fileRef = B59F8F19203490CA00087EF0 /* BVNetworkingManager.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B59F8F1C203490CA00087EF0 /* BVNetworkingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B59F8F1A203490CA00087EF0 /* BVNetworkingManager.m */; }; + B59F8F1E2034C7B900087EF0 /* BVURLSessionDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B59F8F1D2034C7B900087EF0 /* BVURLSessionDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B59F8F232035EF2900087EF0 /* BVNetworkDelegateTestsDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = B59F8F222035EF2900087EF0 /* BVNetworkDelegateTestsDelegate.m */; }; B5A764A11FDAFB7B00B5DC9A /* BVViewsHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = B5A7649F1FDAFB7A00B5DC9A /* BVViewsHelper.h */; settings = {ATTRIBUTES = (Public, ); }; }; B5A764A21FDAFB7B00B5DC9A /* BVViewsHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A764A01FDAFB7B00B5DC9A /* BVViewsHelper.m */; }; B5A764A41FDAFB9C00B5DC9A /* UIImage+BundleLocator.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A764A31FDAFB9C00B5DC9A /* UIImage+BundleLocator.m */; }; @@ -505,13 +527,36 @@ B5A764D71FE1915200B5DC9A /* BVStoreReviewsResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = B5A764D31FE1915200B5DC9A /* BVStoreReviewsResponse.h */; settings = {ATTRIBUTES = (Public, ); }; }; B5A764D81FE1915200B5DC9A /* BVStoreReviewsResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A764D41FE1915200B5DC9A /* BVStoreReviewsResponse.m */; }; B5A764D91FE1915200B5DC9A /* BVBulkStoresResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A764D51FE1915200B5DC9A /* BVBulkStoresResponse.m */; }; - B5A764DC1FE1917C00B5DC9A /* BVStoreIncludeContentType.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A764DA1FE1917C00B5DC9A /* BVStoreIncludeContentType.m */; }; - B5A764DD1FE1917C00B5DC9A /* BVStoreIncludeContentType.h in Headers */ = {isa = PBXBuildFile; fileRef = B5A764DB1FE1917C00B5DC9A /* BVStoreIncludeContentType.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5A764DC1FE1917C00B5DC9A /* BVStoreIncludeType.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A764DA1FE1917C00B5DC9A /* BVStoreIncludeType.m */; }; + B5A764DD1FE1917C00B5DC9A /* BVStoreIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = B5A764DB1FE1917C00B5DC9A /* BVStoreIncludeType.h */; settings = {ATTRIBUTES = (Private, ); }; }; B5A764E21FE191AE00B5DC9A /* BVUploadableStorePhoto.h in Headers */ = {isa = PBXBuildFile; fileRef = B5A764E01FE191AE00B5DC9A /* BVUploadableStorePhoto.h */; settings = {ATTRIBUTES = (Public, ); }; }; B5A764E31FE191AE00B5DC9A /* BVUploadableStorePhoto.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A764E11FE191AE00B5DC9A /* BVUploadableStorePhoto.m */; }; B5A764E61FE191BA00B5DC9A /* BVStoreReviewSubmission.h in Headers */ = {isa = PBXBuildFile; fileRef = B5A764E41FE191BA00B5DC9A /* BVStoreReviewSubmission.h */; settings = {ATTRIBUTES = (Public, ); }; }; B5A764E71FE191BA00B5DC9A /* BVStoreReviewSubmission.m in Sources */ = {isa = PBXBuildFile; fileRef = B5A764E51FE191BA00B5DC9A /* BVStoreReviewSubmission.m */; }; B5A92D961FE85EEE008DA17C /* BVFeedbackSubmission.h in Headers */ = {isa = PBXBuildFile; fileRef = B5A92D951FE85EEE008DA17C /* BVFeedbackSubmission.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE768A200E984B002BA818 /* BVBulkRatingFilterValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE7689200E97DB002BA818 /* BVBulkRatingFilterValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE768C200E9FA0002BA818 /* BVCommentFilterValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE768B200E9F34002BA818 /* BVCommentFilterValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE768E200EA025002BA818 /* BVProductFilterValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE768D200E9FD2002BA818 /* BVProductFilterValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE7690200EA08C002BA818 /* BVQuestionFilterValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE768F200EA04D002BA818 /* BVQuestionFilterValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE7692200EA0E2002BA818 /* BVRelationalFilterOperatorValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE7691200EA0CA002BA818 /* BVRelationalFilterOperatorValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE7694200EA179002BA818 /* BVReviewFilterValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE7693200EA157002BA818 /* BVReviewFilterValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE7696200EA31D002BA818 /* BVAnswersSortOptionValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE7695200EA2E5002BA818 /* BVAnswersSortOptionValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE7698200EA355002BA818 /* BVAuthorIncludeTypeValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE7697200EA33C002BA818 /* BVAuthorIncludeTypeValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE769A200EA409002BA818 /* BVCommentIncludeTypeValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE7699200EA406002BA818 /* BVCommentIncludeTypeValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE769C200EA473002BA818 /* BVCommentsSortOptionValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE769B200EA473002BA818 /* BVCommentsSortOptionValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE769E200EA4FD002BA818 /* BVMonotonicSortOrderValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE769D200EA4DD002BA818 /* BVMonotonicSortOrderValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE76A0200EA53D002BA818 /* BVProductIncludeTypeValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE769F200EA53A002BA818 /* BVProductIncludeTypeValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE76A2200EA58A002BA818 /* BVProductsSortOptionValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE76A1200EA58A002BA818 /* BVProductsSortOptionValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE76A4200EA5EC002BA818 /* BVQuestionsSortOptionValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE76A3200EA5EC002BA818 /* BVQuestionsSortOptionValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE76A6200EA666002BA818 /* BVReviewIncludeTypeValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE76A5200EA666002BA818 /* BVReviewIncludeTypeValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE76A8200EA6B4002BA818 /* BVReviewsSortOptionValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE76A7200EA6B4002BA818 /* BVReviewsSortOptionValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE76AB200EB1DE002BA818 /* BVBaseProductRequest_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE76AA200EB1DE002BA818 /* BVBaseProductRequest_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B5BE76AD200EB4F8002BA818 /* BVBaseReviewsRequest_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE76AC200EB4F8002BA818 /* BVBaseReviewsRequest_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B5BE76AF200EB70E002BA818 /* BVCommentsRequest_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE76AE200EB70E002BA818 /* BVCommentsRequest_Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B5BE7875200FB887002BA818 /* BVStoreIncludeTypeValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE7874200FB887002BA818 /* BVStoreIncludeTypeValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE787720113462002BA818 /* BVBulkRatingIncludeTypeValue.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE787620113462002BA818 /* BVBulkRatingIncludeTypeValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5BE787A20113A32002BA818 /* BVBulkRatingIncludeType.h in Headers */ = {isa = PBXBuildFile; fileRef = B5BE787820113A32002BA818 /* BVBulkRatingIncludeType.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B5BE787B20113A32002BA818 /* BVBulkRatingIncludeType.m in Sources */ = {isa = PBXBuildFile; fileRef = B5BE787920113A32002BA818 /* BVBulkRatingIncludeType.m */; }; B5DDA04E1FE8633800C47C01 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5DDA04D1FE8633800C47C01 /* Assets.xcassets */; }; /* End PBXBuildFile section */ @@ -548,7 +593,7 @@ 155261E81E413A22008887A8 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; 158CF5851E71F4CD000EA063 /* BVBulkProductRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVBulkProductRequest.h; sourceTree = ""; }; 158CF5861E71F4CD000EA063 /* BVBulkProductRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBulkProductRequest.m; sourceTree = ""; }; - 15D4F83A1DF5D5F800E6B30D /* testNotificationProductConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = testNotificationProductConfig.json; path = Tests/Tests/resources/notification_config/testNotificationProductConfig.json; sourceTree = SOURCE_ROOT; }; + 15D4F83A1DF5D5F800E6B30D /* testNotificationProductConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = testNotificationProductConfig.json; sourceTree = ""; }; 15D4F83E1DF5EC3C00E6B30D /* ph.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = ph.png; sourceTree = ""; }; 15D4F8451DF5EFFF00E6B30D /* BVSDKTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BVSDKTests-Bridging-Header.h"; sourceTree = ""; }; 15DA53A81E4D05A70028402D /* BVSDKConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVSDKConfiguration.m; sourceTree = ""; }; @@ -561,7 +606,7 @@ 5F6201DD1F70342C00454D77 /* NSError+BVErrorCodeParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+BVErrorCodeParser.m"; sourceTree = ""; }; 5F6201E01F70399200454D77 /* NSError+BVSubmissionErrorCodeParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+BVSubmissionErrorCodeParser.h"; sourceTree = ""; }; 5F6201E21F7039EA00454D77 /* NSError+BVSubmissionErrorCodeParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSError+BVSubmissionErrorCodeParser.m"; sourceTree = ""; }; - 5FE2C0801F6B05100090DD55 /* BVErrorCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVErrorCode.h; path = ../BVErrorCode.h; sourceTree = ""; }; + 5FE2C0801F6B05100090DD55 /* BVErrorCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVErrorCode.h; sourceTree = ""; }; 7892A8713545E74147C58ECB /* Pods_BVSDKTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BVSDKTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 870F133B1E490B5200D46BE6 /* BVCurationsPostViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVCurationsPostViewController.h; sourceTree = ""; }; 870F133C1E490B5200D46BE6 /* BVCurationsPostViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVCurationsPostViewController.m; sourceTree = ""; }; @@ -631,22 +676,22 @@ 8777BB441EBA4F26008C4715 /* testSyndicationSource.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = testSyndicationSource.json; sourceTree = ""; }; 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; }; + 8779DB6C1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVInternalAnalyticsTest.m; path = BVSDKTests/AnalyticsTests/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; path = BVBasePIIEvent.h; sourceTree = ""; }; 879A628A1E80502100F46ECA /* BVBasePIIEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBasePIIEvent.m; sourceTree = ""; }; 879A62DD1E80621900F46ECA /* BVPixelEvents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVPixelEvents.h; sourceTree = ""; }; 879D08FB1DCA6CAE006B51D3 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; - 879D090F1DCA7DDE006B51D3 /* BVStoreNotificationConfigurationLoader+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "BVStoreNotificationConfigurationLoader+Private.h"; path = "Tests/Tests/BVStoreNotificationConfigurationLoader+Private.h"; sourceTree = SOURCE_ROOT; }; - 879D09101DCA8014006B51D3 /* testNotificationConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = testNotificationConfig.json; path = Tests/Tests/resources/notification_config/testNotificationConfig.json; sourceTree = SOURCE_ROOT; }; + 879D090F1DCA7DDE006B51D3 /* BVStoreNotificationConfigurationLoader+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "BVStoreNotificationConfigurationLoader+Private.h"; path = "BVSDKTests/NotificationTests/BVStoreNotificationConfigurationLoader+Private.h"; sourceTree = SOURCE_ROOT; }; + 879D09101DCA8014006B51D3 /* testNotificationConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = testNotificationConfig.json; sourceTree = ""; }; 87A02B5E1E0963710002701B /* FeedbackSubmissionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbackSubmissionTests.swift; sourceTree = ""; }; 87A02B611E0967EE0002701B /* BVFeedbackSubmission.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVFeedbackSubmission.m; sourceTree = ""; }; 87A02B641E096B300002701B /* BVFeedbackSubmissionResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVFeedbackSubmissionResponse.h; sourceTree = ""; }; 87A02B651E096B300002701B /* BVFeedbackSubmissionResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVFeedbackSubmissionResponse.m; sourceTree = ""; }; 87A02B681E0971AC0002701B /* BVSubmittedFeedback.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSubmittedFeedback.h; sourceTree = ""; }; 87A02B691E0971AC0002701B /* BVSubmittedFeedback.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVSubmittedFeedback.m; sourceTree = ""; }; - 87A9ECB71E1DA87E00666636 /* productsToReviewResult.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = productsToReviewResult.json; path = Tests/Tests/resources/pin/productsToReviewResult.json; sourceTree = SOURCE_ROOT; }; + 87A9ECB71E1DA87E00666636 /* malformedJSON.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = malformedJSON.json; sourceTree = ""; }; 87B6DF9B1ECA3A0F00B75835 /* CommentsDisplayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommentsDisplayTests.swift; sourceTree = ""; }; 87B6DF9F1ECA424300B75835 /* BVCommentsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVCommentsRequest.h; sourceTree = ""; }; 87B6DFA01ECA424300B75835 /* BVCommentsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVCommentsRequest.m; sourceTree = ""; }; @@ -684,17 +729,13 @@ 87C5FF5E1E36AB70004EE6E8 /* ProfileDisplayTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileDisplayTests.swift; sourceTree = ""; }; 87C5FF601E36B416004EE6E8 /* BVAuthor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVAuthor.h; sourceTree = ""; }; 87C5FF611E36B416004EE6E8 /* BVAuthor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAuthor.m; sourceTree = ""; }; - 87C5FF641E393ED3004EE6E8 /* BVAuthorContentType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVAuthorContentType.h; sourceTree = ""; }; - 87C5FF651E393ED3004EE6E8 /* BVAuthorContentType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAuthorContentType.m; sourceTree = ""; }; - 87C5FF681E394C73004EE6E8 /* BVAuthorInclude.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVAuthorInclude.h; sourceTree = ""; }; - 87C5FF691E394C73004EE6E8 /* BVAuthorInclude.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAuthorInclude.m; sourceTree = ""; }; 87D424E71E89C32E00147FDB /* BVReviewIncludeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVReviewIncludeType.h; sourceTree = ""; }; 87D424E81E89C32E00147FDB /* BVReviewIncludeType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVReviewIncludeType.m; sourceTree = ""; }; 87D425251E8EE39700147FDB /* BVViewedCGCEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVViewedCGCEvent.h; sourceTree = ""; }; 87D425261E8EE39700147FDB /* BVViewedCGCEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVViewedCGCEvent.m; sourceTree = ""; }; - 87E28C821E5F88FE00915FDB /* BVPixelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVPixelTests.m; path = Tests/BVPixelTests.m; sourceTree = SOURCE_ROOT; }; - 87E810E31ECCA0190032C753 /* BVSortOptionsComments.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSortOptionsComments.h; sourceTree = ""; }; - 87E810E41ECCA0190032C753 /* BVSortOptionsComments.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVSortOptionsComments.m; sourceTree = ""; }; + 87E28C821E5F88FE00915FDB /* BVPixelTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVPixelTests.m; path = BVSDKTests/AnalyticsTests/BVPixelTests.m; sourceTree = SOURCE_ROOT; }; + 87E810E31ECCA0190032C753 /* BVCommentsSortOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVCommentsSortOption.h; sourceTree = ""; }; + 87E810E41ECCA0190032C753 /* BVCommentsSortOption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVCommentsSortOption.m; sourceTree = ""; }; 87E810E71ECCA3CF0032C753 /* BVCommentFilterType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVCommentFilterType.h; sourceTree = ""; }; 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 = ""; }; @@ -713,8 +754,8 @@ 87F2DAAB1DAD579D00FB43F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 87F2DAB01DAD579D00FB43F3 /* BVSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BVSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 87F2DAB71DAD579D00FB43F3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 87F2DAC51DAD585E00FB43F3 /* BVAnalyticsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVAnalyticsManager.h; path = ../BVAnalyticsManager.h; sourceTree = ""; }; - 87F2DAC61DAD585E00FB43F3 /* BVAnalyticsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVAnalyticsManager.m; path = ../BVAnalyticsManager.m; sourceTree = ""; }; + 87F2DAC51DAD585E00FB43F3 /* BVAnalyticsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVAnalyticsManager.h; sourceTree = ""; }; + 87F2DAC61DAD585E00FB43F3 /* BVAnalyticsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAnalyticsManager.m; sourceTree = ""; }; 87F2DACB1DAD585E00FB43F3 /* BVPixel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVPixel.h; sourceTree = ""; }; 87F2DACC1DAD585E00FB43F3 /* BVPixel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVPixel.m; sourceTree = ""; }; 87F2DAD21DAD585E00FB43F3 /* BVContextualInterests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVContextualInterests.h; sourceTree = ""; }; @@ -782,10 +823,6 @@ 87F2DB501DAD585E00FB43F3 /* BVQuestion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVQuestion.m; sourceTree = ""; }; 87F2DB511DAD585E00FB43F3 /* BVReview.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVReview.h; sourceTree = ""; }; 87F2DB521DAD585E00FB43F3 /* BVReview.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVReview.m; sourceTree = ""; }; - 87F2DB5D1DAD585E00FB43F3 /* PDPContentType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PDPContentType.h; sourceTree = ""; }; - 87F2DB5E1DAD585E00FB43F3 /* PDPContentType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDPContentType.m; sourceTree = ""; }; - 87F2DB5F1DAD585E00FB43F3 /* PDPInclude.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PDPInclude.h; path = ../PDPInclude.h; sourceTree = ""; }; - 87F2DB601DAD585E00FB43F3 /* PDPInclude.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDPInclude.m; sourceTree = ""; }; 87F2DB621DAD585E00FB43F3 /* BVBulkRatingsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVBulkRatingsRequest.h; sourceTree = ""; }; 87F2DB631DAD585E00FB43F3 /* BVBulkRatingsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBulkRatingsRequest.m; sourceTree = ""; }; 87F2DB641DAD585E00FB43F3 /* BVConversationsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVConversationsRequest.h; sourceTree = ""; }; @@ -796,8 +833,8 @@ 87F2DB691DAD585E00FB43F3 /* BVQuestionsAndAnswersRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVQuestionsAndAnswersRequest.m; sourceTree = ""; }; 87F2DB6A1DAD585E00FB43F3 /* BVReviewsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVReviewsRequest.h; sourceTree = ""; }; 87F2DB6B1DAD585E00FB43F3 /* BVReviewsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVReviewsRequest.m; sourceTree = ""; }; - 87F2DB721DAD585E00FB43F3 /* BVBulkRatingsFilterType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVBulkRatingsFilterType.h; sourceTree = ""; }; - 87F2DB731DAD585E00FB43F3 /* BVBulkRatingsFilterType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBulkRatingsFilterType.m; sourceTree = ""; }; + 87F2DB721DAD585E00FB43F3 /* BVBulkRatingFilterType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVBulkRatingFilterType.h; sourceTree = ""; }; + 87F2DB731DAD585E00FB43F3 /* BVBulkRatingFilterType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBulkRatingFilterType.m; sourceTree = ""; }; 87F2DB741DAD585E00FB43F3 /* BVFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVFilter.h; sourceTree = ""; }; 87F2DB751DAD585E00FB43F3 /* BVFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVFilter.m; sourceTree = ""; }; 87F2DB761DAD585E00FB43F3 /* BVFilterOperator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVFilterOperator.h; sourceTree = ""; }; @@ -810,14 +847,14 @@ 87F2DB7D1DAD585E00FB43F3 /* BVReviewFilterType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVReviewFilterType.m; sourceTree = ""; }; 87F2DB7E1DAD585E00FB43F3 /* BVSort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSort.h; sourceTree = ""; }; 87F2DB7F1DAD585E00FB43F3 /* BVSort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVSort.m; sourceTree = ""; }; - 87F2DB801DAD585E00FB43F3 /* BVSortOptionAnswers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSortOptionAnswers.h; sourceTree = ""; }; - 87F2DB811DAD585E00FB43F3 /* BVSortOptionAnswers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVSortOptionAnswers.m; sourceTree = ""; }; - 87F2DB821DAD585E00FB43F3 /* BVSortOptionProducts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSortOptionProducts.h; sourceTree = ""; }; - 87F2DB831DAD585E00FB43F3 /* BVSortOptionProducts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVSortOptionProducts.m; sourceTree = ""; }; - 87F2DB841DAD585E00FB43F3 /* BVSortOptionQuestions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSortOptionQuestions.h; sourceTree = ""; }; - 87F2DB851DAD585E00FB43F3 /* BVSortOptionQuestions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVSortOptionQuestions.m; sourceTree = ""; }; - 87F2DB861DAD585E00FB43F3 /* BVSortOptionReviews.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSortOptionReviews.h; sourceTree = ""; }; - 87F2DB871DAD585E00FB43F3 /* BVSortOptionReviews.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVSortOptionReviews.m; sourceTree = ""; }; + 87F2DB801DAD585E00FB43F3 /* BVAnswersSortOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVAnswersSortOption.h; sourceTree = ""; }; + 87F2DB811DAD585E00FB43F3 /* BVAnswersSortOption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAnswersSortOption.m; sourceTree = ""; }; + 87F2DB821DAD585E00FB43F3 /* BVProductsSortOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVProductsSortOption.h; sourceTree = ""; }; + 87F2DB831DAD585E00FB43F3 /* BVProductsSortOption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVProductsSortOption.m; sourceTree = ""; }; + 87F2DB841DAD585E00FB43F3 /* BVQuestionsSortOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVQuestionsSortOption.h; sourceTree = ""; }; + 87F2DB851DAD585E00FB43F3 /* BVQuestionsSortOption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVQuestionsSortOption.m; sourceTree = ""; }; + 87F2DB861DAD585E00FB43F3 /* BVReviewsSortOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVReviewsSortOption.h; sourceTree = ""; }; + 87F2DB871DAD585E00FB43F3 /* BVReviewsSortOption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVReviewsSortOption.m; sourceTree = ""; }; 87F2DB8A1DAD585E00FB43F3 /* BVAnswerSubmission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVAnswerSubmission.h; sourceTree = ""; }; 87F2DB8B1DAD585E00FB43F3 /* BVAnswerSubmission.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAnswerSubmission.m; sourceTree = ""; }; 87F2DB8C1DAD585E00FB43F3 /* BVAnswerSubmissionErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVAnswerSubmissionErrorResponse.h; sourceTree = ""; }; @@ -912,13 +949,13 @@ 87F2DC161DAD585E00FB43F3 /* BVShopperProfileRequestCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVShopperProfileRequestCache.h; sourceTree = ""; }; 87F2DD4A1DAD589800FB43F3 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 87F2DD4C1DAD589E00FB43F3 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; - 87F2DD541DAD5E9400FB43F3 /* BVBaseStubTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVBaseStubTestCase.h; path = Tests/Tests/BVBaseStubTestCase.h; sourceTree = SOURCE_ROOT; }; - 87F2DD551DAD5E9400FB43F3 /* BVBaseStubTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVBaseStubTestCase.m; path = Tests/Tests/BVBaseStubTestCase.m; sourceTree = SOURCE_ROOT; }; - 87F2DD571DAD5E9400FB43F3 /* BVCurationsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVCurationsTests.m; path = Tests/Tests/BVCurationsTests.m; sourceTree = SOURCE_ROOT; }; - 87F2DD591DAD5E9400FB43F3 /* BVNotificationConfigTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVNotificationConfigTests.h; path = Tests/Tests/BVNotificationConfigTests.h; sourceTree = SOURCE_ROOT; }; - 87F2DD5A1DAD5E9400FB43F3 /* BVNotificationConfigTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVNotificationConfigTests.m; path = Tests/Tests/BVNotificationConfigTests.m; sourceTree = SOURCE_ROOT; }; - 87F2DD5B1DAD5E9400FB43F3 /* BVRecommendationsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVRecommendationsTests.m; path = Tests/Tests/BVRecommendationsTests.m; sourceTree = SOURCE_ROOT; }; - 87F2DD5C1DAD5E9400FB43F3 /* BVUserProfileTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVUserProfileTests.m; path = Tests/Tests/BVUserProfileTests.m; sourceTree = SOURCE_ROOT; }; + 87F2DD541DAD5E9400FB43F3 /* BVBaseStubTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVBaseStubTestCase.h; sourceTree = ""; }; + 87F2DD551DAD5E9400FB43F3 /* BVBaseStubTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBaseStubTestCase.m; sourceTree = ""; }; + 87F2DD571DAD5E9400FB43F3 /* BVCurationsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVCurationsTests.m; path = BVSDKTests/CurationsTests/BVCurationsTests.m; sourceTree = SOURCE_ROOT; }; + 87F2DD591DAD5E9400FB43F3 /* BVNotificationConfigTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVNotificationConfigTests.h; path = BVSDKTests/NotificationTests/BVNotificationConfigTests.h; sourceTree = SOURCE_ROOT; }; + 87F2DD5A1DAD5E9400FB43F3 /* BVNotificationConfigTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVNotificationConfigTests.m; path = BVSDKTests/NotificationTests/BVNotificationConfigTests.m; sourceTree = SOURCE_ROOT; }; + 87F2DD5B1DAD5E9400FB43F3 /* BVRecommendationsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVRecommendationsTests.m; path = BVSDKTests/RecommendationsTests/BVRecommendationsTests.m; sourceTree = SOURCE_ROOT; }; + 87F2DD5C1DAD5E9400FB43F3 /* BVUserProfileTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVUserProfileTests.m; path = BVSDKTests/CommonTests/BVUserProfileTests.m; sourceTree = SOURCE_ROOT; }; 87F2DD661DAD698400FB43F3 /* conversationsGenericPostResponse.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = conversationsGenericPostResponse.json; sourceTree = ""; }; 87F2DD671DAD698400FB43F3 /* conversationsQuestionsIncludeAnswers.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = conversationsQuestionsIncludeAnswers.json; sourceTree = ""; }; 87F2DD681DAD698400FB43F3 /* conversationsReviewsEnduranceCycles.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = conversationsReviewsEnduranceCycles.json; sourceTree = ""; }; @@ -951,15 +988,14 @@ 87F2DD851DAD698400FB43F3 /* testShowStory.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = testShowStory.json; sourceTree = ""; }; 87F2DD861DAD698500FB43F3 /* testShowStorySparse.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = testShowStorySparse.json; sourceTree = ""; }; 87F2DD881DAD698500FB43F3 /* curations500Error.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = curations500Error.json; sourceTree = ""; }; - 87F2DD891DAD698500FB43F3 /* curationsEnduranceCycles.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = curationsEnduranceCycles.json; sourceTree = ""; }; 87F2DD8A1DAD698500FB43F3 /* curationsFeedTest1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = curationsFeedTest1.json; sourceTree = ""; }; 87F2DD8B1DAD698500FB43F3 /* post_ErrorParsingBody.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = post_ErrorParsingBody.json; sourceTree = ""; }; 87F2DD8C1DAD698500FB43F3 /* post_MissingRequiredKey.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = post_MissingRequiredKey.json; sourceTree = ""; }; 87F2DD8D1DAD698500FB43F3 /* post_successfulCreation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = post_successfulCreation.json; sourceTree = ""; }; 87F2DD8E1DAD698500FB43F3 /* puppy.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = puppy.jpg; sourceTree = ""; }; 87F2DD8F1DAD698500FB43F3 /* test_pattern.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = test_pattern.jpg; sourceTree = ""; }; - 87F2DD901DAD698500FB43F3 /* emptyJSON.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = emptyJSON.json; path = Tests/Tests/resources/emptyJSON.json; sourceTree = SOURCE_ROOT; }; - 87F2DD911DAD698500FB43F3 /* malformedJSON.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = malformedJSON.json; path = Tests/Tests/resources/malformedJSON.json; sourceTree = SOURCE_ROOT; }; + 87F2DD901DAD698500FB43F3 /* emptyJSON.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = emptyJSON.json; sourceTree = ""; }; + 87F2DD911DAD698500FB43F3 /* productsToReviewResult.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = productsToReviewResult.json; sourceTree = ""; }; 87F2DD931DAD698500FB43F3 /* recommendationsByCategoryId.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommendationsByCategoryId.json; sourceTree = ""; }; 87F2DD941DAD698500FB43F3 /* recommendationsByProductId.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommendationsByProductId.json; sourceTree = ""; }; 87F2DD951DAD698500FB43F3 /* recommendationsNullJSON.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = recommendationsNullJSON.json; sourceTree = ""; }; @@ -983,6 +1019,9 @@ 87F2DE071DAD945D00FB43F3 /* StoreReviewSubmissionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoreReviewSubmissionTests.swift; sourceTree = ""; }; 87FEC8C91DB1145D007DD44E /* integrate-dynamic-framework.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "integrate-dynamic-framework.sh"; sourceTree = ""; }; 89529D79D81B13BA86882542 /* Pods-BVSDKTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BVSDKTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-BVSDKTests/Pods-BVSDKTests.release.xcconfig"; sourceTree = ""; }; + B56D717C2031F79F002EC872 /* BVLocaleServiceManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVLocaleServiceManager.h; sourceTree = ""; }; + B56D717D2031F79F002EC872 /* BVLocaleServiceManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVLocaleServiceManager.m; sourceTree = ""; }; + B56D71812032476A002EC872 /* BVLocaleManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BVLocaleManagerTests.m; path = BVSDKTests/CommonTests/BVLocaleManagerTests.m; sourceTree = SOURCE_ROOT; }; B56E1F911FBF86360025D5B4 /* UASSubmissionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UASSubmissionTests.swift; sourceTree = ""; }; B575C3841FBDFD6F000F890B /* BVUASSubmission.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BVUASSubmission.h; path = ../Auth/BVUASSubmission.h; sourceTree = ""; }; B575C3851FBDFD6F000F890B /* BVUASSubmission.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BVUASSubmission.m; path = ../Auth/BVUASSubmission.m; sourceTree = ""; }; @@ -992,6 +1031,35 @@ B575C38D1FBE1A4F000F890B /* BVUASSubmissionResponse.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BVUASSubmissionResponse.m; path = ../Auth/BVUASSubmissionResponse.m; sourceTree = ""; }; B575C3901FBE2479000F890B /* BVSubmittedUAS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BVSubmittedUAS.h; path = ../Auth/BVSubmittedUAS.h; sourceTree = ""; }; B575C3911FBE2479000F890B /* BVSubmittedUAS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = BVSubmittedUAS.m; path = ../Auth/BVSubmittedUAS.m; sourceTree = ""; }; + B583AECF2006B4AA001E9548 /* BVRelationalFilterOperator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVRelationalFilterOperator.h; sourceTree = ""; }; + B583AED02006B4AA001E9548 /* BVRelationalFilterOperator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVRelationalFilterOperator.m; sourceTree = ""; }; + B583AED32006C18F001E9548 /* BVFilterType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVFilterType.h; sourceTree = ""; }; + B583AED42006C18F001E9548 /* BVFilterType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVFilterType.m; sourceTree = ""; }; + B583AED72007F80A001E9548 /* BVSortOrder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVSortOrder.h; sourceTree = ""; }; + B583AED82007F80A001E9548 /* BVSortOrder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVSortOrder.m; sourceTree = ""; }; + B583AEDB2007FB59001E9548 /* BVMonotonicSortOrder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVMonotonicSortOrder.h; sourceTree = ""; }; + B583AEDC2007FB59001E9548 /* BVMonotonicSortOrder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVMonotonicSortOrder.m; sourceTree = ""; }; + B583AEDF200816A4001E9548 /* BVSortOption.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVSortOption.h; sourceTree = ""; }; + B583AEE0200816A4001E9548 /* BVSortOption.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVSortOption.m; sourceTree = ""; }; + B583AEE3200940FB001E9548 /* BVInclude.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVInclude.h; sourceTree = ""; }; + B583AEE4200940FB001E9548 /* BVInclude.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVInclude.m; sourceTree = ""; }; + B583AEE72009486C001E9548 /* BVIncludeType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVIncludeType.h; sourceTree = ""; }; + B583AEE82009486C001E9548 /* BVIncludeType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVIncludeType.m; sourceTree = ""; }; + B583AEEB20094A58001E9548 /* BVProductIncludeType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVProductIncludeType.h; sourceTree = ""; }; + B583AEEC20094A58001E9548 /* BVProductIncludeType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVProductIncludeType.m; sourceTree = ""; }; + B583AEEF200956D0001E9548 /* BVAuthorIncludeType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVAuthorIncludeType.h; sourceTree = ""; }; + B583AEF0200956D0001E9548 /* BVAuthorIncludeType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVAuthorIncludeType.m; sourceTree = ""; }; + B583B11F200E8ABD001E9548 /* BVConversationDisplay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVConversationDisplay.h; sourceTree = ""; }; + B583B120200E8ABD001E9548 /* BVIncludeTypeValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVIncludeTypeValues.h; sourceTree = ""; }; + B583B121200E8ABD001E9548 /* BVSortOrderValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSortOrderValues.h; sourceTree = ""; }; + B583B122200E8ABD001E9548 /* BVFilterTypeValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVFilterTypeValues.h; sourceTree = ""; }; + B583B123200E8ABE001E9548 /* BVFilterOperatorValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVFilterOperatorValues.h; sourceTree = ""; }; + B583B124200E8ABE001E9548 /* BVSortOptionValues.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVSortOptionValues.h; sourceTree = ""; }; + B59F8F19203490CA00087EF0 /* BVNetworkingManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVNetworkingManager.h; sourceTree = ""; }; + B59F8F1A203490CA00087EF0 /* BVNetworkingManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVNetworkingManager.m; sourceTree = ""; }; + B59F8F1D2034C7B900087EF0 /* BVURLSessionDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVURLSessionDelegate.h; sourceTree = ""; }; + B59F8F212035EF2900087EF0 /* BVNetworkDelegateTestsDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVNetworkDelegateTestsDelegate.h; sourceTree = ""; }; + B59F8F222035EF2900087EF0 /* BVNetworkDelegateTestsDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVNetworkDelegateTestsDelegate.m; sourceTree = ""; }; B5A7649F1FDAFB7A00B5DC9A /* BVViewsHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVViewsHelper.h; sourceTree = ""; }; B5A764A01FDAFB7B00B5DC9A /* BVViewsHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVViewsHelper.m; sourceTree = ""; }; B5A764A31FDAFB9C00B5DC9A /* UIImage+BundleLocator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+BundleLocator.m"; sourceTree = ""; }; @@ -1006,25 +1074,48 @@ B5A764AF1FDB1ACD00B5DC9A /* BVAnswerCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAnswerCollectionViewCell.m; sourceTree = ""; }; B5A764B01FDB1ACE00B5DC9A /* BVAnswerView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAnswerView.m; sourceTree = ""; }; B5A764B11FDB1ACE00B5DC9A /* BVAnswerTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVAnswerTableViewCell.m; sourceTree = ""; }; - B5A764C21FE1912700B5DC9A /* BVStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVStore.h; path = Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.h; sourceTree = SOURCE_ROOT; }; - B5A764C31FE1912800B5DC9A /* BVStoreLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVStoreLocation.h; path = Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.h; sourceTree = SOURCE_ROOT; }; - B5A764C41FE1912800B5DC9A /* BVStoreLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVStoreLocation.m; path = Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.m; sourceTree = SOURCE_ROOT; }; - B5A764C51FE1912800B5DC9A /* BVStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVStore.m; path = Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.m; sourceTree = SOURCE_ROOT; }; - B5A764CA1FE1913800B5DC9A /* BVStoreReviewsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVStoreReviewsRequest.m; path = Pod/BVConversationsStores/Display/Model/Requests/BVStoreReviewsRequest.m; sourceTree = SOURCE_ROOT; }; - B5A764CB1FE1913800B5DC9A /* BVStoreReviewsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVStoreReviewsRequest.h; path = Pod/BVConversationsStores/Display/Model/Requests/BVStoreReviewsRequest.h; sourceTree = SOURCE_ROOT; }; - B5A764CC1FE1913800B5DC9A /* BVBulkStoreItemsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVBulkStoreItemsRequest.h; path = Pod/BVConversationsStores/Display/Model/Requests/BVBulkStoreItemsRequest.h; sourceTree = SOURCE_ROOT; }; - B5A764CD1FE1913800B5DC9A /* BVBulkStoreItemsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVBulkStoreItemsRequest.m; path = Pod/BVConversationsStores/Display/Model/Requests/BVBulkStoreItemsRequest.m; sourceTree = SOURCE_ROOT; }; - B5A764D21FE1915200B5DC9A /* BVBulkStoresResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVBulkStoresResponse.h; path = Pod/BVConversationsStores/Display/Model/BVBulkStoresResponse.h; sourceTree = SOURCE_ROOT; }; - B5A764D31FE1915200B5DC9A /* BVStoreReviewsResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVStoreReviewsResponse.h; path = Pod/BVConversationsStores/Display/Model/BVStoreReviewsResponse.h; sourceTree = SOURCE_ROOT; }; - B5A764D41FE1915200B5DC9A /* BVStoreReviewsResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVStoreReviewsResponse.m; path = Pod/BVConversationsStores/Display/Model/BVStoreReviewsResponse.m; sourceTree = SOURCE_ROOT; }; - B5A764D51FE1915200B5DC9A /* BVBulkStoresResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVBulkStoresResponse.m; path = Pod/BVConversationsStores/Display/Model/BVBulkStoresResponse.m; sourceTree = SOURCE_ROOT; }; - B5A764DA1FE1917C00B5DC9A /* BVStoreIncludeContentType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVStoreIncludeContentType.m; path = Pod/BVConversationsStores/Display/BVStoreIncludeContentType.m; sourceTree = SOURCE_ROOT; }; - B5A764DB1FE1917C00B5DC9A /* BVStoreIncludeContentType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVStoreIncludeContentType.h; path = Pod/BVConversationsStores/Display/BVStoreIncludeContentType.h; sourceTree = SOURCE_ROOT; }; - B5A764E01FE191AE00B5DC9A /* BVUploadableStorePhoto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVUploadableStorePhoto.h; path = Pod/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.h; sourceTree = SOURCE_ROOT; }; - B5A764E11FE191AE00B5DC9A /* BVUploadableStorePhoto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVUploadableStorePhoto.m; path = Pod/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.m; sourceTree = SOURCE_ROOT; }; - B5A764E41FE191BA00B5DC9A /* BVStoreReviewSubmission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVStoreReviewSubmission.h; path = Pod/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.h; sourceTree = SOURCE_ROOT; }; - B5A764E51FE191BA00B5DC9A /* BVStoreReviewSubmission.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVStoreReviewSubmission.m; path = Pod/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.m; sourceTree = SOURCE_ROOT; }; + B5A764C21FE1912700B5DC9A /* BVStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVStore.h; sourceTree = ""; }; + B5A764C31FE1912800B5DC9A /* BVStoreLocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVStoreLocation.h; sourceTree = ""; }; + B5A764C41FE1912800B5DC9A /* BVStoreLocation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVStoreLocation.m; sourceTree = ""; }; + B5A764C51FE1912800B5DC9A /* BVStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVStore.m; sourceTree = ""; }; + B5A764CA1FE1913800B5DC9A /* BVStoreReviewsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVStoreReviewsRequest.m; sourceTree = ""; }; + B5A764CB1FE1913800B5DC9A /* BVStoreReviewsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVStoreReviewsRequest.h; sourceTree = ""; }; + B5A764CC1FE1913800B5DC9A /* BVBulkStoreItemsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVBulkStoreItemsRequest.h; sourceTree = ""; }; + B5A764CD1FE1913800B5DC9A /* BVBulkStoreItemsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBulkStoreItemsRequest.m; sourceTree = ""; }; + B5A764D21FE1915200B5DC9A /* BVBulkStoresResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVBulkStoresResponse.h; sourceTree = ""; }; + B5A764D31FE1915200B5DC9A /* BVStoreReviewsResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVStoreReviewsResponse.h; sourceTree = ""; }; + B5A764D41FE1915200B5DC9A /* BVStoreReviewsResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVStoreReviewsResponse.m; sourceTree = ""; }; + B5A764D51FE1915200B5DC9A /* BVBulkStoresResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVBulkStoresResponse.m; sourceTree = ""; }; + B5A764DA1FE1917C00B5DC9A /* BVStoreIncludeType.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVStoreIncludeType.m; sourceTree = ""; }; + B5A764DB1FE1917C00B5DC9A /* BVStoreIncludeType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVStoreIncludeType.h; sourceTree = ""; }; + B5A764E01FE191AE00B5DC9A /* BVUploadableStorePhoto.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVUploadableStorePhoto.h; sourceTree = ""; }; + B5A764E11FE191AE00B5DC9A /* BVUploadableStorePhoto.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVUploadableStorePhoto.m; sourceTree = ""; }; + B5A764E41FE191BA00B5DC9A /* BVStoreReviewSubmission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BVStoreReviewSubmission.h; path = BVSDK/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.h; sourceTree = SOURCE_ROOT; }; + B5A764E51FE191BA00B5DC9A /* BVStoreReviewSubmission.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BVStoreReviewSubmission.m; path = BVSDK/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.m; sourceTree = SOURCE_ROOT; }; B5A92D951FE85EEE008DA17C /* BVFeedbackSubmission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVFeedbackSubmission.h; sourceTree = ""; }; + B5BE7689200E97DB002BA818 /* BVBulkRatingFilterValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVBulkRatingFilterValue.h; sourceTree = ""; }; + B5BE768B200E9F34002BA818 /* BVCommentFilterValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVCommentFilterValue.h; sourceTree = ""; }; + B5BE768D200E9FD2002BA818 /* BVProductFilterValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVProductFilterValue.h; sourceTree = ""; }; + B5BE768F200EA04D002BA818 /* BVQuestionFilterValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVQuestionFilterValue.h; sourceTree = ""; }; + B5BE7691200EA0CA002BA818 /* BVRelationalFilterOperatorValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVRelationalFilterOperatorValue.h; sourceTree = ""; }; + B5BE7693200EA157002BA818 /* BVReviewFilterValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVReviewFilterValue.h; sourceTree = ""; }; + B5BE7695200EA2E5002BA818 /* BVAnswersSortOptionValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVAnswersSortOptionValue.h; sourceTree = ""; }; + B5BE7697200EA33C002BA818 /* BVAuthorIncludeTypeValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVAuthorIncludeTypeValue.h; sourceTree = ""; }; + B5BE7699200EA406002BA818 /* BVCommentIncludeTypeValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVCommentIncludeTypeValue.h; sourceTree = ""; }; + B5BE769B200EA473002BA818 /* BVCommentsSortOptionValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVCommentsSortOptionValue.h; sourceTree = ""; }; + B5BE769D200EA4DD002BA818 /* BVMonotonicSortOrderValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVMonotonicSortOrderValue.h; sourceTree = ""; }; + B5BE769F200EA53A002BA818 /* BVProductIncludeTypeValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVProductIncludeTypeValue.h; sourceTree = ""; }; + B5BE76A1200EA58A002BA818 /* BVProductsSortOptionValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVProductsSortOptionValue.h; sourceTree = ""; }; + B5BE76A3200EA5EC002BA818 /* BVQuestionsSortOptionValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVQuestionsSortOptionValue.h; sourceTree = ""; }; + B5BE76A5200EA666002BA818 /* BVReviewIncludeTypeValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVReviewIncludeTypeValue.h; sourceTree = ""; }; + B5BE76A7200EA6B4002BA818 /* BVReviewsSortOptionValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVReviewsSortOptionValue.h; sourceTree = ""; }; + B5BE76AA200EB1DE002BA818 /* BVBaseProductRequest_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVBaseProductRequest_Private.h; sourceTree = ""; }; + B5BE76AC200EB4F8002BA818 /* BVBaseReviewsRequest_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVBaseReviewsRequest_Private.h; sourceTree = ""; }; + B5BE76AE200EB70E002BA818 /* BVCommentsRequest_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVCommentsRequest_Private.h; sourceTree = ""; }; + B5BE7874200FB887002BA818 /* BVStoreIncludeTypeValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVStoreIncludeTypeValue.h; sourceTree = ""; }; + B5BE787620113462002BA818 /* BVBulkRatingIncludeTypeValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVBulkRatingIncludeTypeValue.h; sourceTree = ""; }; + B5BE787820113A32002BA818 /* BVBulkRatingIncludeType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BVBulkRatingIncludeType.h; sourceTree = ""; }; + B5BE787920113A32002BA818 /* BVBulkRatingIncludeType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BVBulkRatingIncludeType.m; sourceTree = ""; }; B5DDA04D1FE8633800C47C01 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; EC098EFAFB725736F0751BA0 /* libPods-BVSDK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-BVSDK.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -1071,8 +1162,7 @@ 870F13401E490B5200D46BE6 /* BVCurationsUICollectionViewCell.m */, 1544B36C1E37D4F7000B0427 /* SocialMediaIcons */, ); - name = BVCurationsUI; - path = Pod/BVCurationsUI; + path = BVCurationsUI; sourceTree = ""; }; 87252B0B1EF46FF40011C845 /* Video */ = { @@ -1095,8 +1185,7 @@ 875410AD1E1F10DF006C5C6E /* BVStoreReviewNotificationViewController.h */, 875410AE1E1F10DF006C5C6E /* BVStoreReviewNotificationViewController.m */, ); - name = BVExtensionNotifications; - path = Pod/BVExtensionNotifications; + path = BVExtensionNotifications; sourceTree = ""; }; 875411531E1F201E006C5C6E /* BVNotifications */ = { @@ -1134,8 +1223,7 @@ 87C5FE9B1E229168004EE6E8 /* BVProductReviewNotificationConfigurationLoader.h */, 87C5FE9C1E229168004EE6E8 /* BVProductReviewNotificationConfigurationLoader.m */, ); - name = BVNotifications; - path = Pod/BVNotifications; + path = BVNotifications; sourceTree = ""; }; 87A02B5D1E0963020002701B /* Feedback */ = { @@ -1173,16 +1261,18 @@ 87C5FEAE1E22A05D004EE6E8 /* BVSDKManager.m */, 87C5FEAF1E22A05D004EE6E8 /* BVStringKeyValuePair.h */, 87C5FEB01E22A05D004EE6E8 /* BVStringKeyValuePair.m */, + B59F8F1D2034C7B900087EF0 /* BVURLSessionDelegate.h */, 87C5FEB31E22A05D004EE6E8 /* Private */, ); - name = BVCommon; - path = Pod/BVCommon; + path = BVCommon; sourceTree = ""; }; 87C5FEB31E22A05D004EE6E8 /* Private */ = { isa = PBXGroup; children = ( 87C5FEB41E22A05D004EE6E8 /* BVMessageInterceptor.h */, + B59F8F19203490CA00087EF0 /* BVNetworkingManager.h */, + B59F8F1A203490CA00087EF0 /* BVNetworkingManager.m */, 15058B6D1E5CC21C0081E2BD /* BVSDKConfiguration.h */, ); path = Private; @@ -1259,39 +1349,33 @@ 87F2DAA91DAD579D00FB43F3 /* BVSDK */ = { isa = PBXGroup; children = ( - 87FEC8C81DB11448007DD44E /* Support Files */, + 87F2DAC41DAD585E00FB43F3 /* BVAnalytics */, 87C5FE9F1E22A05D004EE6E8 /* BVCommon */, B5A7649D1FDAEBB900B5DC9A /* BVCommonUI */, - 87F2DAC41DAD585E00FB43F3 /* BVAnalytics */, 87F2DAF61DAD585E00FB43F3 /* BVConversations */, B5A764BC1FE190A400B5DC9A /* BVConversationsStores */, B5A764A71FDB1A7500B5DC9A /* BVConversationsUI */, 87F2DBE01DAD585E00FB43F3 /* BVCurations */, 15970ECE1E366BB500807032 /* BVCurationsUI */, - 875411531E1F201E006C5C6E /* BVNotifications */, 875410A71E1F10DF006C5C6E /* BVExtensionNotifications */, + 875411531E1F201E006C5C6E /* BVNotifications */, 87F2DC021DAD585E00FB43F3 /* BVRecommendations */, + 87FEC8C81DB11448007DD44E /* Support */, ); - name = BVSDK; + path = BVSDK; sourceTree = ""; }; 87F2DAB41DAD579D00FB43F3 /* BVSDKTests */ = { isa = PBXGroup; children = ( + B59F8F2620360D9700087EF0 /* AnalyticsTests */, + B59F8F2720360DB500087EF0 /* CommonTests */, 87F2DDF21DAD945D00FB43F3 /* ConversationsTests */, + B59F8F2820360DD500087EF0 /* CurationsTests */, 87F2DDC61DAD698900FB43F3 /* MockData */, - 87F2DD541DAD5E9400FB43F3 /* BVBaseStubTestCase.h */, - 8779DB6C1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m */, - 87E28C821E5F88FE00915FDB /* BVPixelTests.m */, - 87F2DD551DAD5E9400FB43F3 /* BVBaseStubTestCase.m */, - 87F2DD571DAD5E9400FB43F3 /* BVCurationsTests.m */, - 87F2DD591DAD5E9400FB43F3 /* BVNotificationConfigTests.h */, - 87F2DD5A1DAD5E9400FB43F3 /* BVNotificationConfigTests.m */, - 87F2DD5B1DAD5E9400FB43F3 /* BVRecommendationsTests.m */, - 87F2DD5C1DAD5E9400FB43F3 /* BVUserProfileTests.m */, - 879D090F1DCA7DDE006B51D3 /* BVStoreNotificationConfigurationLoader+Private.h */, - 87F2DAB71DAD579D00FB43F3 /* Info.plist */, - 15D4F8451DF5EFFF00E6B30D /* BVSDKTests-Bridging-Header.h */, + B59F8F2520360D7100087EF0 /* NotificationTests */, + B59F8F2920360DE200087EF0 /* RecommendationsTests */, + B59F8F24203606E800087EF0 /* Support */, ); path = BVSDKTests; sourceTree = ""; @@ -1304,8 +1388,7 @@ 87F2DACC1DAD585E00FB43F3 /* BVPixel.m */, 87F2DAD11DAD585E00FB43F3 /* Private */, ); - name = BVAnalytics; - path = Pod/BVAnalytics; + path = BVAnalytics; sourceTree = ""; }; 87F2DAD11DAD585E00FB43F3 /* Private */ = { @@ -1314,6 +1397,8 @@ 87F2DAC51DAD585E00FB43F3 /* BVAnalyticsManager.h */, 87F2DAC61DAD585E00FB43F3 /* BVAnalyticsManager.m */, 87F2DAD21DAD585E00FB43F3 /* BVContextualInterests.h */, + B56D717C2031F79F002EC872 /* BVLocaleServiceManager.h */, + B56D717D2031F79F002EC872 /* BVLocaleServiceManager.m */, ); path = Private; sourceTree = ""; @@ -1324,26 +1409,12 @@ 87F2DB0A1DAD585E00FB43F3 /* Display */, 87F2DB881DAD585E00FB43F3 /* Submission */, ); - name = BVConversations; - path = Pod/BVConversations; + path = BVConversations; sourceTree = ""; }; 87F2DB0A1DAD585E00FB43F3 /* Display */ = { isa = PBXGroup; children = ( - 87F2DB0E1DAD585E00FB43F3 /* BVCommaUtil.h */, - 87F2DB0F1DAD585E00FB43F3 /* BVCommaUtil.m */, - 87C5FF641E393ED3004EE6E8 /* BVAuthorContentType.h */, - 87C5FF651E393ED3004EE6E8 /* BVAuthorContentType.m */, - 87C5FF681E394C73004EE6E8 /* BVAuthorInclude.h */, - 87C5FF691E394C73004EE6E8 /* BVAuthorInclude.m */, - 87E810EB1ECCC4CD0032C753 /* BVCommentIncludeType.h */, - 87E810EC1ECCC4CD0032C753 /* BVCommentIncludeType.m */, - 87D424E71E89C32E00147FDB /* BVReviewIncludeType.h */, - 87D424E81E89C32E00147FDB /* BVReviewIncludeType.m */, - 87F2DB5D1DAD585E00FB43F3 /* PDPContentType.h */, - 87F2DB5E1DAD585E00FB43F3 /* PDPContentType.m */, - 87F2DB601DAD585E00FB43F3 /* PDPInclude.m */, 87F2DB121DAD585E00FB43F3 /* Model */, 87F2DB611DAD585E00FB43F3 /* Requests */, 87F2DB711DAD585E00FB43F3 /* Sorting & Filtering */, @@ -1470,7 +1541,7 @@ 87F2DB691DAD585E00FB43F3 /* BVQuestionsAndAnswersRequest.m */, 87F2DB6A1DAD585E00FB43F3 /* BVReviewsRequest.h */, 87F2DB6B1DAD585E00FB43F3 /* BVReviewsRequest.m */, - 87F2DB5F1DAD585E00FB43F3 /* PDPInclude.h */, + B5BE76A9200EB19D002BA818 /* Private */, ); path = Requests; sourceTree = ""; @@ -1478,32 +1549,30 @@ 87F2DB711DAD585E00FB43F3 /* Sorting & Filtering */ = { isa = PBXGroup; children = ( - 87F2DB721DAD585E00FB43F3 /* BVBulkRatingsFilterType.h */, - 87F2DB731DAD585E00FB43F3 /* BVBulkRatingsFilterType.m */, - 87E810E71ECCA3CF0032C753 /* BVCommentFilterType.h */, - 87E810E81ECCA3CF0032C753 /* BVCommentFilterType.m */, - 87F2DB741DAD585E00FB43F3 /* BVFilter.h */, - 87F2DB751DAD585E00FB43F3 /* BVFilter.m */, - 87F2DB761DAD585E00FB43F3 /* BVFilterOperator.h */, - 87F2DB771DAD585E00FB43F3 /* BVFilterOperator.m */, - 87F2DB781DAD585E00FB43F3 /* BVProductFilterType.h */, - 87F2DB791DAD585E00FB43F3 /* BVProductFilterType.m */, - 87F2DB7A1DAD585E00FB43F3 /* BVQuestionFilterType.h */, - 87F2DB7B1DAD585E00FB43F3 /* BVQuestionFilterType.m */, - 87F2DB7C1DAD585E00FB43F3 /* BVReviewFilterType.h */, - 87F2DB7D1DAD585E00FB43F3 /* BVReviewFilterType.m */, - 87F2DB7E1DAD585E00FB43F3 /* BVSort.h */, - 87F2DB7F1DAD585E00FB43F3 /* BVSort.m */, - 87F2DB801DAD585E00FB43F3 /* BVSortOptionAnswers.h */, - 87F2DB811DAD585E00FB43F3 /* BVSortOptionAnswers.m */, - 87E810E31ECCA0190032C753 /* BVSortOptionsComments.h */, - 87E810E41ECCA0190032C753 /* BVSortOptionsComments.m */, - 87F2DB821DAD585E00FB43F3 /* BVSortOptionProducts.h */, - 87F2DB831DAD585E00FB43F3 /* BVSortOptionProducts.m */, - 87F2DB841DAD585E00FB43F3 /* BVSortOptionQuestions.h */, - 87F2DB851DAD585E00FB43F3 /* BVSortOptionQuestions.m */, - 87F2DB861DAD585E00FB43F3 /* BVSortOptionReviews.h */, - 87F2DB871DAD585E00FB43F3 /* BVSortOptionReviews.m */, + B5BE7695200EA2E5002BA818 /* BVAnswersSortOptionValue.h */, + B5BE7697200EA33C002BA818 /* BVAuthorIncludeTypeValue.h */, + B5BE7689200E97DB002BA818 /* BVBulkRatingFilterValue.h */, + B5BE787620113462002BA818 /* BVBulkRatingIncludeTypeValue.h */, + B5BE768B200E9F34002BA818 /* BVCommentFilterValue.h */, + B5BE7699200EA406002BA818 /* BVCommentIncludeTypeValue.h */, + B5BE769B200EA473002BA818 /* BVCommentsSortOptionValue.h */, + B583B11F200E8ABD001E9548 /* BVConversationDisplay.h */, + B583B123200E8ABE001E9548 /* BVFilterOperatorValues.h */, + B583B122200E8ABD001E9548 /* BVFilterTypeValues.h */, + B583B120200E8ABD001E9548 /* BVIncludeTypeValues.h */, + B5BE769D200EA4DD002BA818 /* BVMonotonicSortOrderValue.h */, + B5BE768D200E9FD2002BA818 /* BVProductFilterValue.h */, + B5BE769F200EA53A002BA818 /* BVProductIncludeTypeValue.h */, + B5BE76A1200EA58A002BA818 /* BVProductsSortOptionValue.h */, + B5BE768F200EA04D002BA818 /* BVQuestionFilterValue.h */, + B5BE76A3200EA5EC002BA818 /* BVQuestionsSortOptionValue.h */, + B5BE7691200EA0CA002BA818 /* BVRelationalFilterOperatorValue.h */, + B5BE7693200EA157002BA818 /* BVReviewFilterValue.h */, + B5BE76A5200EA666002BA818 /* BVReviewIncludeTypeValue.h */, + B5BE76A7200EA6B4002BA818 /* BVReviewsSortOptionValue.h */, + B583B124200E8ABE001E9548 /* BVSortOptionValues.h */, + B583B121200E8ABD001E9548 /* BVSortOrderValues.h */, + B583B11E200E888C001E9548 /* Private */, ); path = "Sorting & Filtering"; sourceTree = ""; @@ -1686,8 +1755,7 @@ 87F2DBF01DAD585E00FB43F3 /* BVCurationsPhotoUploader.h */, 87F2DBF11DAD585E00FB43F3 /* BVCurationsPhotoUploader.m */, ); - name = BVCurations; - path = Pod/BVCurations; + path = BVCurations; sourceTree = ""; }; 87F2DC021DAD585E00FB43F3 /* BVRecommendations */ = { @@ -1710,17 +1778,16 @@ 87F2DC111DAD585E00FB43F3 /* BVRecsAnalyticsHelper.m */, 87F2DC121DAD585E00FB43F3 /* BVShopperProfile.h */, 87F2DC131DAD585E00FB43F3 /* BVShopperProfile.m */, - 87F2DC141DAD585E00FB43F3 /* BVShopperProfileRequestCache.m */, 87F2DC151DAD585E00FB43F3 /* Private */, ); - name = BVRecommendations; - path = Pod/BVRecommendations; + path = BVRecommendations; sourceTree = ""; }; 87F2DC151DAD585E00FB43F3 /* Private */ = { isa = PBXGroup; children = ( 87F2DC161DAD585E00FB43F3 /* BVShopperProfileRequestCache.h */, + 87F2DC141DAD585E00FB43F3 /* BVShopperProfileRequestCache.m */, ); path = Private; sourceTree = ""; @@ -1742,8 +1809,8 @@ 87F2DD651DAD698400FB43F3 /* conversations */ = { isa = PBXGroup; children = ( - 15D4F83A1DF5D5F800E6B30D /* testNotificationProductConfig.json */, 879D09101DCA8014006B51D3 /* testNotificationConfig.json */, + 15D4F83A1DF5D5F800E6B30D /* testNotificationProductConfig.json */, 87F2DD661DAD698400FB43F3 /* conversationsGenericPostResponse.json */, 87F2DD671DAD698400FB43F3 /* conversationsQuestionsIncludeAnswers.json */, 87F2DD681DAD698400FB43F3 /* conversationsReviewsEnduranceCycles.json */, @@ -1771,9 +1838,8 @@ 87F2DD861DAD698500FB43F3 /* testShowStorySparse.json */, 8777BB441EBA4F26008C4715 /* testSyndicationSource.json */, ); - name = conversations; - path = Tests/Tests/resources/conversations; - sourceTree = SOURCE_ROOT; + path = conversations; + sourceTree = ""; }; 87F2DD691DAD698400FB43F3 /* sortingReviews */ = { isa = PBXGroup; @@ -1801,7 +1867,6 @@ isa = PBXGroup; children = ( 87F2DD881DAD698500FB43F3 /* curations500Error.json */, - 87F2DD891DAD698500FB43F3 /* curationsEnduranceCycles.json */, 87F2DD8A1DAD698500FB43F3 /* curationsFeedTest1.json */, 87F2DD8B1DAD698500FB43F3 /* post_ErrorParsingBody.json */, 87F2DD8C1DAD698500FB43F3 /* post_MissingRequiredKey.json */, @@ -1810,7 +1875,7 @@ 87F2DD8F1DAD698500FB43F3 /* test_pattern.jpg */, ); name = curations; - path = Tests/Tests/resources/curations; + path = BVSDKTests/MockData/curations; sourceTree = SOURCE_ROOT; }; 87F2DD921DAD698500FB43F3 /* recommendations */ = { @@ -1822,9 +1887,8 @@ 87F2DD961DAD698500FB43F3 /* recommendationsResult.json */, 87F2DD971DAD698500FB43F3 /* userProfile1.json */, ); - name = recommendations; - path = Tests/Tests/resources/recommendations; - sourceTree = SOURCE_ROOT; + path = recommendations; + sourceTree = ""; }; 87F2DDC61DAD698900FB43F3 /* MockData */ = { isa = PBXGroup; @@ -1832,11 +1896,11 @@ 87F2DD651DAD698400FB43F3 /* conversations */, 87F2DD871DAD698500FB43F3 /* curations */, 87F2DD901DAD698500FB43F3 /* emptyJSON.json */, - 87A9ECB71E1DA87E00666636 /* productsToReviewResult.json */, - 87F2DD911DAD698500FB43F3 /* malformedJSON.json */, + 87A9ECB71E1DA87E00666636 /* malformedJSON.json */, + 87F2DD911DAD698500FB43F3 /* productsToReviewResult.json */, 87F2DD921DAD698500FB43F3 /* recommendations */, ); - name = MockData; + path = MockData; sourceTree = ""; }; 87F2DDF21DAD945D00FB43F3 /* ConversationsTests */ = { @@ -1847,20 +1911,19 @@ 87F2DE001DAD945D00FB43F3 /* ParsingTests */, 87F2DE021DAD945D00FB43F3 /* SubmissionTests */, ); - name = ConversationsTests; - path = Tests/Tests/ConversationsTests; - sourceTree = SOURCE_ROOT; + path = ConversationsTests; + sourceTree = ""; }; 87F2DDF31DAD945D00FB43F3 /* DisplayTests */ = { isa = PBXGroup; children = ( + 87B6DF9B1ECA3A0F00B75835 /* CommentsDisplayTests.swift */, 87F2DDF41DAD945D00FB43F3 /* ConversationsDisplayTests.swift */, 87F2DDF51DAD945D00FB43F3 /* InlineRatingsDisplayTests.swift */, 87F2DDF61DAD945D00FB43F3 /* ProductDisplayTests.swift */, 87C5FF5E1E36AB70004EE6E8 /* ProfileDisplayTests.swift */, 87F2DDF71DAD945D00FB43F3 /* QuestionDisplayTests.swift */, 87F2DDF81DAD945D00FB43F3 /* ReviewDisplayTests.swift */, - 87B6DF9B1ECA3A0F00B75835 /* CommentsDisplayTests.swift */, 87F2DDF91DAD945D00FB43F3 /* StoresTests */, ); path = DisplayTests; @@ -1910,14 +1973,14 @@ path = SubmissionTests; sourceTree = ""; }; - 87FEC8C81DB11448007DD44E /* Support Files */ = { + 87FEC8C81DB11448007DD44E /* Support */ = { isa = PBXGroup; children = ( 87F2DAAA1DAD579D00FB43F3 /* BVSDK.h */, 87F2DAAB1DAD579D00FB43F3 /* Info.plist */, 87FEC8C91DB1145D007DD44E /* integrate-dynamic-framework.sh */, ); - path = "Support Files"; + path = Support; sourceTree = ""; }; 9CDB496D31BBD7782FF5DC99 /* Pods */ = { @@ -1944,6 +2007,122 @@ path = Auth; sourceTree = ""; }; + B583B11E200E888C001E9548 /* Private */ = { + isa = PBXGroup; + children = ( + 87F2DB801DAD585E00FB43F3 /* BVAnswersSortOption.h */, + 87F2DB811DAD585E00FB43F3 /* BVAnswersSortOption.m */, + B583AEEF200956D0001E9548 /* BVAuthorIncludeType.h */, + B583AEF0200956D0001E9548 /* BVAuthorIncludeType.m */, + 87F2DB721DAD585E00FB43F3 /* BVBulkRatingFilterType.h */, + 87F2DB731DAD585E00FB43F3 /* BVBulkRatingFilterType.m */, + B5BE787820113A32002BA818 /* BVBulkRatingIncludeType.h */, + B5BE787920113A32002BA818 /* BVBulkRatingIncludeType.m */, + 87F2DB0E1DAD585E00FB43F3 /* BVCommaUtil.h */, + 87F2DB0F1DAD585E00FB43F3 /* BVCommaUtil.m */, + 87E810E71ECCA3CF0032C753 /* BVCommentFilterType.h */, + 87E810E81ECCA3CF0032C753 /* BVCommentFilterType.m */, + 87E810EB1ECCC4CD0032C753 /* BVCommentIncludeType.h */, + 87E810EC1ECCC4CD0032C753 /* BVCommentIncludeType.m */, + 87E810E31ECCA0190032C753 /* BVCommentsSortOption.h */, + 87E810E41ECCA0190032C753 /* BVCommentsSortOption.m */, + 87F2DB741DAD585E00FB43F3 /* BVFilter.h */, + 87F2DB751DAD585E00FB43F3 /* BVFilter.m */, + 87F2DB761DAD585E00FB43F3 /* BVFilterOperator.h */, + 87F2DB771DAD585E00FB43F3 /* BVFilterOperator.m */, + B583AED32006C18F001E9548 /* BVFilterType.h */, + B583AED42006C18F001E9548 /* BVFilterType.m */, + B583AEE3200940FB001E9548 /* BVInclude.h */, + B583AEE4200940FB001E9548 /* BVInclude.m */, + B583AEE72009486C001E9548 /* BVIncludeType.h */, + B583AEE82009486C001E9548 /* BVIncludeType.m */, + B583AEDB2007FB59001E9548 /* BVMonotonicSortOrder.h */, + B583AEDC2007FB59001E9548 /* BVMonotonicSortOrder.m */, + 87F2DB781DAD585E00FB43F3 /* BVProductFilterType.h */, + 87F2DB791DAD585E00FB43F3 /* BVProductFilterType.m */, + B583AEEB20094A58001E9548 /* BVProductIncludeType.h */, + B583AEEC20094A58001E9548 /* BVProductIncludeType.m */, + 87F2DB821DAD585E00FB43F3 /* BVProductsSortOption.h */, + 87F2DB831DAD585E00FB43F3 /* BVProductsSortOption.m */, + 87F2DB7A1DAD585E00FB43F3 /* BVQuestionFilterType.h */, + 87F2DB7B1DAD585E00FB43F3 /* BVQuestionFilterType.m */, + 87F2DB841DAD585E00FB43F3 /* BVQuestionsSortOption.h */, + 87F2DB851DAD585E00FB43F3 /* BVQuestionsSortOption.m */, + B583AECF2006B4AA001E9548 /* BVRelationalFilterOperator.h */, + B583AED02006B4AA001E9548 /* BVRelationalFilterOperator.m */, + 87F2DB7C1DAD585E00FB43F3 /* BVReviewFilterType.h */, + 87F2DB7D1DAD585E00FB43F3 /* BVReviewFilterType.m */, + 87D424E71E89C32E00147FDB /* BVReviewIncludeType.h */, + 87D424E81E89C32E00147FDB /* BVReviewIncludeType.m */, + 87F2DB861DAD585E00FB43F3 /* BVReviewsSortOption.h */, + 87F2DB871DAD585E00FB43F3 /* BVReviewsSortOption.m */, + 87F2DB7E1DAD585E00FB43F3 /* BVSort.h */, + 87F2DB7F1DAD585E00FB43F3 /* BVSort.m */, + B583AEDF200816A4001E9548 /* BVSortOption.h */, + B583AEE0200816A4001E9548 /* BVSortOption.m */, + B583AED72007F80A001E9548 /* BVSortOrder.h */, + B583AED82007F80A001E9548 /* BVSortOrder.m */, + ); + path = Private; + sourceTree = ""; + }; + B59F8F24203606E800087EF0 /* Support */ = { + isa = PBXGroup; + children = ( + 15D4F8451DF5EFFF00E6B30D /* BVSDKTests-Bridging-Header.h */, + 87F2DAB71DAD579D00FB43F3 /* Info.plist */, + ); + path = Support; + sourceTree = ""; + }; + B59F8F2520360D7100087EF0 /* NotificationTests */ = { + isa = PBXGroup; + children = ( + 87F2DD591DAD5E9400FB43F3 /* BVNotificationConfigTests.h */, + 87F2DD5A1DAD5E9400FB43F3 /* BVNotificationConfigTests.m */, + 879D090F1DCA7DDE006B51D3 /* BVStoreNotificationConfigurationLoader+Private.h */, + ); + path = NotificationTests; + sourceTree = ""; + }; + B59F8F2620360D9700087EF0 /* AnalyticsTests */ = { + isa = PBXGroup; + children = ( + 8779DB6C1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m */, + 87E28C821E5F88FE00915FDB /* BVPixelTests.m */, + ); + path = AnalyticsTests; + sourceTree = ""; + }; + B59F8F2720360DB500087EF0 /* CommonTests */ = { + isa = PBXGroup; + children = ( + 87F2DD541DAD5E9400FB43F3 /* BVBaseStubTestCase.h */, + 87F2DD551DAD5E9400FB43F3 /* BVBaseStubTestCase.m */, + B56D71812032476A002EC872 /* BVLocaleManagerTests.m */, + B59F8F212035EF2900087EF0 /* BVNetworkDelegateTestsDelegate.h */, + B59F8F222035EF2900087EF0 /* BVNetworkDelegateTestsDelegate.m */, + 87F2DD5C1DAD5E9400FB43F3 /* BVUserProfileTests.m */, + ); + path = CommonTests; + sourceTree = ""; + }; + B59F8F2820360DD500087EF0 /* CurationsTests */ = { + isa = PBXGroup; + children = ( + 87F2DD571DAD5E9400FB43F3 /* BVCurationsTests.m */, + ); + path = CurationsTests; + sourceTree = ""; + }; + B59F8F2920360DE200087EF0 /* RecommendationsTests */ = { + isa = PBXGroup; + children = ( + 87F2DD5B1DAD5E9400FB43F3 /* BVRecommendationsTests.m */, + ); + path = RecommendationsTests; + sourceTree = ""; + }; B5A7649D1FDAEBB900B5DC9A /* BVCommonUI */ = { isa = PBXGroup; children = ( @@ -1952,8 +2131,7 @@ B5A764A31FDAFB9C00B5DC9A /* UIImage+BundleLocator.m */, B5A7649E1FDAEBCB00B5DC9A /* Private */, ); - name = BVCommonUI; - path = Pod/BVCommonUI; + path = BVCommonUI; sourceTree = ""; }; B5A7649E1FDAEBCB00B5DC9A /* Private */ = { @@ -1969,8 +2147,7 @@ children = ( 87F2DBBB1DAD585E00FB43F3 /* Views */, ); - name = BVConversationsUI; - path = Pod/BVConversationsUI; + path = BVConversationsUI; sourceTree = ""; }; B5A764BC1FE190A400B5DC9A /* BVConversationsStores */ = { @@ -1979,16 +2156,15 @@ B5A764BD1FE190DA00B5DC9A /* Display */, B5A764BE1FE190E000B5DC9A /* Submission */, ); - name = BVConversationsStores; - path = Pod/BVConversationsStores; + path = BVConversationsStores; sourceTree = ""; }; B5A764BD1FE190DA00B5DC9A /* Display */ = { isa = PBXGroup; children = ( - B5A764DB1FE1917C00B5DC9A /* BVStoreIncludeContentType.h */, - B5A764DA1FE1917C00B5DC9A /* BVStoreIncludeContentType.m */, B5A764BF1FE190F400B5DC9A /* Model */, + B5A764C01FE1910100B5DC9A /* Requests */, + B5BE7872200FB79A002BA818 /* Sorting & Filtering */, ); path = Display; sourceTree = ""; @@ -2010,7 +2186,6 @@ B5A764D31FE1915200B5DC9A /* BVStoreReviewsResponse.h */, B5A764D41FE1915200B5DC9A /* BVStoreReviewsResponse.m */, B5A764C11FE1911A00B5DC9A /* GenericConversationsResult */, - B5A764C01FE1910100B5DC9A /* Requests */, ); path = Model; sourceTree = ""; @@ -2055,6 +2230,34 @@ path = Review; sourceTree = ""; }; + B5BE76A9200EB19D002BA818 /* Private */ = { + isa = PBXGroup; + children = ( + B5BE76AA200EB1DE002BA818 /* BVBaseProductRequest_Private.h */, + B5BE76AC200EB4F8002BA818 /* BVBaseReviewsRequest_Private.h */, + B5BE76AE200EB70E002BA818 /* BVCommentsRequest_Private.h */, + ); + path = Private; + sourceTree = ""; + }; + B5BE7872200FB79A002BA818 /* Sorting & Filtering */ = { + isa = PBXGroup; + children = ( + B5BE7874200FB887002BA818 /* BVStoreIncludeTypeValue.h */, + B5BE7873200FB7B4002BA818 /* Private */, + ); + path = "Sorting & Filtering"; + sourceTree = ""; + }; + B5BE7873200FB7B4002BA818 /* Private */ = { + isa = PBXGroup; + children = ( + B5A764DB1FE1917C00B5DC9A /* BVStoreIncludeType.h */, + B5A764DA1FE1917C00B5DC9A /* BVStoreIncludeType.m */, + ); + path = Private; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2066,14 +2269,21 @@ 87D424F31E8AD45400147FDB /* BVCurationsUICollectionView.h in Headers */, 87F2DC181DAD585E00FB43F3 /* BVAnalyticsManager.h in Headers */, 153550B01E898CF800C62D90 /* BVCurationsUICollectionViewCell.h in Headers */, + B5BE7690200EA08C002BA818 /* BVQuestionFilterValue.h in Headers */, 87F2DD431DAD585E00FB43F3 /* BVRecsAnalyticsHelper.h in Headers */, - 87F2DCC31DAD585E00FB43F3 /* BVSortOptionAnswers.h in Headers */, + B583AEED20094A58001E9548 /* BVProductIncludeType.h in Headers */, + B5BE76A4200EA5EC002BA818 /* BVQuestionsSortOptionValue.h in Headers */, + 87F2DCC31DAD585E00FB43F3 /* BVAnswersSortOption.h in Headers */, 87F2DC641DAD585E00FB43F3 /* BVContextDataValue.h in Headers */, 87F2DD221DAD585E00FB43F3 /* BVCurationsFeedLoader.h in Headers */, 87F2DD181DAD585E00FB43F3 /* BVCurationsAddPostRequest.h in Headers */, 87F2DCBF1DAD585E00FB43F3 /* BVReviewFilterType.h in Headers */, + B5BE7696200EA31D002BA818 /* BVAnswersSortOptionValue.h in Headers */, + B5BE769A200EA409002BA818 /* BVCommentIncludeTypeValue.h in Headers */, + B5BE7694200EA179002BA818 /* BVReviewFilterValue.h in Headers */, 87F2DCE51DAD585E00FB43F3 /* BVQuestionSubmission.h in Headers */, 87F2DD0F1DAD585E00FB43F3 /* BVReviewsCollectionView.h in Headers */, + B583B127200E8ABE001E9548 /* BVSortOrderValues.h in Headers */, 87F2DCCB1DAD585E00FB43F3 /* BVAnswerSubmission.h in Headers */, 87F2DCD91DAD585E00FB43F3 /* BVSubmission.h in Headers */, 5FE2C0811F6B05100090DD55 /* BVErrorCode.h in Headers */, @@ -2084,25 +2294,32 @@ 87F2DCD11DAD585E00FB43F3 /* BVSubmittedAnswer.h in Headers */, 87F2DCEB1DAD585E00FB43F3 /* BVSubmittedQuestion.h in Headers */, 87F2DC7A1DAD585E00FB43F3 /* BVPhotoSizes.h in Headers */, + B583B126200E8ABE001E9548 /* BVIncludeTypeValues.h in Headers */, 87F2DCF11DAD585E00FB43F3 /* BVReviewSubmissionResponse.h in Headers */, 87F2DD071DAD585E00FB43F3 /* BVQuestionsTableView.h in Headers */, 87F2DD3D1DAD585E00FB43F3 /* BVRecommendationsLoader.h in Headers */, 87F2DD0D1DAD585E00FB43F3 /* BVReviewCollectionViewCell.h in Headers */, 87F2DD3C1DAD585E00FB43F3 /* BVRecommendations.h in Headers */, + B583AEE1200816A4001E9548 /* BVSortOption.h in Headers */, + B583AEDD2007FB59001E9548 /* BVMonotonicSortOrder.h in Headers */, 87F2DC751DAD585E00FB43F3 /* BVGenericConversationsResult.h in Headers */, 87F2DCD71DAD585E00FB43F3 /* BVFormFieldOptions.h in Headers */, 87F2DC861DAD585E00FB43F3 /* BVResponse.h in Headers */, 87F2DCCD1DAD585E00FB43F3 /* BVAnswerSubmissionErrorResponse.h in Headers */, + B56D717E2031F79F002EC872 /* BVLocaleServiceManager.h in Headers */, 87F2DC6A1DAD585E00FB43F3 /* BVConversationsInclude.h in Headers */, - 87F2DCC71DAD585E00FB43F3 /* BVSortOptionQuestions.h in Headers */, + 87F2DCC71DAD585E00FB43F3 /* BVQuestionsSortOption.h in Headers */, 87F2DC581DAD585E00FB43F3 /* BVCommaUtil.h in Headers */, 87F2DD151DAD585E00FB43F3 /* BVReviewView.h in Headers */, + B5BE76A6200EA666002BA818 /* BVReviewIncludeTypeValue.h in Headers */, 87F2DD171DAD585E00FB43F3 /* BVCurations.h in Headers */, 87F2DD261DAD585E00FB43F3 /* BVCurationsPhotoUploader.h in Headers */, + B583B125200E8ABE001E9548 /* BVConversationDisplay.h in Headers */, 87F2DC621DAD585E00FB43F3 /* BVBulkRatingsResponse.h in Headers */, 87F2DCAF1DAD585E00FB43F3 /* BVReviewsRequest.h in Headers */, 87F2DC731DAD585E00FB43F3 /* BVDistributionValue.h in Headers */, 87F2DC681DAD585E00FB43F3 /* BVConversationsErrorResponse.h in Headers */, + B59F8F1B203490CA00087EF0 /* BVNetworkingManager.h in Headers */, 87F2DD3F1DAD585E00FB43F3 /* BVRecommendationsRequest.h in Headers */, 87F2DD3A1DAD585E00FB43F3 /* BVProductReview.h in Headers */, 87F2DC661DAD585E00FB43F3 /* BVConversationsError.h in Headers */, @@ -2112,6 +2329,7 @@ 87F2DC991DAD585E00FB43F3 /* BVReview.h in Headers */, 87F2DCCF1DAD585E00FB43F3 /* BVAnswerSubmissionResponse.h in Headers */, 87F2DAB81DAD579D00FB43F3 /* BVSDK.h in Headers */, + B5BE768A200E984B002BA818 /* BVBulkRatingFilterValue.h in Headers */, 87F2DC971DAD585E00FB43F3 /* BVQuestion.h in Headers */, 87F2DCB71DAD585E00FB43F3 /* BVFilter.h in Headers */, 8779DB271DD20D8D00E6CAF5 /* BVStoreReviewsTableView.h in Headers */, @@ -2119,8 +2337,10 @@ 87F2DCE71DAD585E00FB43F3 /* BVQuestionSubmissionErrorResponse.h in Headers */, 158CF5871E71F4CD000EA063 /* BVBulkProductRequest.h in Headers */, 87F2DD131DAD585E00FB43F3 /* BVReviewTableViewCell.h in Headers */, - 87F2DCB51DAD585E00FB43F3 /* BVBulkRatingsFilterType.h in Headers */, + 87F2DCB51DAD585E00FB43F3 /* BVBulkRatingFilterType.h in Headers */, 87F2DC931DAD585E00FB43F3 /* BVAnswer.h in Headers */, + B5BE768E200EA025002BA818 /* BVProductFilterValue.h in Headers */, + B583B12A200E8ABE001E9548 /* BVSortOptionValues.h in Headers */, 87F2DCAD1DAD585E00FB43F3 /* BVQuestionsAndAnswersRequest.h in Headers */, 87F2DCBD1DAD585E00FB43F3 /* BVQuestionFilterType.h in Headers */, 87F2DCE11DAD585E00FB43F3 /* BVUploadablePhoto.h in Headers */, @@ -2136,21 +2356,24 @@ 87F2DCDD1DAD585E00FB43F3 /* BVSubmissionErrorResponse.h in Headers */, 87F2DC8D1DAD585E00FB43F3 /* BVSecondaryRating.h in Headers */, 87F2DCF31DAD585E00FB43F3 /* BVSubmittedReview.h in Headers */, - 87F2DCC91DAD585E00FB43F3 /* BVSortOptionReviews.h in Headers */, + 87F2DCC91DAD585E00FB43F3 /* BVReviewsSortOption.h in Headers */, 87F2DC5C1DAD585E00FB43F3 /* BVBadge.h in Headers */, 87F2DCAB1DAD585E00FB43F3 /* BVProductDisplayPageRequest.h in Headers */, + B59F8F1E2034C7B900087EF0 /* BVURLSessionDelegate.h in Headers */, 87F2DC821DAD585E00FB43F3 /* BVQuestionsAndAnswersResponse.h in Headers */, 87F2DC6C1DAD585E00FB43F3 /* BVDimensionAndDistributionUtil.h in Headers */, + B583AED92007F80A001E9548 /* BVSortOrder.h in Headers */, 87F2DC761DAD585E00FB43F3 /* BVModelUtil.h in Headers */, 87F2DCED1DAD585E00FB43F3 /* BVReviewSubmission.h in Headers */, - 87F2DCA31DAD585E00FB43F3 /* PDPContentType.h in Headers */, 87F2DCC11DAD585E00FB43F3 /* BVSort.h in Headers */, + B5BE76AB200EB1DE002BA818 /* BVBaseProductRequest_Private.h in Headers */, 87F2DCD31DAD585E00FB43F3 /* BVFieldError.h in Headers */, 87F2DCA71DAD585E00FB43F3 /* BVBulkRatingsRequest.h in Headers */, 87F2DD0B1DAD585E00FB43F3 /* BVQuestionView.h in Headers */, 87F2DC781DAD585E00FB43F3 /* BVPhoto.h in Headers */, 87F2DD031DAD585E00FB43F3 /* BVQuestionCollectionViewCell.h in Headers */, 87F2DCA91DAD585E00FB43F3 /* BVConversationsRequest.h in Headers */, + B5BE76A2200EA58A002BA818 /* BVProductsSortOptionValue.h in Headers */, 87F2DC7E1DAD585E00FB43F3 /* BVProductStatistics.h in Headers */, 150FC2CA1E71C58B00717041 /* BVBaseProductRequest.h in Headers */, 87F2DC8B1DAD585E00FB43F3 /* BVReviewStatistics.h in Headers */, @@ -2160,6 +2383,8 @@ 87F2DC7C1DAD585E00FB43F3 /* BVProductsResponse.h in Headers */, 87F2DC891DAD585E00FB43F3 /* BVReviewStatistic.h in Headers */, 87F2DD011DAD585E00FB43F3 /* BVProductPageViews.h in Headers */, + B5BE76AF200EB70E002BA818 /* BVCommentsRequest_Private.h in Headers */, + B5BE787A20113A32002BA818 /* BVBulkRatingIncludeType.h in Headers */, 87F2DD381DAD585E00FB43F3 /* BVProductRecommendationView.h in Headers */, 87F2DCEF1DAD585E00FB43F3 /* BVReviewSubmissionErrorResponse.h in Headers */, 87F2DCDB1DAD585E00FB43F3 /* BVSubmissionAction.h in Headers */, @@ -2168,6 +2393,7 @@ 8754117D1E1F201E006C5C6E /* BVProductReviewRichNotificationCenter.h in Headers */, 875411791E1F201E006C5C6E /* BVProductReviewNotificationCenter.h in Headers */, 875410B41E1F10DF006C5C6E /* BVStoreReviewNotificationViewController.h in Headers */, + B583B128200E8ABE001E9548 /* BVFilterTypeValues.h in Headers */, 875410B01E1F10DF006C5C6E /* BVNotificationViewController.h in Headers */, 875411851E1F201E006C5C6E /* BVStoreReviewRichNotificationCenter.h in Headers */, 875411731E1F201E006C5C6E /* BVNotificationProperties.h in Headers */, @@ -2175,11 +2401,14 @@ 875410AF1E1F10DF006C5C6E /* BVNotifications.h in Headers */, 8754117B1E1F201E006C5C6E /* BVProductReviewNotificationProperties.h in Headers */, 8754117F1E1F201E006C5C6E /* BVProductReviewSimpleNotificationCenter.h in Headers */, + B5BE76AD200EB4F8002BA818 /* BVBaseReviewsRequest_Private.h in Headers */, + B5BE7698200EA355002BA818 /* BVAuthorIncludeTypeValue.h in Headers */, 875411871E1F201E006C5C6E /* BVStoreReviewSimpleNotificationCenter.h in Headers */, 875411811E1F201E006C5C6E /* BVStoreReviewNotificationCenter.h in Headers */, 875411831E1F201E006C5C6E /* BVStoreReviewNotificationProperties.h in Headers */, 8754116F1E1F201E006C5C6E /* BVNotificationCenterObject.h in Headers */, 875411751E1F201E006C5C6E /* BVNotificationsAnalyticsHelper.h in Headers */, + B5BE7875200FB887002BA818 /* BVStoreIncludeTypeValue.h in Headers */, 875411721E1F201E006C5C6E /* BVNotificationConstants.h in Headers */, 875411771E1F201E006C5C6E /* BVOpenURLMetaData.h in Headers */, 875411701E1F201E006C5C6E /* BVNotificationConfiguration.h in Headers */, @@ -2196,31 +2425,35 @@ 87C5FEB61E22A05D004EE6E8 /* BVAuthenticatedUser.h in Headers */, 87C5FEC11E22A05D004EE6E8 /* BVNullHelper.h in Headers */, 87C5FEBB1E22A05D004EE6E8 /* BVDiagnosticHelpers.h in Headers */, - 87F2DCA51DAD585E00FB43F3 /* PDPInclude.h in Headers */, 87F2DC601DAD585E00FB43F3 /* BVBrand.h in Headers */, 87F2DC841DAD585E00FB43F3 /* BVRatingDistribution.h in Headers */, 87F2DC5E1DAD585E00FB43F3 /* BVBadgeType.h in Headers */, 1539745C1E719B9600BA52C1 /* BVBaseConversationsResponse.h in Headers */, + B583AEE5200940FB001E9548 /* BVInclude.h in Headers */, 87A02B6A1E0971AC0002701B /* BVSubmittedFeedback.h in Headers */, 873DC4951E7482840080FA03 /* BVImpressionEvent.h in Headers */, 873DC4991E7482840080FA03 /* BVPageViewEvent.h in Headers */, + B5BE769E200EA4FD002BA818 /* BVMonotonicSortOrderValue.h in Headers */, 873DC48E1E7482840080FA03 /* BVAnalyticEvent.h in Headers */, + B5BE787720113462002BA818 /* BVBulkRatingIncludeTypeValue.h in Headers */, 873DC48F1E7482840080FA03 /* BVAnalyticEventManager.h in Headers */, 873DC4A11E7482840080FA03 /* BVTransactionItem.h in Headers */, 873DC4911E7482840080FA03 /* BVConversionEvent.h in Headers */, 873DC4971E7482840080FA03 /* BVInViewEvent.h in Headers */, 873DC49B1E7482840080FA03 /* BVPersonalizationEvent.h in Headers */, + B5BE76A0200EA53D002BA818 /* BVProductIncludeTypeValue.h in Headers */, + B5BE769C200EA473002BA818 /* BVCommentsSortOptionValue.h in Headers */, 873DC4931E7482840080FA03 /* BVFeatureUsedEvent.h in Headers */, 873DC49F1E7482840080FA03 /* BVTransactionEvent.h in Headers */, 873DC49D1E7482840080FA03 /* BVPixelTypes.h in Headers */, 87A02B661E096B300002701B /* BVFeedbackSubmissionResponse.h in Headers */, + B5BE7692200EA0E2002BA818 /* BVRelationalFilterOperatorValue.h in Headers */, 87C5FF581E368DF2004EE6E8 /* BVAuthorRequest.h in Headers */, 87C5FF5C1E36A8B7004EE6E8 /* BVAuthorResponse.h in Headers */, - 87F2DCC51DAD585E00FB43F3 /* BVSortOptionProducts.h in Headers */, + 87F2DCC51DAD585E00FB43F3 /* BVProductsSortOption.h in Headers */, B5A764A11FDAFB7B00B5DC9A /* BVViewsHelper.h in Headers */, - 87C5FF661E393ED3004EE6E8 /* BVAuthorContentType.h in Headers */, - 87C5FF6A1E394C73004EE6E8 /* BVAuthorInclude.h in Headers */, 15398EEA1E69F61B00A6685B /* BVDisplayableProductContent.h in Headers */, + B583AED52006C18F001E9548 /* BVFilterType.h in Headers */, 150FC2C61E71C21700717041 /* BVBulkProductResponse.h in Headers */, 87C5FF621E36B416004EE6E8 /* BVAuthor.h in Headers */, 87D424E91E89C32E00147FDB /* BVReviewIncludeType.h in Headers */, @@ -2228,11 +2461,12 @@ 8777BB421EBA3823008C4715 /* BVSyndicationSource.h in Headers */, 87B6DFA11ECA424300B75835 /* BVCommentsRequest.h in Headers */, 87B6DFA91ECA49B600B75835 /* BVCommentsResponse.h in Headers */, + B5BE76A8200EA6B4002BA818 /* BVReviewsSortOptionValue.h in Headers */, 879A628B1E80502100F46ECA /* BVBasePIIEvent.h in Headers */, 87B6DFA51ECA484400B75835 /* BVComment.h in Headers */, 87E810E91ECCA3CF0032C753 /* BVCommentFilterType.h in Headers */, 87E810ED1ECCC4CD0032C753 /* BVCommentIncludeType.h in Headers */, - 87E810E51ECCA0190032C753 /* BVSortOptionsComments.h in Headers */, + 87E810E51ECCA0190032C753 /* BVCommentsSortOption.h in Headers */, 87EDAFD11EDDD02F00FA07C0 /* BVCommentSubmissionResponse.h in Headers */, 87EDAFD51EDDD1FD00FA07C0 /* BVSubmittedComment.h in Headers */, 87EDAFD91EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.h in Headers */, @@ -2242,14 +2476,17 @@ 5F6201E11F70399200454D77 /* NSError+BVSubmissionErrorCodeParser.h in Headers */, B575C3861FBDFD6F000F890B /* BVUASSubmission.h in Headers */, B575C3921FBE2479000F890B /* BVSubmittedUAS.h in Headers */, + B5BE768C200E9FA0002BA818 /* BVCommentFilterValue.h in Headers */, + B583AEF1200956D0001E9548 /* BVAuthorIncludeType.h in Headers */, B5A764B31FDB1ACE00B5DC9A /* BVAnswerView.h in Headers */, B5A764B41FDB1ACE00B5DC9A /* BVAnswersTableView.h in Headers */, B5A764C61FE1912800B5DC9A /* BVStore.h in Headers */, B5A764D71FE1915200B5DC9A /* BVStoreReviewsResponse.h in Headers */, B5A764C71FE1912800B5DC9A /* BVStoreLocation.h in Headers */, B5A764E21FE191AE00B5DC9A /* BVUploadableStorePhoto.h in Headers */, + B583AEE92009486C001E9548 /* BVIncludeType.h in Headers */, B5A764CF1FE1913800B5DC9A /* BVStoreReviewsRequest.h in Headers */, - B5A764DD1FE1917C00B5DC9A /* BVStoreIncludeContentType.h in Headers */, + B5A764DD1FE1917C00B5DC9A /* BVStoreIncludeType.h in Headers */, B5A92D961FE85EEE008DA17C /* BVFeedbackSubmission.h in Headers */, B5A764E61FE191BA00B5DC9A /* BVStoreReviewSubmission.h in Headers */, B5A764D01FE1913800B5DC9A /* BVBulkStoreItemsRequest.h in Headers */, @@ -2260,6 +2497,7 @@ B575C38A1FBE02FE000F890B /* BVUASSubmissionErrorResponse.h in Headers */, B575C38E1FBE1A4F000F890B /* BVUASSubmissionResponse.h in Headers */, 5F51EA501F5F2BB4002FA8FD /* BVFormInputType.h in Headers */, + B583B129200E8ABE001E9548 /* BVFilterOperatorValues.h in Headers */, 5F6201DC1F7030FA00454D77 /* NSError+BVErrorCodeParser.h in Headers */, 5F608FF41F6AF3E100E197CE /* BVSubmissionErrorCode.h in Headers */, B5A764A61FDAFBA700B5DC9A /* UIImage+BundleLocator.h in Headers */, @@ -2267,6 +2505,7 @@ 87C5FEC91E22A05D004EE6E8 /* BVMessageInterceptor.h in Headers */, 87F2DD201DAD585E00FB43F3 /* BVCurationsFeedItem.h in Headers */, 15058B6E1E5CC21C0081E2BD /* BVSDKConfiguration.h in Headers */, + B583AED12006B4AA001E9548 /* BVRelationalFilterOperator.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2330,6 +2569,7 @@ }; 87F2DAAF1DAD579D00FB43F3 = { CreatedOnToolsVersion = 8.0; + DevelopmentTeam = TH9FFAVND4; LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; @@ -2379,7 +2619,6 @@ buildActionMask = 2147483647; files = ( 87F2DDAE1DAD698500FB43F3 /* testShowQuestions.json in Resources */, - 87F2DDB81DAD698500FB43F3 /* curationsEnduranceCycles.json in Resources */, 87F2DDA51DAD698500FB43F3 /* testShowComments.json in Resources */, 87F2DDB31DAD698500FB43F3 /* testShowReviewSparse.json in Resources */, 87F2DDB41DAD698500FB43F3 /* testShowStatistics.json in Resources */, @@ -2401,7 +2640,7 @@ 87F2DDBA1DAD698500FB43F3 /* post_ErrorParsingBody.json in Resources */, 87F2DDAC1DAD698500FB43F3 /* testShowProfileSparse.json in Resources */, 87F2DD9A1DAD698500FB43F3 /* conversationsReviewsEnduranceCycles.json in Resources */, - 87F2DDC01DAD698500FB43F3 /* malformedJSON.json in Resources */, + 87F2DDC01DAD698500FB43F3 /* productsToReviewResult.json in Resources */, 87F2DDBC1DAD698500FB43F3 /* post_successfulCreation.json in Resources */, 87F2DDBE1DAD698500FB43F3 /* test_pattern.jpg in Resources */, 879D09111DCA8014006B51D3 /* testNotificationConfig.json in Resources */, @@ -2424,7 +2663,7 @@ 87F2DDC31DAD698500FB43F3 /* recommendationsNullJSON.json in Resources */, 87F2DDA91DAD698500FB43F3 /* testShowProducts.json in Resources */, 15D4F83F1DF5EC3C00E6B30D /* ph.png in Resources */, - 87A9ECB81E1DA87E00666636 /* productsToReviewResult.json in Resources */, + 87A9ECB81E1DA87E00666636 /* malformedJSON.json in Resources */, 87F2DDBD1DAD698500FB43F3 /* puppy.jpg in Resources */, 87F2DDA01DAD698500FB43F3 /* storeFetchByIds.json in Resources */, 87F2DDA11DAD698500FB43F3 /* storeItemWithStatsAndReviews.json in Resources */, @@ -2542,7 +2781,6 @@ 87F2DCEE1DAD585E00FB43F3 /* BVReviewSubmission.m in Sources */, 87F2DC611DAD585E00FB43F3 /* BVBrand.m in Sources */, 87F2DC8C1DAD585E00FB43F3 /* BVReviewStatistics.m in Sources */, - 87C5FF6B1E394C73004EE6E8 /* BVAuthorInclude.m in Sources */, 150FC2D31E71DEAE00717041 /* BVProductTextSearchRequest.m in Sources */, 5F51EA4D1F575659002FA8FD /* BVFormInputType.m in Sources */, B5A764A41FDAFB9C00B5DC9A /* UIImage+BundleLocator.m in Sources */, @@ -2552,7 +2790,7 @@ 87F2DD3E1DAD585E00FB43F3 /* BVRecommendationsLoader.m in Sources */, 87F2DD471DAD585E00FB43F3 /* BVShopperProfileRequestCache.m in Sources */, 87F2DCAC1DAD585E00FB43F3 /* BVProductDisplayPageRequest.m in Sources */, - B5A764DC1FE1917C00B5DC9A /* BVStoreIncludeContentType.m in Sources */, + B5A764DC1FE1917C00B5DC9A /* BVStoreIncludeType.m in Sources */, 87F2DC961DAD585E00FB43F3 /* BVProduct.m in Sources */, 87EDAFDA1EDDD3E200FA07C0 /* BVCommentSubmissionErrorResponse.m in Sources */, 87F2DC881DAD585E00FB43F3 /* BVReviewsResponse.m in Sources */, @@ -2564,10 +2802,12 @@ 875411861E1F201E006C5C6E /* BVStoreReviewRichNotificationCenter.m in Sources */, B5A764C81FE1912800B5DC9A /* BVStoreLocation.m in Sources */, 87F2DD371DAD585E00FB43F3 /* BVProductRecommendationsContainer.m in Sources */, + B583AEEA2009486C001E9548 /* BVIncludeType.m in Sources */, 158CF5881E71F4CD000EA063 /* BVBulkProductRequest.m in Sources */, 87F2DC981DAD585E00FB43F3 /* BVQuestion.m in Sources */, 87F2DD0A1DAD585E00FB43F3 /* BVQuestionTableViewCell.m in Sources */, 875411801E1F201E006C5C6E /* BVProductReviewSimpleNotificationCenter.m in Sources */, + B583AEDE2007FB59001E9548 /* BVMonotonicSortOrder.m in Sources */, 87F2DD231DAD585E00FB43F3 /* BVCurationsFeedLoader.m in Sources */, B5A764C91FE1912800B5DC9A /* BVStore.m in Sources */, 873DC4A21E7482840080FA03 /* BVTransactionItem.m in Sources */, @@ -2578,13 +2818,14 @@ 873DC4961E7482840080FA03 /* BVImpressionEvent.m in Sources */, 87F2DCF21DAD585E00FB43F3 /* BVReviewSubmissionResponse.m in Sources */, 87F2DCDA1DAD585E00FB43F3 /* BVSubmission.m in Sources */, + B56D717F2031F79F002EC872 /* BVLocaleServiceManager.m in Sources */, 87F2DCCC1DAD585E00FB43F3 /* BVAnswerSubmission.m in Sources */, + B583AED22006B4AA001E9548 /* BVRelationalFilterOperator.m in Sources */, B5A764D91FE1915200B5DC9A /* BVBulkStoresResponse.m in Sources */, 875411741E1F201E006C5C6E /* BVNotificationProperties.m in Sources */, 875411711E1F201E006C5C6E /* BVNotificationConfiguration.m in Sources */, 87F2DD0E1DAD585E00FB43F3 /* BVReviewCollectionViewCell.m in Sources */, 87F2DC591DAD585E00FB43F3 /* BVCommaUtil.m in Sources */, - 87F2DCA61DAD585E00FB43F3 /* PDPInclude.m in Sources */, 87F2DC811DAD585E00FB43F3 /* BVQAStatistics.m in Sources */, B5A764E71FE191BA00B5DC9A /* BVStoreReviewSubmission.m in Sources */, 87C5FEBF1E22A05D004EE6E8 /* BVLogger.m in Sources */, @@ -2594,6 +2835,7 @@ 8754117C1E1F201E006C5C6E /* BVProductReviewNotificationProperties.m in Sources */, 875411881E1F201E006C5C6E /* BVStoreReviewSimpleNotificationCenter.m in Sources */, 87F2DC651DAD585E00FB43F3 /* BVContextDataValue.m in Sources */, + B59F8F1C203490CA00087EF0 /* BVNetworkingManager.m in Sources */, 87F2DD391DAD585E00FB43F3 /* BVProductRecommendationView.m in Sources */, 87F2DCD01DAD585E00FB43F3 /* BVAnswerSubmissionResponse.m in Sources */, 87F2DCC21DAD585E00FB43F3 /* BVSort.m in Sources */, @@ -2601,10 +2843,11 @@ B575C38F1FBE1A4F000F890B /* BVUASSubmissionResponse.m in Sources */, 87F2DCD81DAD585E00FB43F3 /* BVFormFieldOptions.m in Sources */, 87F2DCAA1DAD585E00FB43F3 /* BVConversationsRequest.m in Sources */, - 87F2DCC81DAD585E00FB43F3 /* BVSortOptionQuestions.m in Sources */, + 87F2DCC81DAD585E00FB43F3 /* BVQuestionsSortOption.m in Sources */, 87C5FEBC1E22A05D004EE6E8 /* BVDiagnosticHelpers.m in Sources */, 87F2DD3B1DAD585E00FB43F3 /* BVProductReview.m in Sources */, 8777BB431EBA3823008C4715 /* BVSyndicationSource.m in Sources */, + B583AEEE20094A58001E9548 /* BVProductIncludeType.m in Sources */, 87C5FF591E368DF2004EE6E8 /* BVAuthorRequest.m in Sources */, 87F2DCF01DAD585E00FB43F3 /* BVReviewSubmissionErrorResponse.m in Sources */, 87F2DD251DAD585E00FB43F3 /* BVCurationsFeedRequest.m in Sources */, @@ -2639,14 +2882,14 @@ 87B6DFAA1ECA49B600B75835 /* BVCommentsResponse.m in Sources */, 873DC49E1E7482840080FA03 /* BVPixelTypes.m in Sources */, 875411821E1F201E006C5C6E /* BVStoreReviewNotificationCenter.m in Sources */, - 87F2DCB61DAD585E00FB43F3 /* BVBulkRatingsFilterType.m in Sources */, + 87F2DCB61DAD585E00FB43F3 /* BVBulkRatingFilterType.m in Sources */, 87F2DC6F1DAD585E00FB43F3 /* BVDimensionElement.m in Sources */, 879A628C1E80502100F46ECA /* BVBasePIIEvent.m in Sources */, 87F2DCDE1DAD585E00FB43F3 /* BVSubmissionErrorResponse.m in Sources */, 87F2DC921DAD585E00FB43F3 /* BVVideo.m in Sources */, 153974481E6F151500BA52C1 /* BVBaseReviewsRequest.m in Sources */, B575C3871FBDFD6F000F890B /* BVUASSubmission.m in Sources */, - 87E810E61ECCA0190032C753 /* BVSortOptionsComments.m in Sources */, + 87E810E61ECCA0190032C753 /* BVCommentsSortOption.m in Sources */, 875411761E1F201E006C5C6E /* BVNotificationsAnalyticsHelper.m in Sources */, 87D425281E8EE39700147FDB /* BVViewedCGCEvent.m in Sources */, 87F2DD061DAD585E00FB43F3 /* BVQuestionsCollectionView.m in Sources */, @@ -2659,6 +2902,7 @@ 87F2DC721DAD585E00FB43F3 /* BVDistributionElement.m in Sources */, 87C5FEC61E22A05D004EE6E8 /* BVStringKeyValuePair.m in Sources */, 87F2DC7D1DAD585E00FB43F3 /* BVProductsResponse.m in Sources */, + B583AEF2200956D0001E9548 /* BVAuthorIncludeType.m in Sources */, 875411841E1F201E006C5C6E /* BVStoreReviewNotificationProperties.m in Sources */, 87F2DCBE1DAD585E00FB43F3 /* BVQuestionFilterType.m in Sources */, 87A02B6B1E0971AC0002701B /* BVSubmittedFeedback.m in Sources */, @@ -2675,11 +2919,13 @@ 87C5FE9A1E22914C004EE6E8 /* BVStoreNotificationConfigurationLoader.m in Sources */, 87F2DC6B1DAD585E00FB43F3 /* BVConversationsInclude.m in Sources */, 87F2DCAE1DAD585E00FB43F3 /* BVQuestionsAndAnswersRequest.m in Sources */, + B583AED62006C18F001E9548 /* BVFilterType.m in Sources */, 87F2DCD41DAD585E00FB43F3 /* BVFieldError.m in Sources */, 87F2DC941DAD585E00FB43F3 /* BVAnswer.m in Sources */, 875410B31E1F10DF006C5C6E /* BVProductReviewNotificationViewController.m in Sources */, 87F2DC851DAD585E00FB43F3 /* BVRatingDistribution.m in Sources */, 87F2DC6D1DAD585E00FB43F3 /* BVDimensionAndDistributionUtil.m in Sources */, + B5BE787B20113A32002BA818 /* BVBulkRatingIncludeType.m in Sources */, 87F2DD441DAD585E00FB43F3 /* BVRecsAnalyticsHelper.m in Sources */, 8754117A1E1F201E006C5C6E /* BVProductReviewNotificationCenter.m in Sources */, 87EDAFD21EDDD02F00FA07C0 /* BVCommentSubmissionResponse.m in Sources */, @@ -2697,23 +2943,24 @@ 87F2DCB81DAD585E00FB43F3 /* BVFilter.m in Sources */, 87F2DC7F1DAD585E00FB43F3 /* BVProductStatistics.m in Sources */, 87F2DC771DAD585E00FB43F3 /* BVModelUtil.m in Sources */, + B583AEE2200816A4001E9548 /* BVSortOption.m in Sources */, 87F2DCBA1DAD585E00FB43F3 /* BVFilterOperator.m in Sources */, 87F2DC5D1DAD585E00FB43F3 /* BVBadge.m in Sources */, + B583AEE6200940FB001E9548 /* BVInclude.m in Sources */, B575C3931FBE2479000F890B /* BVSubmittedUAS.m in Sources */, 87C5FEB71E22A05D004EE6E8 /* BVAuthenticatedUser.m in Sources */, 87F2DD081DAD585E00FB43F3 /* BVQuestionsTableView.m in Sources */, - 87F2DCCA1DAD585E00FB43F3 /* BVSortOptionReviews.m in Sources */, - 87F2DCA41DAD585E00FB43F3 /* PDPContentType.m in Sources */, + 87F2DCCA1DAD585E00FB43F3 /* BVReviewsSortOption.m in Sources */, 87F2DC671DAD585E00FB43F3 /* BVConversationsError.m in Sources */, 87F2DCA81DAD585E00FB43F3 /* BVBulkRatingsRequest.m in Sources */, 873DC49C1E7482840080FA03 /* BVPersonalizationEvent.m in Sources */, 87C5FE9E1E229168004EE6E8 /* BVProductReviewNotificationConfigurationLoader.m in Sources */, - 87C5FF671E393ED3004EE6E8 /* BVAuthorContentType.m in Sources */, 87F2DC691DAD585E00FB43F3 /* BVConversationsErrorResponse.m in Sources */, B5A764D11FE1913800B5DC9A /* BVBulkStoreItemsRequest.m in Sources */, - 87F2DCC41DAD585E00FB43F3 /* BVSortOptionAnswers.m in Sources */, + 87F2DCC41DAD585E00FB43F3 /* BVAnswersSortOption.m in Sources */, 87F2DC631DAD585E00FB43F3 /* BVBulkRatingsResponse.m in Sources */, - 87F2DCC61DAD585E00FB43F3 /* BVSortOptionProducts.m in Sources */, + B583AEDA2007F80A001E9548 /* BVSortOrder.m in Sources */, + 87F2DCC61DAD585E00FB43F3 /* BVProductsSortOption.m in Sources */, 87F2DD161DAD585E00FB43F3 /* BVReviewView.m in Sources */, 87B6DFA21ECA424300B75835 /* BVCommentsRequest.m in Sources */, 87B6DFA61ECA484400B75835 /* BVComment.m in Sources */, @@ -2740,10 +2987,12 @@ 87F2DE111DAD945D00FB43F3 /* ProductIdEscapeTests.swift in Sources */, 87F2DD5E1DAD5E9400FB43F3 /* BVBaseStubTestCase.m in Sources */, 87F2DE0D1DAD945D00FB43F3 /* ConversationsStoresDisplayTests.swift in Sources */, + B59F8F232035EF2900087EF0 /* BVNetworkDelegateTestsDelegate.m in Sources */, 87EDAFC91EDDC6A900FA07C0 /* CommentSubmissionTests.swift in Sources */, 87F2DE0B1DAD945D00FB43F3 /* QuestionDisplayTests.swift in Sources */, - 8779DB6D1DD3AAE800E6CAF5 /* BVInternalAnalyticsTest.m in Sources */, + B56D71822032476A002EC872 /* BVLocaleManagerTests.m in Sources */, 87F2DE101DAD945D00FB43F3 /* FilterTests.swift in Sources */, + B59185962040DADC006BE717 /* BVInternalAnalyticsTest.m in Sources */, 87F2DE0A1DAD945D00FB43F3 /* ProductDisplayTests.swift in Sources */, 87F2DE131DAD945D00FB43F3 /* AnswerSubmissionTests.swift in Sources */, 87F2DE0E1DAD945D00FB43F3 /* ConversationsTests.swift in Sources */, @@ -2903,7 +3152,7 @@ ); GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_PREFIX_HEADER = ""; - INFOPLIST_FILE = "Support Files/Info.plist"; + INFOPLIST_FILE = BVSDK/Support/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -2930,7 +3179,7 @@ ); GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_PREFIX_HEADER = ""; - INFOPLIST_FILE = "Support Files/Info.plist"; + INFOPLIST_FILE = BVSDK/Support/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.4; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -2945,11 +3194,12 @@ isa = XCBuildConfiguration; baseConfigurationReference = 3462CE8D50CC0B93A458E62C /* Pods-BVSDKTests.debug.xcconfig */; buildSettings = { - INFOPLIST_FILE = BVSDKTests/Info.plist; + DEVELOPMENT_TEAM = TH9FFAVND4; + INFOPLIST_FILE = BVSDKTests/Support/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks ${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap"; PRODUCT_BUNDLE_IDENTIFIER = com.bazaarvoice.bvsdk.BVSDKTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "./BVSDKTests/BVSDKTests-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "./BVSDKTests/Support/BVSDKTests-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 3.0; }; @@ -2959,11 +3209,12 @@ isa = XCBuildConfiguration; baseConfigurationReference = 89529D79D81B13BA86882542 /* Pods-BVSDKTests.release.xcconfig */; buildSettings = { - INFOPLIST_FILE = BVSDKTests/Info.plist; + DEVELOPMENT_TEAM = TH9FFAVND4; + INFOPLIST_FILE = BVSDKTests/Support/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks ${BUILT_PRODUCTS_DIR}/CommonCryptoModuleMap"; PRODUCT_BUNDLE_IDENTIFIER = com.bazaarvoice.bvsdk.BVSDKTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "./BVSDKTests/BVSDKTests-Bridging-Header.h"; + SWIFT_OBJC_BRIDGING_HEADER = "./BVSDKTests/Support/BVSDKTests-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 3.0; }; diff --git a/Pod/BVAnalytics/BVPixel.h b/BVSDK/BVAnalytics/BVPixel.h similarity index 100% rename from Pod/BVAnalytics/BVPixel.h rename to BVSDK/BVAnalytics/BVPixel.h diff --git a/Pod/BVAnalytics/BVPixel.m b/BVSDK/BVAnalytics/BVPixel.m similarity index 100% rename from Pod/BVAnalytics/BVPixel.m rename to BVSDK/BVAnalytics/BVPixel.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVAnalyticEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVAnalyticEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVAnalyticEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVAnalyticEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.h b/BVSDK/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.m b/BVSDK/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.m similarity index 97% rename from Pod/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.m index 5fabc061..16df6f45 100644 --- a/Pod/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.m +++ b/BVSDK/BVAnalytics/BVPixelEvents/BVAnalyticEventManager.m @@ -21,7 +21,7 @@ @interface BVAnalyticEventManager () @implementation BVAnalyticEventManager -static BVAnalyticEventManager *mgrInstance = nil; +__strong static BVAnalyticEventManager *mgrInstance = nil; + (BVAnalyticEventManager *)sharedManager { static dispatch_once_t onceToken; diff --git a/Pod/BVAnalytics/BVPixelEvents/BVBasePIIEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVBasePIIEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVBasePIIEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVBasePIIEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVBasePIIEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVBasePIIEvent.m similarity index 97% rename from Pod/BVAnalytics/BVPixelEvents/BVBasePIIEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVBasePIIEvent.m index fc95b720..68b6f8be 100644 --- a/Pod/BVAnalytics/BVPixelEvents/BVBasePIIEvent.m +++ b/BVSDK/BVAnalytics/BVPixelEvents/BVBasePIIEvent.m @@ -7,7 +7,7 @@ #import "BVBasePIIEvent.h" -static NSSet *whitelistParams; +__strong static NSSet *whitelistParams; @implementation BVBasePIIEvent diff --git a/Pod/BVAnalytics/BVPixelEvents/BVConversionEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVConversionEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVConversionEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVConversionEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVConversionEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVConversionEvent.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVConversionEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVConversionEvent.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVFeatureUsedEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVFeatureUsedEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVFeatureUsedEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVFeatureUsedEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVFeatureUsedEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVFeatureUsedEvent.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVFeatureUsedEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVFeatureUsedEvent.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVImpressionEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVImpressionEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVImpressionEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVImpressionEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVImpressionEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVImpressionEvent.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVImpressionEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVImpressionEvent.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVInViewEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVInViewEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVInViewEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVInViewEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVInViewEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVInViewEvent.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVInViewEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVInViewEvent.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPageViewEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVPageViewEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVPageViewEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVPageViewEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPageViewEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVPageViewEvent.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVPageViewEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVPageViewEvent.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPersonalizationEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVPersonalizationEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVPersonalizationEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVPersonalizationEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPersonalizationEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVPersonalizationEvent.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVPersonalizationEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVPersonalizationEvent.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPixelEvents.h b/BVSDK/BVAnalytics/BVPixelEvents/BVPixelEvents.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVPixelEvents.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVPixelEvents.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.h b/BVSDK/BVAnalytics/BVPixelEvents/BVPixelTypes.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVPixelTypes.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.m b/BVSDK/BVAnalytics/BVPixelEvents/BVPixelTypes.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVPixelTypes.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVPixelTypes.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVTransactionEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVTransactionEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVTransactionEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVTransactionEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVTransactionEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVTransactionEvent.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVTransactionEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVTransactionEvent.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVTransactionItem.h b/BVSDK/BVAnalytics/BVPixelEvents/BVTransactionItem.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVTransactionItem.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVTransactionItem.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVTransactionItem.m b/BVSDK/BVAnalytics/BVPixelEvents/BVTransactionItem.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVTransactionItem.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVTransactionItem.m diff --git a/Pod/BVAnalytics/BVPixelEvents/BVViewedCGCEvent.h b/BVSDK/BVAnalytics/BVPixelEvents/BVViewedCGCEvent.h similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVViewedCGCEvent.h rename to BVSDK/BVAnalytics/BVPixelEvents/BVViewedCGCEvent.h diff --git a/Pod/BVAnalytics/BVPixelEvents/BVViewedCGCEvent.m b/BVSDK/BVAnalytics/BVPixelEvents/BVViewedCGCEvent.m similarity index 100% rename from Pod/BVAnalytics/BVPixelEvents/BVViewedCGCEvent.m rename to BVSDK/BVAnalytics/BVPixelEvents/BVViewedCGCEvent.m diff --git a/Pod/BVAnalytics/BVAnalyticsManager.h b/BVSDK/BVAnalytics/Private/BVAnalyticsManager.h similarity index 92% rename from Pod/BVAnalytics/BVAnalyticsManager.h rename to BVSDK/BVAnalytics/Private/BVAnalyticsManager.h index abb55675..ccf827f4 100644 --- a/Pod/BVAnalytics/BVAnalyticsManager.h +++ b/BVSDK/BVAnalytics/Private/BVAnalyticsManager.h @@ -23,6 +23,10 @@ /// NO @property(nonatomic, assign) BOOL isDryRunAnalytics; +/// This can be optionally set by the configuration, otherwise, we just use the +/// current. +@property(nonatomic, strong) NSLocale *analyticsLocale; + /// Create and get the singleton instance of the analytics manager. + (BVAnalyticsManager *)sharedManager; diff --git a/Pod/BVAnalytics/BVAnalyticsManager.m b/BVSDK/BVAnalytics/Private/BVAnalyticsManager.m similarity index 77% rename from Pod/BVAnalytics/BVAnalyticsManager.m rename to BVSDK/BVAnalytics/Private/BVAnalyticsManager.m index a9589047..f50ec70d 100644 --- a/Pod/BVAnalytics/BVAnalyticsManager.m +++ b/BVSDK/BVAnalytics/Private/BVAnalyticsManager.m @@ -10,16 +10,17 @@ #import "BVAnalyticEventManager.h" #import "BVAnalyticsManager.h" +#import "BVLocaleServiceManager.h" #import "BVLogger.h" +#import "BVNetworkingManager.h" #import "BVPersonalizationEvent.h" +#import "BVSDKConfiguration.h" #import "BVSDKConstants.h" +#import "BVSDKManager.h" #include #include -#define BV_MAGPIE_ENDPOINT @"https://network.bazaarvoice.com/event" -#define BV_MAGPIE_STAGING_ENDPOINT @"https://network-stg.bazaarvoice.com/event" - @interface BVAnalyticsManager () @property(strong) @@ -28,20 +29,24 @@ @interface BVAnalyticsManager () @property NSTimer *queueFlushTimer; @property NSTimeInterval queueFlushInterval; +@property(nonatomic, strong) + dispatch_queue_t localeUpdateNotificationTokenQueue; +@property(strong) id localeUpdateNotificationCenterToken; + @property(nonatomic, strong) dispatch_queue_t concurrentEventQueue; @end @implementation BVAnalyticsManager +@synthesize analyticsLocale = _analyticsLocale; + static BVAnalyticsManager *analyticsInstance = nil; + (BVAnalyticsManager *)sharedManager { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ analyticsInstance = [[self alloc] init]; - analyticsInstance->_concurrentEventQueue = dispatch_queue_create( - "com.bazaarvoice.analyticEventQueue", DISPATCH_QUEUE_CONCURRENT); }); return analyticsInstance; @@ -52,6 +57,11 @@ - (id)init { if (self != nil) { self.eventQueue = [NSMutableArray array]; self.pageviewQueue = [NSMutableArray array]; + self.concurrentEventQueue = dispatch_queue_create( + "com.bazaarvoice.analyticEventQueue", DISPATCH_QUEUE_CONCURRENT); + self.localeUpdateNotificationTokenQueue = dispatch_queue_create( + "com.bazaarvoice.notificationTokenQueue", DISPATCH_QUEUE_SERIAL); + [self setFlushInterval:10.0]; [self registerForAppStateChanges]; } @@ -353,9 +363,18 @@ - (void)sendBatchedPOSTEvent:(NSDictionary *)eventData return; } - NSURLSessionConfiguration *config = - [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; + /// For private classes we ask for the NSURLSession but we don't hand back any + /// objects since it would be useless to the developers as they have no + /// interface to the object graph. + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:nil]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; request.HTTPMethod = @"POST"; @@ -413,6 +432,86 @@ - (void)sendBatchedPOSTEvent:(NSDictionary *)eventData }; } +#pragma mark - NSLocale Analytic Handling + +- (NSLocale *)analyticsLocale { + __block NSLocale *locale = nil; + dispatch_sync(self.localeUpdateNotificationTokenQueue, ^{ + locale = _analyticsLocale; + }); + return locale; +} + +- (void)setAnalyticsLocale:(NSLocale *)analyticsLocale { + dispatch_sync(self.localeUpdateNotificationTokenQueue, ^{ + _analyticsLocale = analyticsLocale; + + if (_analyticsLocale) { + /// Turn off locale state changes + [self unregisterForCurrentLocaleDidChangeNotifications]; + } else { + _analyticsLocale = [NSLocale autoupdatingCurrentLocale]; + /// Turn on locale state changes + [self registerForCurrentLocaleDidChangeNotifications]; + } + + [self logAnalyticsLocaleUnsafe]; + }); +} + +- (void)logAnalyticsLocale { + dispatch_sync(self.localeUpdateNotificationTokenQueue, ^{ + [self logAnalyticsLocaleUnsafe]; + }); +} + +- (void)logAnalyticsLocaleUnsafe { + NSString *logLocale = nil; + if (_analyticsLocale) { + logLocale = + ((NSString *)[_analyticsLocale objectForKey:NSLocaleCountryCode]) + .uppercaseString; + } + + [[BVLogger sharedLogger] + analyticsMessage:[NSString + stringWithFormat:@"Configuration has set Locale: %@", + logLocale]]; +} + +- (void)registerForCurrentLocaleDidChangeNotifications { + if (!self.localeUpdateNotificationCenterToken) { + [[BVLogger sharedLogger] + analyticsMessage: + @"Analytics REGISTERING for Locale Change Notifications."]; + + self.localeUpdateNotificationCenterToken = + [[NSNotificationCenter defaultCenter] + addObserverForName:NSCurrentLocaleDidChangeNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *note) { + dispatch_sync(self.localeUpdateNotificationTokenQueue, ^{ + _analyticsLocale = [NSLocale autoupdatingCurrentLocale]; + [self logAnalyticsLocaleUnsafe]; + }); + }]; + } +} + +- (void)unregisterForCurrentLocaleDidChangeNotifications { + + [[BVLogger sharedLogger] + analyticsMessage: + @"Analytics UNREGISTERING for Locale Change Notifications."]; + + if (self.localeUpdateNotificationCenterToken) { + [[NSNotificationCenter defaultCenter] + removeObserver:self.localeUpdateNotificationCenterToken]; + self.localeUpdateNotificationCenterToken = nil; + } +} + #pragma mark - Helpers - (NSString *)formatDate:(NSDate *)date { @@ -427,14 +526,20 @@ - (NSString *)formatDate:(NSDate *)date { } - (NSString *)baseUrl { + BVLocaleServiceManager *localeServiceManager = + [BVLocaleServiceManager sharedManager]; + NSAssert(localeServiceManager, @"BVLocaleServiceManager is nil."); + if (self.isStagingServer) { - [[BVLogger sharedLogger] error:@"WARNING: Using staging server for " - @"analytic events. This should only " - @"enabled for non-production."]; - return BV_MAGPIE_STAGING_ENDPOINT; - } else { - return BV_MAGPIE_ENDPOINT; + [[BVLogger sharedLogger] + error:@"WARNING: Using staging server for analytic events. This should " + @"only be enabled for non-production."]; } + + return [localeServiceManager + resourceForService:BVLocaleServiceManagerServiceAnalytics + withLocale:self.analyticsLocale + andIsProduction:(!self.isStagingServer)]; } - (void)setFlushInterval:(NSTimeInterval)newInterval { diff --git a/Pod/BVAnalytics/Private/BVContextualInterests.h b/BVSDK/BVAnalytics/Private/BVContextualInterests.h similarity index 100% rename from Pod/BVAnalytics/Private/BVContextualInterests.h rename to BVSDK/BVAnalytics/Private/BVContextualInterests.h diff --git a/BVSDK/BVAnalytics/Private/BVLocaleServiceManager.h b/BVSDK/BVAnalytics/Private/BVLocaleServiceManager.h new file mode 100644 index 00000000..b7e525a7 --- /dev/null +++ b/BVSDK/BVAnalytics/Private/BVLocaleServiceManager.h @@ -0,0 +1,25 @@ +// +// BVLocaleServiceManager.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +/// The types of services that require Locale specific actions. +typedef NS_ENUM(NSUInteger, BVLocaleServiceManagerService) { + BVLocaleServiceManagerServiceAnalytics +}; + +@interface BVLocaleServiceManager : NSObject + +/// Create and get the singleton instance of the region manager. ++ (nonnull instancetype)sharedManager; + +/// Acquire the base URL for service given a region object +- (nonnull NSString *)resourceForService:(BVLocaleServiceManagerService)service + withLocale:(nonnull NSLocale *)locale + andIsProduction:(BOOL)isProduction; + +@end diff --git a/BVSDK/BVAnalytics/Private/BVLocaleServiceManager.m b/BVSDK/BVAnalytics/Private/BVLocaleServiceManager.m new file mode 100644 index 00000000..e02c6d35 --- /dev/null +++ b/BVSDK/BVAnalytics/Private/BVLocaleServiceManager.m @@ -0,0 +1,233 @@ +// +// BVLocaleServiceManager.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVLocaleServiceManager.h" +#import "BVLogger.h" + +#define __ISA(X, CLASS) ([(X) isKindOfClass:[CLASS class]]) + +/// If we have to expand this we will rethink how we package all these fields +/// it's just that we don't have any other examples in order to determine a +/// proper course of action. + +/// Top level Keys/Constants +#define BV_LOCALE_SERVICE_MANAGER_RESOURCE_PRODUCTION @"production" +#define BV_LOCALE_SERVICE_MANAGER_RESOURCE_STAGING @"staging" +#define BV_LOCALE_SERVICE_MANAGER_RESOURCE_DEFAULT @"default" +#define BV_LOCALE_SERVICE_MANAGER_RESOURCE_VALUES @"values" +#define BV_LOCALE_SERVICE_MANAGER_RESOURCE_MAPPINGS @"mappings" + +/// BVAnalytics Keys/Constants +#define BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE @"bvanalytics" +#define BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_DEFAULT \ + BV_LOCALE_SERVICE_MANAGER_RESOURCE_DEFAULT +#define BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU @"EU" + +/// Magpie Resource URLs +#define BV_MAGPIE_ENDPOINT @"https://network.bazaarvoice.com/event" +#define BV_MAGPIE_STAGING_ENDPOINT @"https://network-stg.bazaarvoice.com/event" +#define BV_MAGPIE_EU_ENDPOINT @"https://network-eu.bazaarvoice.com/event" +#define BV_MAGPIE_EU_STAGING_ENDPOINT \ + @"https://network-eu-stg.bazaarvoice.com/event" + +@implementation BVLocaleServiceManager + +__strong static BVLocaleServiceManager *localeManagerInstance = nil; ++ (nonnull instancetype)sharedManager { + static dispatch_once_t localeSharedManagerOnceToken; + dispatch_once(&localeSharedManagerOnceToken, ^{ + localeManagerInstance = [[self alloc] init]; + }); + return localeManagerInstance; +} + ++ (nullable NSString *)serviceToIdentifier: + (BVLocaleServiceManagerService)service { + NSString *identifier = nil; + switch (service) { + case BVLocaleServiceManagerServiceAnalytics: + identifier = BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE; + break; + default: + break; + } + return identifier; +} + +__strong static NSDictionary *resourceDictionary = nil; ++ (nonnull NSDictionary *)localeResourceDictionary { + static dispatch_once_t resourceDictionaryOnceToken; + dispatch_once(&resourceDictionaryOnceToken, ^{ + resourceDictionary = @{ + BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE : @{ + + BV_LOCALE_SERVICE_MANAGER_RESOURCE_VALUES : @{ + BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_DEFAULT : @{ + BV_LOCALE_SERVICE_MANAGER_RESOURCE_PRODUCTION : BV_MAGPIE_ENDPOINT, + BV_LOCALE_SERVICE_MANAGER_RESOURCE_STAGING : + BV_MAGPIE_STAGING_ENDPOINT + }, + BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU : @{ + BV_LOCALE_SERVICE_MANAGER_RESOURCE_PRODUCTION : + BV_MAGPIE_EU_ENDPOINT, + BV_LOCALE_SERVICE_MANAGER_RESOURCE_STAGING : + BV_MAGPIE_EU_STAGING_ENDPOINT + } + }, + + BV_LOCALE_SERVICE_MANAGER_RESOURCE_MAPPINGS : @{ + BV_LOCALE_SERVICE_MANAGER_RESOURCE_DEFAULT : + BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_DEFAULT, + @"AT" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Austria + @"BE" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Belgium + @"BG" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Bulgaria + @"CH" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Switzerland + @"CY" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Republic of + // Cyprus + @"CZ" : + BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Czech Republic + @"DE" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Germany + @"DK" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Denmark + @"ES" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Spain + @"EE" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Estonia + @"FI" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Finland + @"FR" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // France + @"GB" : + BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Great Britain + // / UK + @"GR" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Greece + @"HR" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Croatia + @"HU" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Hungary + @"IE" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Ireland + @"IS" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Iceland + @"IT" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Italy + @"LI" : + BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Liechtenstein + @"LT" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Lithuania + @"LU" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Luxembourg + @"LV" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Latvia + @"MT" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Malta + @"NL" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Netherlands + @"NO" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Norway + @"PL" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Poland + @"PT" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Portugal + @"RO" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Romania + @"SE" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Sweden + @"SI" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU, // Slovenia + @"SK" : BV_LOCALE_SERVICE_MANAGER_ANALYTICS_SERVICE_EU // Slovakia + } + } + }; + }); + + return resourceDictionary; +} + +- (nonnull NSString *)resourceForService:(BVLocaleServiceManagerService)service + withLocale:(nonnull NSLocale *)locale + andIsProduction:(BOOL)isProduction { + + NSString *resource = @""; + + /// Validate BVLocaleServiceManagerService parameter + NSString *serviceValue = [[self class] serviceToIdentifier:service]; + NSAssert(serviceValue, @"Service Value is nil, please check that the " + @"mappings are acccurate with the service local " + @"dictionary."); + if (!serviceValue) { + return resource; + } + + /// Validate that a valid locale object is acquired + NSAssert(locale, @"A valid local object must be passed in."); + if (!locale) { + return resource; + } + + id localeObject = [locale objectForKey:NSLocaleCountryCode]; + NSString *localeIdentifier = (__ISA(localeObject, NSString)) + ? ((NSString *)localeObject).uppercaseString + : nil; + NSAssert(localeIdentifier, + @"This should have received a valid locale identifier, %@", locale); + if (!localeIdentifier) { + return resource; + } + + /// Validate localeResourceDictionary pieces + NSDictionary *resourceDictionary = [[self class] localeResourceDictionary]; + NSAssert(resourceDictionary, @"Resource dictionary shouldn't be nil."); + if (!resourceDictionary) { + return resource; + } + + /// Validate the specific service dictionary we're querying + id serviceObj = [resourceDictionary objectForKey:serviceValue]; + NSDictionary *serviceDictionary = + (__ISA(serviceObj, NSDictionary)) ? (NSDictionary *)serviceObj : nil; + NSAssert(serviceDictionary, @"Service dictionary shouldn't be nil."); + if (!serviceDictionary) { + return resource; + } + + /// Validate the service values dictionary + id valuesObj = [serviceDictionary + objectForKey:BV_LOCALE_SERVICE_MANAGER_RESOURCE_VALUES]; + NSDictionary *valuesDict = + (__ISA(valuesObj, NSDictionary)) ? (NSDictionary *)valuesObj : nil; + NSAssert(valuesDict, @"Values dictionary shouldn't be nil."); + if (!valuesDict) { + return resource; + } + + /// Validate the service mappings dictionary + id mappingsObj = [serviceDictionary + objectForKey:BV_LOCALE_SERVICE_MANAGER_RESOURCE_MAPPINGS]; + NSDictionary *mappingsDict = + (__ISA(mappingsObj, NSDictionary)) ? (NSDictionary *)mappingsObj : nil; + NSAssert(mappingsDict, @"Mappings dictionary shouldn't be nil."); + if (!mappingsDict) { + return resource; + } + + /// Check to see if we have a mapping or we need to jump to a default + id map = [mappingsDict objectForKey:localeIdentifier] + ?: BV_LOCALE_SERVICE_MANAGER_RESOURCE_DEFAULT; + + /// Grab the proper specfic value dictionary + id valueObj = [valuesDict objectForKey:map]; + NSDictionary *valueDict = + (__ISA(valueObj, NSDictionary)) ? (NSDictionary *)valueObj : nil; + NSAssert(valueDict, @"Value dictionary shouldn't be nil."); + if (!valueDict) { + return resource; + } + + id environmentKey = isProduction + ? BV_LOCALE_SERVICE_MANAGER_RESOURCE_PRODUCTION + : BV_LOCALE_SERVICE_MANAGER_RESOURCE_STAGING; + + id resourceObj = [valueDict objectForKey:environmentKey]; + NSString *value = + (__ISA(resourceObj, NSString)) ? (NSString *)resourceObj : nil; + NSAssert(value, @"No proper value for environment key."); + if (!value) { + return resource; + } else { + resource = value; + } + + [[BVLogger sharedLogger] + analyticsMessage: + [NSString + stringWithFormat:@"Analytics using Locale: %@ for resource: %@", + localeIdentifier, resource]]; + + return resource; +} + +@end diff --git a/Pod/BVCommon/BVAuthenticatedUser.h b/BVSDK/BVCommon/BVAuthenticatedUser.h similarity index 99% rename from Pod/BVCommon/BVAuthenticatedUser.h rename to BVSDK/BVCommon/BVAuthenticatedUser.h index 7200d421..a66d9ffe 100644 --- a/Pod/BVCommon/BVAuthenticatedUser.h +++ b/BVSDK/BVCommon/BVAuthenticatedUser.h @@ -27,7 +27,7 @@ more information on calculating the Authorization string, please refer to the [Bazaarvoice documentation](http://knowledge.bazaarvoice.com/wp-content/conversations/en_US/KB/Default.htm#Authentication/Site_authentication/Tech_integration_site_auth.htm%23Generate) - . The authentication recipe is valid for any customer of the Converstaions, + . The authentication recipe is valid for any customer of the Conversations, Ads, or Recommendations APIs. */ @interface BVAuthenticatedUser : NSObject diff --git a/Pod/BVCommon/BVAuthenticatedUser.m b/BVSDK/BVCommon/BVAuthenticatedUser.m similarity index 89% rename from Pod/BVCommon/BVAuthenticatedUser.m rename to BVSDK/BVCommon/BVAuthenticatedUser.m index 6f3caef0..7604a475 100644 --- a/Pod/BVCommon/BVAuthenticatedUser.m +++ b/BVSDK/BVCommon/BVAuthenticatedUser.m @@ -7,6 +7,7 @@ #import "BVAuthenticatedUser.h" #import "BVCommon.h" +#import "BVNetworkingManager.h" #import @interface BVAuthenticatedUser () @@ -44,9 +45,19 @@ - (void)updateProfile:(bool)force [[BVLogger sharedLogger] verbose:[NSString stringWithFormat:@"GET: %@", profileUrl]]; - NSURLSessionConfiguration *config = - [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; + /// For private classes we ask for the NSURLSession but we don't hand back + /// any objects since it would be useless to the developers as they have no + /// interface to the object graph. + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && [sessionDelegate respondsToSelector:@selector + (URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:nil]; + } + + session = + session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; NSURLSessionDataTask *profileTask = [session dataTaskWithURL:[NSURL URLWithString:profileUrl] diff --git a/Pod/BVCommon/BVBaseAnalyticsHelper.h b/BVSDK/BVCommon/BVBaseAnalyticsHelper.h similarity index 100% rename from Pod/BVCommon/BVBaseAnalyticsHelper.h rename to BVSDK/BVCommon/BVBaseAnalyticsHelper.h diff --git a/Pod/BVCommon/BVBaseAnalyticsHelper.m b/BVSDK/BVCommon/BVBaseAnalyticsHelper.m similarity index 100% rename from Pod/BVCommon/BVBaseAnalyticsHelper.m rename to BVSDK/BVCommon/BVBaseAnalyticsHelper.m diff --git a/Pod/BVCommon/BVCommon.h b/BVSDK/BVCommon/BVCommon.h similarity index 100% rename from Pod/BVCommon/BVCommon.h rename to BVSDK/BVCommon/BVCommon.h diff --git a/Pod/BVCommon/BVDiagnosticHelpers.h b/BVSDK/BVCommon/BVDiagnosticHelpers.h similarity index 100% rename from Pod/BVCommon/BVDiagnosticHelpers.h rename to BVSDK/BVCommon/BVDiagnosticHelpers.h diff --git a/Pod/BVCommon/BVDiagnosticHelpers.m b/BVSDK/BVCommon/BVDiagnosticHelpers.m similarity index 100% rename from Pod/BVCommon/BVDiagnosticHelpers.m rename to BVSDK/BVCommon/BVDiagnosticHelpers.m diff --git a/Pod/BVCommon/BVDisplayableProductContent.h b/BVSDK/BVCommon/BVDisplayableProductContent.h similarity index 100% rename from Pod/BVCommon/BVDisplayableProductContent.h rename to BVSDK/BVCommon/BVDisplayableProductContent.h diff --git a/Pod/BVCommon/BVErrorCodeConstants.h b/BVSDK/BVCommon/BVErrorCodeConstants.h similarity index 100% rename from Pod/BVCommon/BVErrorCodeConstants.h rename to BVSDK/BVCommon/BVErrorCodeConstants.h diff --git a/Pod/BVCommon/BVLogger.h b/BVSDK/BVCommon/BVLogger.h similarity index 100% rename from Pod/BVCommon/BVLogger.h rename to BVSDK/BVCommon/BVLogger.h diff --git a/BVSDK/BVCommon/BVLogger.m b/BVSDK/BVCommon/BVLogger.m new file mode 100644 index 00000000..c472cf9d --- /dev/null +++ b/BVSDK/BVCommon/BVLogger.m @@ -0,0 +1,118 @@ +// +// BVLogger.m +// Bazaarvoice SDK +// +// Copyright 2016 Bazaarvoice Inc. All rights reserved. +// + +#import "BVLogger.h" + +#if __has_builtin(__builtin_os_log_format) +#import +#endif /* OSLog check */ + +#define BV_LOG_TAG @"" + +@implementation BVLogger + +__strong static BVLogger *sharedLoggerInstance = nil; + ++ (BVLogger *)sharedLogger { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedLoggerInstance = [[self alloc] init]; + }); + return sharedLoggerInstance; +} + +- (id)init { + if ((self = [super init])) { + self.logLevel = BVLogLevelError; + } + return self; +} + +- (void)analyticsMessage:(nonnull NSString *)message { + [self printMessage:message forLogLevel:BVLogLevelAnalyticsOnly]; +} + +- (void)verbose:(nonnull NSString *)message { + [self printMessage:message forLogLevel:BVLogLevelVerbose]; +} + +- (void)info:(nonnull NSString *)message { + [self printMessage:message forLogLevel:BVLogLevelInfo]; +} + +- (void)warning:(nonnull NSString *)message { + [self printMessage:message forLogLevel:BVLogLevelWarning]; +} + +- (void)error:(nonnull NSString *)message { + [self printMessage:message forLogLevel:BVLogLevelError]; +} + +- (void)printError:(nonnull NSError *)error { + [self printMessage:[error localizedDescription] forLogLevel:BVLogLevelError]; +} + +- (void)printErrors:(nonnull NSArray *)errors { + for (NSError *error in errors) { + [self printError:error]; + } +} + +- (void)printMessage:(nonnull NSString *)message + forLogLevel:(BVLogLevel)logLevel { + + if (BVLogLevelNone == self.logLevel || !message || 0 == message.length) { + return; + } + + NSString *logMsg = [NSString stringWithFormat:@"%@: %@", BV_LOG_TAG, message]; + + if (BVLogLevelAnalyticsOnly == logLevel && + BVLogLevelAnalyticsOnly == self.logLevel) { + [self enqueueMessage:logMsg forLogLevel:logLevel]; + return; + } + + if (logLevel <= self.logLevel) { + [self enqueueMessage:logMsg forLogLevel:logLevel]; + } +} + +- (void)enqueueMessage:(nonnull NSString *)message + forLogLevel:(BVLogLevel)logLevel { + + dispatch_async(dispatch_get_main_queue(), ^{ +#if __has_builtin(__builtin_os_log_format) + + if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) { + const char *msg = [message UTF8String]; + + switch (logLevel) { + + case BVLogLevelError: + os_log_error(OS_LOG_DEFAULT, "%{public}s", msg); + break; + case BVLogLevelAnalyticsOnly: + case BVLogLevelWarning: + case BVLogLevelInfo: + os_log_info(OS_LOG_DEFAULT, "%{public}s", msg); + break; + case BVLogLevelVerbose: + default: + os_log_debug(OS_LOG_DEFAULT, "%{public}s", msg); + break; + } + } + +#endif /* OSLog check */ + + /// We'll log to both loggers for the time being + NSLog(@"%@", message); + }); +} + +@end diff --git a/Pod/BVCommon/BVMessageInterceptor.m b/BVSDK/BVCommon/BVMessageInterceptor.m similarity index 100% rename from Pod/BVCommon/BVMessageInterceptor.m rename to BVSDK/BVCommon/BVMessageInterceptor.m diff --git a/Pod/BVCommon/BVNullHelper.h b/BVSDK/BVCommon/BVNullHelper.h similarity index 100% rename from Pod/BVCommon/BVNullHelper.h rename to BVSDK/BVCommon/BVNullHelper.h diff --git a/Pod/BVCommon/BVSDKConfiguration.m b/BVSDK/BVCommon/BVSDKConfiguration.m similarity index 96% rename from Pod/BVCommon/BVSDKConfiguration.m rename to BVSDK/BVCommon/BVSDKConfiguration.m index ad6c65bb..d99481dd 100644 --- a/Pod/BVCommon/BVSDKConfiguration.m +++ b/BVSDK/BVCommon/BVSDKConfiguration.m @@ -10,7 +10,7 @@ @implementation BVSDKConfiguration - (nonnull instancetype)initWithDictionary:(nonnull NSDictionary *)dict configType:(BVConfigurationType)type { - if (self = [super init]) { + if ((self = [super init])) { _staging = type == BVConfigurationTypeStaging; for (NSString *key in dict) { if ([self respondsToSelector:NSSelectorFromString(key)]) { diff --git a/Pod/BVCommon/BVSDKConstants.h b/BVSDK/BVCommon/BVSDKConstants.h similarity index 94% rename from Pod/BVCommon/BVSDKConstants.h rename to BVSDK/BVCommon/BVSDKConstants.h index 1fe80ae4..4f925454 100644 --- a/Pod/BVCommon/BVSDKConstants.h +++ b/BVSDK/BVCommon/BVSDKConstants.h @@ -11,11 +11,11 @@ /// Provides the master version of the SDK. -#define BV_SDK_VERSION @"6.9.0" +#define BV_SDK_VERSION @"7.0.0" /// Conversation SDK Version #define SDK_HEADER_NAME @"X-UA-BV-SDK" -#define SDK_HEADER_VALUE @"IOS_SDK_V690" +#define SDK_HEADER_VALUE @"IOS_SDK_V700" /// Error domain for NSError results, when present. #define BVErrDomain @"com.bazaarvoice.bvsdk" diff --git a/Pod/BVCommon/BVSDKManager.h b/BVSDK/BVCommon/BVSDKManager.h similarity index 51% rename from Pod/BVCommon/BVSDKManager.h rename to BVSDK/BVCommon/BVSDKManager.h index da46ed00..ed7e5b5b 100644 --- a/Pod/BVCommon/BVSDKManager.h +++ b/BVSDK/BVCommon/BVSDKManager.h @@ -8,6 +8,7 @@ #import "BVAuthenticatedUser.h" #import "BVCommon.h" #import "BVLogger.h" +#import "BVURLSessionDelegate.h" #import typedef NS_ENUM(NSUInteger, BVConfigurationType) { @@ -15,10 +16,6 @@ typedef NS_ENUM(NSUInteger, BVConfigurationType) { BVConfigurationTypeStaging }; -// For internal use of notifying the BVLocation module when the SDK has been -// intialized. -#define LOCATION_API_KEY_SET_NOTIFICATION @"locationAPIKeyReady" - // For intenal use of notifying with the Conversations Store api key has been // initialized. #define CONVERSATIONS_STORES_API_KEY_SET_NOTIFICATION \ @@ -57,6 +54,27 @@ typedef NS_ENUM(NSUInteger, BVConfigurationType) { */ @interface BVSDKManager : NSObject +/// BVURLSessionDelegate used to acquire NSURLSession objects and proxy methods. +/// Please see BVURLSessionDelegate.h for more details. Setting this is +/// guaranteed to be thread safe, however, it isn't atomic with respect to +/// currently enqueued and running jobs so behavior is undefined if you +/// set/unset this anytime after initialization. It's best to set this at launch +/// and to not unset. +@property(nullable, nonatomic, weak) id + urlSessionDelegate; + +/// Read-only value of urlRoot for BVRecommendations APIs. Varies depending on +/// value of staging. +@property(nonnull, nonatomic, readonly) NSString *urlRootShopperAdvertising; + +/// Network timeout in seconds. Default is 60 seconds. Note that iOS may set a +/// minimum timeout of 240 seconds for post requests. +@property(nonatomic, assign) float timeout; + +/// The authenticed user retrieved after calling setUserWithAuthString. The +/// model may be empty until the BV user profile has been reconcilled. +@property(nonnull, strong, readonly) BVAuthenticatedUser *bvUser; + + (void)configure:(BVConfigurationType)configurationType; + (void)configureWithConfiguration:(nonnull NSDictionary *)configDict @@ -72,72 +90,6 @@ typedef NS_ENUM(NSUInteger, BVConfigurationType) { /// BVLogLevel.kBVLogLevelError - (void)setLogLevel:(BVLogLevel)logLevel; -/// Read-only value of urlRoot for BVRecommendations APIs. Varies depending on -/// value of staging. -@property(nonnull, nonatomic, readonly) NSString *urlRootShopperAdvertising; - -/// Client ID associated with the API key -@property(nonnull, nonatomic, strong) NSString *clientId - __attribute__((deprecated( - "Use BVSDKManager#configure:(BVConfigurationType)configurationType " - "instead."))); - -/// Boolean indicating whether this request should go to staging (true) or -/// production (false). Default is production (false). -@property(nonatomic, assign) BOOL staging __attribute__((deprecated( - "Use BVSDKManager#configure:(BVConfigurationType)configurationType " - "instead."))); - -/// Your private API key for the BVConversations product -@property(nonnull, nonatomic, strong) NSString *apiKeyConversations - __attribute__((deprecated("Use " - "BVSDKManager#configure:(BVConfigurationType)" - "configurationType instead."))); - -/// Your private API key for the BVConversations, Store Reviews product -@property(nonnull, nonatomic, strong) NSString *apiKeyConversationsStores - __attribute__((deprecated("Use " - "BVSDKManager#configure:(BVConfigurationType)" - "configurationType instead."))); - -/// Your private API key for Post Interaction Notifications -@property(nonnull, nonatomic, strong) NSString *apiKeyPIN - __attribute__((deprecated( - "Use BVSDKManager#configure:(BVConfigurationType)configurationType " - "instead."))); - -/// The category of the Notification Content Extension you will use for store -/// review notifications -@property(nonnull, nonatomic, strong) - NSString *storeReviewContentExtensionCategory - __attribute__((deprecated("Use " - "BVSDKManager#configure:(BVConfigurationType)" - "configurationType instead."))); - -/// The category of the Notification Content Extension you will use for PIN -@property(nonnull, nonatomic, strong) NSString *PINContentExtensionCategory - __attribute__((deprecated("Use " - "BVSDKManager#configure:(BVConfigurationType)" - "configurationType instead."))); - -/// Your private API key for the BVRecommendations products -@property(nonnull, nonatomic, strong) NSString *apiKeyShopperAdvertising - __attribute__((deprecated("Use " - "BVSDKManager#configure:(BVConfigurationType)" - "configurationType instead."))); - -/// Your private API key for the BVCurations API -@property(nonnull, nonatomic, strong) NSString *apiKeyCurations - __attribute__((deprecated("Use " - "BVSDKManager#configure:(BVConfigurationType)" - "configurationType instead."))); - -/// Your private API key for the BVLocations API -@property(nonnull, nonatomic, strong) NSString *apiKeyLocation - __attribute__((deprecated("Use " - "BVSDKManager#configure:(BVConfigurationType)" - "configurationType instead."))); - /** Set user information. Associates a user profile with device for taylored recommendations. @@ -149,10 +101,6 @@ typedef NS_ENUM(NSUInteger, BVConfigurationType) { */ - (void)setUserWithAuthString:(nonnull NSString *)userAuthString; -/// The authenticed user retrieved after calling setUserWithAuthString. The -/// model may be empty until the BV user profile has been reconcilled. -@property(nonnull, strong, readonly) BVAuthenticatedUser *bvUser; - /** Generate DFP (Doubleclick For Publsher's) compatible custom targeting. @@ -164,8 +112,4 @@ typedef NS_ENUM(NSUInteger, BVConfigurationType) { */ - (nonnull NSDictionary *)getCustomTargeting; -/// Network timeout in seconds. Default is 60 seconds. Note that iOS may set a -/// minimum timeout of 240 seconds for post requests. -@property(nonatomic, assign) float timeout; - @end diff --git a/Pod/BVCommon/BVSDKManager.m b/BVSDK/BVCommon/BVSDKManager.m similarity index 81% rename from Pod/BVCommon/BVSDKManager.m rename to BVSDK/BVCommon/BVSDKManager.m index 3619318c..59a175ea 100644 --- a/Pod/BVCommon/BVSDKManager.m +++ b/BVSDK/BVCommon/BVSDKManager.m @@ -14,13 +14,43 @@ @interface BVSDKManager () @property(nonatomic, strong) BVSDKConfiguration *configuration; +@property(nonatomic, strong) dispatch_queue_t urlSessionDelegateQueue; +@property(nonnull, nonatomic, strong) NSString *clientId; +@property(nonatomic, assign) BOOL staging; +@property(nonnull, nonatomic, strong) NSString *apiKeyConversations; +@property(nonnull, nonatomic, strong) NSString *apiKeyConversationsStores; +@property(nonnull, nonatomic, strong) NSString *apiKeyPIN; +@property(nonnull, nonatomic, strong) + NSString *storeReviewContentExtensionCategory; +@property(nonnull, nonatomic, strong) NSString *PINContentExtensionCategory; +@property(nonnull, nonatomic, strong) NSString *apiKeyShopperAdvertising; +@property(nonnull, nonatomic, strong) NSString *apiKeyCurations; @end @implementation BVSDKManager + static NSString *const BVSDKConfigFileNameProd = @"bvsdk_config_prod"; static NSString *const BVSDKConfigFileNameStaging = @"bvsdk_config_staging"; static NSString *const BVSDKConfigFileExt = @"json"; +@synthesize urlSessionDelegate = _urlSessionDelegate, + urlSessionDelegateQueue = _urlSessionDelegateQueue; + +- (nullable id)urlSessionDelegate { + __block id tempDelegate = nil; + dispatch_sync(self.urlSessionDelegateQueue, ^{ + tempDelegate = _urlSessionDelegate; + }); + return tempDelegate; +} + +- (void)setUrlSessionDelegate: + (nullable id)urlSessionDelegate { + dispatch_sync(self.urlSessionDelegateQueue, ^{ + _urlSessionDelegate = urlSessionDelegate; + }); +} + + (void)configure:(BVConfigurationType)configurationType { [[self sharedManager] attemptToLoadConfiguration:configurationType]; } @@ -48,21 +78,23 @@ + (instancetype)sharedManager { return _sharedObject; } -- (id)init { - self = [super init]; - if (self) { +- (instancetype)init { + if ((self = [super init])) { _bvUser = [[BVAuthenticatedUser alloc] init]; // make sure analytics has been started [BVAnalyticsManager sharedManager]; + _urlSessionDelegateQueue = dispatch_queue_create( + "com.bazaarvoice.BVSDKManager.urlSessionDelegateQueue", + DISPATCH_QUEUE_SERIAL); + _timeout = 60; _staging = NO; _clientId = nil; _apiKeyConversations = nil; _apiKeyShopperAdvertising = nil; _apiKeyConversationsStores = nil; - _apiKeyLocation = nil; _configuration = [[BVSDKConfiguration alloc] init]; } return self; @@ -105,6 +137,26 @@ - (void)configureWithDictionary:(NSDictionary *)dict configType:configType]; [BVAnalyticsManager sharedManager].isDryRunAnalytics = _configuration.dryRunAnalytics; + + /// Handle Analytics Locale Configuration + NSLocale *analyticsLocale = nil; + NSString *analyticsLocaleIdentifier = + _configuration.analyticsLocaleIdentifier; + + if (analyticsLocaleIdentifier) { + analyticsLocale = + [[NSLocale alloc] initWithLocaleIdentifier:analyticsLocaleIdentifier]; + } else { + [[BVLogger sharedLogger] + warning:[NSString stringWithFormat:@"BVSDK is currently using user " + @"region settings. Please see the " + @"documentation regarding setting " + @"proper locale settings for " + @"dealing with user data privacy."]]; + } + + [BVAnalyticsManager sharedManager].analyticsLocale = analyticsLocale; + [self copyJson:config toObj:self]; } @@ -187,17 +239,6 @@ - (void)setApiKeyConversationsStores:(NSString *)apiKeyConversationsStores { forKeyPath:@"apiKeyConversationsStores"]; } -- (void)setApiKeyLocation:(NSString *)apiKeyLocation { - _apiKeyLocation = apiKeyLocation; - NSDictionary *userInfo = - @{LOCATION_API_KEY_SET_NOTIFICATION : _apiKeyLocation}; - [[NSNotificationCenter defaultCenter] - postNotificationName:LOCATION_API_KEY_SET_NOTIFICATION - object:nil - userInfo:userInfo]; - [_configuration setValue:apiKeyLocation forKeyPath:@"apiKeyLocation"]; -} - - (void)setApiKeyPIN:(NSString *)apiKeyPIN { _apiKeyPIN = apiKeyPIN; NSDictionary *userInfo = @{PIN_API_KEY_SET_NOTIFICATION : _apiKeyPIN}; diff --git a/Pod/BVCommon/BVStringKeyValuePair.h b/BVSDK/BVCommon/BVStringKeyValuePair.h similarity index 100% rename from Pod/BVCommon/BVStringKeyValuePair.h rename to BVSDK/BVCommon/BVStringKeyValuePair.h diff --git a/Pod/BVCommon/BVStringKeyValuePair.m b/BVSDK/BVCommon/BVStringKeyValuePair.m similarity index 100% rename from Pod/BVCommon/BVStringKeyValuePair.m rename to BVSDK/BVCommon/BVStringKeyValuePair.m diff --git a/BVSDK/BVCommon/BVURLSessionDelegate.h b/BVSDK/BVCommon/BVURLSessionDelegate.h new file mode 100644 index 00000000..c13e22e7 --- /dev/null +++ b/BVSDK/BVCommon/BVURLSessionDelegate.h @@ -0,0 +1,36 @@ +// +// BVURLSessionDelegate.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#ifndef BVURLSESSIONDELEGATE_H +#define BVURLSESSIONDELEGATE_H + +#import + +@protocol BVURLSessionDelegate +@required + +/* + * This method will be invoked before an impending networking action to acquire + * the NSURLSession to be used to create the cooresponding NSURLSessionTask. The + * object that is responsible for the networking event will be passed as a + * parameter to the callee so that the operation can be tracked through to the + * NSURLSession delegate methods. + */ +- (nonnull NSURLSession *)URLSessionForBVObject:(nullable id)bvObject; + +/* + * This method will be invoked after a networking action is enqueued and returns + * the NSURLSessionTask, object, and session responsible for a successfully + * submitted (but not necessarily successfully completed) action. + */ +- (void)URLSessionTask:(nonnull NSURLSessionTask *)urlSessionTask + fromBVObject:(nonnull id)bvObject + withURLSession:(nonnull NSURLSession *)session; + +@end + +#endif /* BVURLSESSIONDELEGATE_H */ diff --git a/Pod/BVCommon/Private/BVMessageInterceptor.h b/BVSDK/BVCommon/Private/BVMessageInterceptor.h similarity index 100% rename from Pod/BVCommon/Private/BVMessageInterceptor.h rename to BVSDK/BVCommon/Private/BVMessageInterceptor.h diff --git a/BVSDK/BVCommon/Private/BVNetworkingManager.h b/BVSDK/BVCommon/Private/BVNetworkingManager.h new file mode 100644 index 00000000..95df2bc1 --- /dev/null +++ b/BVSDK/BVCommon/Private/BVNetworkingManager.h @@ -0,0 +1,26 @@ +// +// BVNetworkingManager.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +/* + * Private interface for internal BV networking management. Since there's + * various vagaries associated with sending/receiving networking data we're + * going to try and shove all of that here to try and abstract it away from the + * various other objects that need to interact with BV APIs. + */ +@interface BVNetworkingManager : NSObject + +/// Create and get the singleton instance of the networking manager. ++ (nonnull instancetype)sharedManager; + +/// Right now this will only vend the global context URLSession used internally, +/// however, just in case we have context specific networking requests we can +/// shoehorn this into this class eventually. +@property(nonnull, readonly) NSURLSession *bvNetworkingSession; + +@end diff --git a/BVSDK/BVCommon/Private/BVNetworkingManager.m b/BVSDK/BVCommon/Private/BVNetworkingManager.m new file mode 100644 index 00000000..63fe343e --- /dev/null +++ b/BVSDK/BVCommon/Private/BVNetworkingManager.m @@ -0,0 +1,101 @@ +// +// BVNetworkingManager.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#define __EXISTS(OBJ, SELECTOR) ([(OBJ) respondsToSelector:@selector(SELECTOR)]) + +#import "BVNetworkingManager.h" + +@interface BVNetworkingManager () +@property(nonatomic, strong, readwrite) NSURLSession *bvNetworkingSession; +@property(nonatomic, strong) NSOperationQueue *networkingOperationQueue; +@end + +@implementation BVNetworkingManager + +__strong static BVNetworkingManager *networkingManagerInstance = nil; ++ (nonnull instancetype)sharedManager { + static dispatch_once_t networkingManagerOnceToken; + dispatch_once(&networkingManagerOnceToken, ^{ + networkingManagerInstance = [[self alloc] init]; + }); + return networkingManagerInstance; +} + +@synthesize bvNetworkingSession = _bvNetworkingSession, + networkingOperationQueue = _networkingOperationQueue; + +- (nonnull NSURLSession *)bvNetworkingSession { + static dispatch_once_t networkingSessionOnceToken; + dispatch_once(&networkingSessionOnceToken, ^{ + NSURLSessionConfiguration *config = + [NSURLSessionConfiguration defaultSessionConfiguration]; + if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *)) { + if (__EXISTS(config, waitsForConnectivity)) { + config.waitsForConnectivity = YES; + } + } + + _bvNetworkingSession = + [NSURLSession sessionWithConfiguration:config + delegate:self + delegateQueue:_networkingOperationQueue]; + }); + + return _bvNetworkingSession; +} + +- (instancetype)init { + if ((self = [super init])) { + _networkingOperationQueue = [[NSOperationQueue alloc] init]; + _networkingOperationQueue.maxConcurrentOperationCount = 1; + if (@available(iOS 8.0, macOS 10.10, tvOS 9.0, watchOS 2.0, *)) { + if (__EXISTS(_networkingOperationQueue, qualityOfService)) { + _networkingOperationQueue.qualityOfService = + NSQualityOfServiceUserInitiated; + } + } + } + return self; +} + +#pragma mark - NSURLSessionDelegate + +- (void)URLSession:(NSURLSession *)session + didBecomeInvalidWithError:(nullable NSError *)error { + /// Unused for now. +} + +#pragma mark - NSURLSessionTaskDelegate + +- (void)URLSession:(NSURLSession *)session + taskIsWaitingForConnectivity:(NSURLSessionTask *)task { + /// Unused for now. +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didSendBodyData:(int64_t)bytesSent + totalBytesSent:(int64_t)totalBytesSent + totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { + /// Unused for now. +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics + API_AVAILABLE(ios(10.0)) { + /// Unused for now. +} + +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didCompleteWithError:(nullable NSError *)error { + /// Unused for now. +} + +@end diff --git a/Pod/BVCommon/Private/BVSDKConfiguration.h b/BVSDK/BVCommon/Private/BVSDKConfiguration.h similarity index 93% rename from Pod/BVCommon/Private/BVSDKConfiguration.h rename to BVSDK/BVCommon/Private/BVSDKConfiguration.h index 80b27877..4de953e9 100644 --- a/Pod/BVCommon/Private/BVSDKConfiguration.h +++ b/BVSDK/BVCommon/Private/BVSDKConfiguration.h @@ -43,13 +43,14 @@ /// Your private API key for the BVCurations API @property(nonatomic, strong, readonly, nullable) NSString *apiKeyCurations; -/// Your private API key for the BVLocations API -@property(nonatomic, strong, readonly, nullable) NSString *apiKeyLocation; - @property(nonatomic, assign, readonly) BOOL staging; @property(nonatomic, assign, readonly) BOOL dryRunAnalytics; +/// The Locale Specific binding for Analytics +@property(nonatomic, strong, readonly, nullable) + NSString *analyticsLocaleIdentifier; + @end // expose config to internal SDK diff --git a/Pod/BVCommonUI/BVViewsHelper.h b/BVSDK/BVCommonUI/BVViewsHelper.h similarity index 100% rename from Pod/BVCommonUI/BVViewsHelper.h rename to BVSDK/BVCommonUI/BVViewsHelper.h diff --git a/Pod/BVCommonUI/BVViewsHelper.m b/BVSDK/BVCommonUI/BVViewsHelper.m similarity index 100% rename from Pod/BVCommonUI/BVViewsHelper.m rename to BVSDK/BVCommonUI/BVViewsHelper.m diff --git a/BVSDKTests/UIImage+BundleLocator.h b/BVSDK/BVCommonUI/Private/UIImage+BundleLocator.h similarity index 100% rename from BVSDKTests/UIImage+BundleLocator.h rename to BVSDK/BVCommonUI/Private/UIImage+BundleLocator.h diff --git a/Pod/BVCommonUI/UIImage+BundleLocator.m b/BVSDK/BVCommonUI/UIImage+BundleLocator.m similarity index 100% rename from Pod/BVCommonUI/UIImage+BundleLocator.m rename to BVSDK/BVCommonUI/UIImage+BundleLocator.m diff --git a/Pod/BVConversations/.gitkeep b/BVSDK/BVConversations/.gitkeep similarity index 100% rename from Pod/BVConversations/.gitkeep rename to BVSDK/BVConversations/.gitkeep diff --git a/Pod/BVConversations/Display/Model/BVAuthorResponse.h b/BVSDK/BVConversations/Display/Model/BVAuthorResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVAuthorResponse.h rename to BVSDK/BVConversations/Display/Model/BVAuthorResponse.h diff --git a/Pod/BVConversations/Display/Model/BVAuthorResponse.m b/BVSDK/BVConversations/Display/Model/BVAuthorResponse.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVAuthorResponse.m rename to BVSDK/BVConversations/Display/Model/BVAuthorResponse.m diff --git a/Pod/BVConversations/Display/Model/BVBadge.h b/BVSDK/BVConversations/Display/Model/BVBadge.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVBadge.h rename to BVSDK/BVConversations/Display/Model/BVBadge.h diff --git a/Pod/BVConversations/Display/Model/BVBadge.m b/BVSDK/BVConversations/Display/Model/BVBadge.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVBadge.m rename to BVSDK/BVConversations/Display/Model/BVBadge.m diff --git a/Pod/BVConversations/Display/Model/BVBadgeType.h b/BVSDK/BVConversations/Display/Model/BVBadgeType.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVBadgeType.h rename to BVSDK/BVConversations/Display/Model/BVBadgeType.h diff --git a/Pod/BVConversations/Display/Model/BVBadgeType.m b/BVSDK/BVConversations/Display/Model/BVBadgeType.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVBadgeType.m rename to BVSDK/BVConversations/Display/Model/BVBadgeType.m diff --git a/Pod/BVConversations/Display/Model/BVBrand.h b/BVSDK/BVConversations/Display/Model/BVBrand.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVBrand.h rename to BVSDK/BVConversations/Display/Model/BVBrand.h diff --git a/Pod/BVConversations/Display/Model/BVBrand.m b/BVSDK/BVConversations/Display/Model/BVBrand.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVBrand.m rename to BVSDK/BVConversations/Display/Model/BVBrand.m diff --git a/Pod/BVConversations/Display/Model/BVBulkProductResponse.h b/BVSDK/BVConversations/Display/Model/BVBulkProductResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVBulkProductResponse.h rename to BVSDK/BVConversations/Display/Model/BVBulkProductResponse.h diff --git a/Pod/BVConversations/Display/Model/BVBulkProductResponse.m b/BVSDK/BVConversations/Display/Model/BVBulkProductResponse.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVBulkProductResponse.m rename to BVSDK/BVConversations/Display/Model/BVBulkProductResponse.m diff --git a/Pod/BVConversations/Display/Model/BVBulkRatingsResponse.h b/BVSDK/BVConversations/Display/Model/BVBulkRatingsResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVBulkRatingsResponse.h rename to BVSDK/BVConversations/Display/Model/BVBulkRatingsResponse.h diff --git a/Pod/BVConversations/Display/Model/BVBulkRatingsResponse.m b/BVSDK/BVConversations/Display/Model/BVBulkRatingsResponse.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVBulkRatingsResponse.m rename to BVSDK/BVConversations/Display/Model/BVBulkRatingsResponse.m diff --git a/Pod/BVConversations/Display/Model/BVCommentsResponse.h b/BVSDK/BVConversations/Display/Model/BVCommentsResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVCommentsResponse.h rename to BVSDK/BVConversations/Display/Model/BVCommentsResponse.h diff --git a/Pod/BVConversations/Display/Model/BVCommentsResponse.m b/BVSDK/BVConversations/Display/Model/BVCommentsResponse.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVCommentsResponse.m rename to BVSDK/BVConversations/Display/Model/BVCommentsResponse.m diff --git a/Pod/BVConversations/Display/Model/BVContextDataValue.h b/BVSDK/BVConversations/Display/Model/BVContextDataValue.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVContextDataValue.h rename to BVSDK/BVConversations/Display/Model/BVContextDataValue.h diff --git a/Pod/BVConversations/Display/Model/BVContextDataValue.m b/BVSDK/BVConversations/Display/Model/BVContextDataValue.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVContextDataValue.m rename to BVSDK/BVConversations/Display/Model/BVContextDataValue.m diff --git a/Pod/BVConversations/Display/Model/BVConversationsError.h b/BVSDK/BVConversations/Display/Model/BVConversationsError.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVConversationsError.h rename to BVSDK/BVConversations/Display/Model/BVConversationsError.h diff --git a/Pod/BVConversations/Display/Model/BVConversationsError.m b/BVSDK/BVConversations/Display/Model/BVConversationsError.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVConversationsError.m rename to BVSDK/BVConversations/Display/Model/BVConversationsError.m diff --git a/Pod/BVConversations/Display/Model/BVConversationsErrorResponse.h b/BVSDK/BVConversations/Display/Model/BVConversationsErrorResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVConversationsErrorResponse.h rename to BVSDK/BVConversations/Display/Model/BVConversationsErrorResponse.h diff --git a/Pod/BVConversations/Display/Model/BVConversationsErrorResponse.m b/BVSDK/BVConversations/Display/Model/BVConversationsErrorResponse.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVConversationsErrorResponse.m rename to BVSDK/BVConversations/Display/Model/BVConversationsErrorResponse.m diff --git a/Pod/BVConversations/Display/Model/BVConversationsInclude.h b/BVSDK/BVConversations/Display/Model/BVConversationsInclude.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVConversationsInclude.h rename to BVSDK/BVConversations/Display/Model/BVConversationsInclude.h diff --git a/Pod/BVConversations/Display/Model/BVConversationsInclude.m b/BVSDK/BVConversations/Display/Model/BVConversationsInclude.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVConversationsInclude.m rename to BVSDK/BVConversations/Display/Model/BVConversationsInclude.m diff --git a/Pod/BVConversations/Display/Model/BVDimensionAndDistributionUtil.h b/BVSDK/BVConversations/Display/Model/BVDimensionAndDistributionUtil.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVDimensionAndDistributionUtil.h rename to BVSDK/BVConversations/Display/Model/BVDimensionAndDistributionUtil.h diff --git a/Pod/BVConversations/Display/Model/BVDimensionAndDistributionUtil.m b/BVSDK/BVConversations/Display/Model/BVDimensionAndDistributionUtil.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVDimensionAndDistributionUtil.m rename to BVSDK/BVConversations/Display/Model/BVDimensionAndDistributionUtil.m diff --git a/Pod/BVConversations/Display/Model/BVDimensionElement.h b/BVSDK/BVConversations/Display/Model/BVDimensionElement.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVDimensionElement.h rename to BVSDK/BVConversations/Display/Model/BVDimensionElement.h diff --git a/Pod/BVConversations/Display/Model/BVDimensionElement.m b/BVSDK/BVConversations/Display/Model/BVDimensionElement.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVDimensionElement.m rename to BVSDK/BVConversations/Display/Model/BVDimensionElement.m diff --git a/Pod/BVConversations/Display/Model/BVDistributionElement.h b/BVSDK/BVConversations/Display/Model/BVDistributionElement.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVDistributionElement.h rename to BVSDK/BVConversations/Display/Model/BVDistributionElement.h diff --git a/Pod/BVConversations/Display/Model/BVDistributionElement.m b/BVSDK/BVConversations/Display/Model/BVDistributionElement.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVDistributionElement.m rename to BVSDK/BVConversations/Display/Model/BVDistributionElement.m diff --git a/Pod/BVConversations/Display/Model/BVDistributionValue.h b/BVSDK/BVConversations/Display/Model/BVDistributionValue.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVDistributionValue.h rename to BVSDK/BVConversations/Display/Model/BVDistributionValue.h diff --git a/Pod/BVConversations/Display/Model/BVDistributionValue.m b/BVSDK/BVConversations/Display/Model/BVDistributionValue.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVDistributionValue.m rename to BVSDK/BVConversations/Display/Model/BVDistributionValue.m diff --git a/Pod/BVConversations/Display/BVErrorCode.h b/BVSDK/BVConversations/Display/Model/BVErrorCode.h similarity index 100% rename from Pod/BVConversations/Display/BVErrorCode.h rename to BVSDK/BVConversations/Display/Model/BVErrorCode.h diff --git a/Pod/BVConversations/Display/Model/BVGenericConversationsResult.h b/BVSDK/BVConversations/Display/Model/BVGenericConversationsResult.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVGenericConversationsResult.h rename to BVSDK/BVConversations/Display/Model/BVGenericConversationsResult.h diff --git a/Pod/BVConversations/Display/Model/BVModelUtil.h b/BVSDK/BVConversations/Display/Model/BVModelUtil.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVModelUtil.h rename to BVSDK/BVConversations/Display/Model/BVModelUtil.h diff --git a/Pod/BVConversations/Display/Model/BVModelUtil.m b/BVSDK/BVConversations/Display/Model/BVModelUtil.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVModelUtil.m rename to BVSDK/BVConversations/Display/Model/BVModelUtil.m diff --git a/Pod/BVConversations/Display/Model/BVPhoto.h b/BVSDK/BVConversations/Display/Model/BVPhoto.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVPhoto.h rename to BVSDK/BVConversations/Display/Model/BVPhoto.h diff --git a/Pod/BVConversations/Display/Model/BVPhoto.m b/BVSDK/BVConversations/Display/Model/BVPhoto.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVPhoto.m rename to BVSDK/BVConversations/Display/Model/BVPhoto.m diff --git a/Pod/BVConversations/Display/Model/BVPhotoSizes.h b/BVSDK/BVConversations/Display/Model/BVPhotoSizes.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVPhotoSizes.h rename to BVSDK/BVConversations/Display/Model/BVPhotoSizes.h diff --git a/Pod/BVConversations/Display/Model/BVPhotoSizes.m b/BVSDK/BVConversations/Display/Model/BVPhotoSizes.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVPhotoSizes.m rename to BVSDK/BVConversations/Display/Model/BVPhotoSizes.m diff --git a/Pod/BVConversations/Display/Model/BVProductStatistics.h b/BVSDK/BVConversations/Display/Model/BVProductStatistics.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVProductStatistics.h rename to BVSDK/BVConversations/Display/Model/BVProductStatistics.h diff --git a/Pod/BVConversations/Display/Model/BVProductStatistics.m b/BVSDK/BVConversations/Display/Model/BVProductStatistics.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVProductStatistics.m rename to BVSDK/BVConversations/Display/Model/BVProductStatistics.m diff --git a/Pod/BVConversations/Display/Model/BVProductsResponse.h b/BVSDK/BVConversations/Display/Model/BVProductsResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVProductsResponse.h rename to BVSDK/BVConversations/Display/Model/BVProductsResponse.h diff --git a/Pod/BVConversations/Display/Model/BVProductsResponse.m b/BVSDK/BVConversations/Display/Model/BVProductsResponse.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVProductsResponse.m rename to BVSDK/BVConversations/Display/Model/BVProductsResponse.m diff --git a/Pod/BVConversations/Display/Model/BVQAStatistics.h b/BVSDK/BVConversations/Display/Model/BVQAStatistics.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVQAStatistics.h rename to BVSDK/BVConversations/Display/Model/BVQAStatistics.h diff --git a/Pod/BVConversations/Display/Model/BVQAStatistics.m b/BVSDK/BVConversations/Display/Model/BVQAStatistics.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVQAStatistics.m rename to BVSDK/BVConversations/Display/Model/BVQAStatistics.m diff --git a/Pod/BVConversations/Display/Model/BVQuestionsAndAnswersResponse.h b/BVSDK/BVConversations/Display/Model/BVQuestionsAndAnswersResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVQuestionsAndAnswersResponse.h rename to BVSDK/BVConversations/Display/Model/BVQuestionsAndAnswersResponse.h diff --git a/Pod/BVConversations/Display/Model/BVQuestionsAndAnswersResponse.m b/BVSDK/BVConversations/Display/Model/BVQuestionsAndAnswersResponse.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVQuestionsAndAnswersResponse.m rename to BVSDK/BVConversations/Display/Model/BVQuestionsAndAnswersResponse.m diff --git a/Pod/BVConversations/Display/Model/BVRatingDistribution.h b/BVSDK/BVConversations/Display/Model/BVRatingDistribution.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVRatingDistribution.h rename to BVSDK/BVConversations/Display/Model/BVRatingDistribution.h diff --git a/Pod/BVConversations/Display/Model/BVRatingDistribution.m b/BVSDK/BVConversations/Display/Model/BVRatingDistribution.m similarity index 94% rename from Pod/BVConversations/Display/Model/BVRatingDistribution.m rename to BVSDK/BVConversations/Display/Model/BVRatingDistribution.m index e5bc5b13..c4a4389f 100644 --- a/Pod/BVConversations/Display/Model/BVRatingDistribution.m +++ b/BVSDK/BVConversations/Display/Model/BVRatingDistribution.m @@ -22,7 +22,7 @@ - (nullable id)initWithApiResponse:(nullable id)apiRepsonse { NSNumber *count = value[@"Count"]; NSNumber *valueNum = value[@"RatingValue"]; - int valueInt = [valueNum intValue]; + NSUInteger valueInt = [valueNum unsignedIntegerValue]; switch (valueInt) { case 1: self.oneStarCount = count; diff --git a/Pod/BVConversations/Display/Model/BVResponse.h b/BVSDK/BVConversations/Display/Model/BVResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVResponse.h rename to BVSDK/BVConversations/Display/Model/BVResponse.h diff --git a/Pod/BVConversations/Display/Model/BVReviewStatistic.h b/BVSDK/BVConversations/Display/Model/BVReviewStatistic.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVReviewStatistic.h rename to BVSDK/BVConversations/Display/Model/BVReviewStatistic.h diff --git a/Pod/BVConversations/Display/Model/BVReviewStatistic.m b/BVSDK/BVConversations/Display/Model/BVReviewStatistic.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVReviewStatistic.m rename to BVSDK/BVConversations/Display/Model/BVReviewStatistic.m diff --git a/Pod/BVConversations/Display/Model/BVReviewStatistics.h b/BVSDK/BVConversations/Display/Model/BVReviewStatistics.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVReviewStatistics.h rename to BVSDK/BVConversations/Display/Model/BVReviewStatistics.h diff --git a/Pod/BVConversations/Display/Model/BVReviewStatistics.m b/BVSDK/BVConversations/Display/Model/BVReviewStatistics.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVReviewStatistics.m rename to BVSDK/BVConversations/Display/Model/BVReviewStatistics.m diff --git a/Pod/BVConversations/Display/Model/BVReviewsResponse.h b/BVSDK/BVConversations/Display/Model/BVReviewsResponse.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVReviewsResponse.h rename to BVSDK/BVConversations/Display/Model/BVReviewsResponse.h diff --git a/Pod/BVConversations/Display/Model/BVReviewsResponse.m b/BVSDK/BVConversations/Display/Model/BVReviewsResponse.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVReviewsResponse.m rename to BVSDK/BVConversations/Display/Model/BVReviewsResponse.m diff --git a/Pod/BVConversations/Display/Model/BVSecondaryRating.h b/BVSDK/BVConversations/Display/Model/BVSecondaryRating.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVSecondaryRating.h rename to BVSDK/BVConversations/Display/Model/BVSecondaryRating.h diff --git a/Pod/BVConversations/Display/Model/BVSecondaryRating.m b/BVSDK/BVConversations/Display/Model/BVSecondaryRating.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVSecondaryRating.m rename to BVSDK/BVConversations/Display/Model/BVSecondaryRating.m diff --git a/Pod/BVConversations/Display/Model/BVSecondaryRatingsAverages.h b/BVSDK/BVConversations/Display/Model/BVSecondaryRatingsAverages.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVSecondaryRatingsAverages.h rename to BVSDK/BVConversations/Display/Model/BVSecondaryRatingsAverages.h diff --git a/Pod/BVConversations/Display/Model/BVSecondaryRatingsAverages.m b/BVSDK/BVConversations/Display/Model/BVSecondaryRatingsAverages.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVSecondaryRatingsAverages.m rename to BVSDK/BVConversations/Display/Model/BVSecondaryRatingsAverages.m diff --git a/Pod/BVConversations/Display/Model/BVSyndicationSource.h b/BVSDK/BVConversations/Display/Model/BVSyndicationSource.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVSyndicationSource.h rename to BVSDK/BVConversations/Display/Model/BVSyndicationSource.h diff --git a/Pod/BVConversations/Display/Model/BVSyndicationSource.m b/BVSDK/BVConversations/Display/Model/BVSyndicationSource.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVSyndicationSource.m rename to BVSDK/BVConversations/Display/Model/BVSyndicationSource.m diff --git a/Pod/BVConversations/Display/Model/BVVideo.h b/BVSDK/BVConversations/Display/Model/BVVideo.h similarity index 100% rename from Pod/BVConversations/Display/Model/BVVideo.h rename to BVSDK/BVConversations/Display/Model/BVVideo.h diff --git a/Pod/BVConversations/Display/Model/BVVideo.m b/BVSDK/BVConversations/Display/Model/BVVideo.m similarity index 100% rename from Pod/BVConversations/Display/Model/BVVideo.m rename to BVSDK/BVConversations/Display/Model/BVVideo.m diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAnswer.h b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVAnswer.h similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVAnswer.h rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVAnswer.h diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAnswer.m b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVAnswer.m similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVAnswer.m rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVAnswer.m diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.h b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.h similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.h rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.h diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.m b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.m similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.m rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVAuthor.m diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVComment.h b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVComment.h similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVComment.h rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVComment.h diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVComment.m b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVComment.m similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVComment.m rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVComment.m diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVProduct.h b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVProduct.h similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVProduct.h rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVProduct.h diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVProduct.m b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVProduct.m similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVProduct.m rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVProduct.m diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVQuestion.h b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVQuestion.h similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVQuestion.h rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVQuestion.h diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVQuestion.m b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVQuestion.m similarity index 100% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVQuestion.m rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVQuestion.m diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVReview.h b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVReview.h similarity index 98% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVReview.h rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVReview.h index f96b2d12..641ddfc3 100644 --- a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVReview.h +++ b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVReview.h @@ -32,7 +32,7 @@ @property(nullable) NSString *reviewText; @property(nullable) NSString *userNickname; @property(nullable) NSString *title; -@property int rating; +@property NSUInteger rating; @property(nullable) BVProduct *product; @property(nullable) TagDimensions tagDimensions; diff --git a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVReview.m b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVReview.m similarity index 98% rename from Pod/BVConversations/Display/Model/GenericConversationsResult/BVReview.m rename to BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVReview.m index 673402ff..f9eec552 100644 --- a/Pod/BVConversations/Display/Model/GenericConversationsResult/BVReview.m +++ b/BVSDK/BVConversations/Display/Model/GenericConversationsResult/BVReview.m @@ -78,7 +78,7 @@ - (id)initWithApiResponse:(NSDictionary *)apiResponse NSNumber *num = apiResponse[@"Rating"]; if (num && [num isKindOfClass:[NSNumber class]]) { - self.rating = (int)[num integerValue]; + self.rating = [num unsignedIntegerValue]; } else { self.rating = 0; } diff --git a/Pod/BVConversations/Display/Model/NSError+BVErrorCodeParser.h b/BVSDK/BVConversations/Display/Model/NSError+BVErrorCodeParser.h similarity index 100% rename from Pod/BVConversations/Display/Model/NSError+BVErrorCodeParser.h rename to BVSDK/BVConversations/Display/Model/NSError+BVErrorCodeParser.h diff --git a/Pod/BVConversations/Display/Model/NSError+BVErrorCodeParser.m b/BVSDK/BVConversations/Display/Model/NSError+BVErrorCodeParser.m similarity index 100% rename from Pod/BVConversations/Display/Model/NSError+BVErrorCodeParser.m rename to BVSDK/BVConversations/Display/Model/NSError+BVErrorCodeParser.m diff --git a/Pod/BVConversations/Display/Requests/BVAuthorRequest.h b/BVSDK/BVConversations/Display/Requests/BVAuthorRequest.h similarity index 61% rename from Pod/BVConversations/Display/Requests/BVAuthorRequest.h rename to BVSDK/BVConversations/Display/Requests/BVAuthorRequest.h index 0bae0707..dac0537c 100644 --- a/Pod/BVConversations/Display/Requests/BVAuthorRequest.h +++ b/BVSDK/BVConversations/Display/Requests/BVAuthorRequest.h @@ -5,13 +5,9 @@ // Copyright © 2017 Bazaarvoice. All rights reserved. // -#import "BVAuthorContentType.h" #import "BVAuthorResponse.h" +#import "BVConversationDisplay.h" #import "BVConversationsRequest.h" -#import "BVSort.h" -#import "BVSortOptionAnswers.h" -#import "BVSortOptionQuestions.h" -#import "BVSortOptionReviews.h" /** This class allow you to build request parameters and request a user profile * (author) and accepted content (Reviews, Questions, Answers) the author has @@ -29,24 +25,31 @@ /// Add the statistics for the content type. Call multiple times for more than /// one submission type. -- (nonnull instancetype)includeStatistics:(BVAuthorContentType)contentType; +- (nonnull instancetype)includeStatistics: + (BVAuthorIncludeTypeValue)authorIncludeTypeValue; /// The type of submitted content to include. Call once for each type of /// Reviews, Questions, Answers. -- (nonnull instancetype)includeContent:(BVAuthorContentType)contentType - limit:(int)limit; +- (nonnull instancetype)includeAuthorIncludeTypeValue: + (BVAuthorIncludeTypeValue)authorIncludeTypeValue + limit:(NSUInteger)limit; /// When Reviews are included in the response, optinally add one or more sort /// parameters. -- (nonnull instancetype)sortIncludedReviews:(BVSortOptionReviews)option - order:(BVSortOrder)order; +- (nonnull instancetype) +sortByReviewsSortOptionValue:(BVReviewsSortOptionValue)reviewsSortOptionValue + monotonicSortOrderValue:(BVMonotonicSortOrderValue)monotonicSortOrderValue; /// When Questions are included in the response, optinally add one or more sort /// parameters. -- (nonnull instancetype)sortIncludedQuestions:(BVSortOptionQuestions)option - order:(BVSortOrder)order; +- (nonnull instancetype)sortByQuestionsSortOptionValue: + (BVQuestionsSortOptionValue)questionsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue) + monotonicSortOrderValue; /// When Answers are included in the response, optinally add one or more sort /// parameters. -- (nonnull instancetype)sortIncludedAnswers:(BVSortOptionAnswers)option - order:(BVSortOrder)order; +- (nonnull instancetype) +sortByAnswersSortOptionValue:(BVAnswersSortOptionValue)answersSortOptionValue + monotonicSortOrderValue:(BVMonotonicSortOrderValue)monotonicSortOrderValue; /// Make an asynch http request to fetch the Author's profile data. See the /// BVAuthorResponse model for available fields. diff --git a/Pod/BVConversations/Display/Requests/BVAuthorRequest.m b/BVSDK/BVConversations/Display/Requests/BVAuthorRequest.m similarity index 57% rename from Pod/BVConversations/Display/Requests/BVAuthorRequest.m rename to BVSDK/BVConversations/Display/Requests/BVAuthorRequest.m index fa46b37b..f53a0d74 100644 --- a/Pod/BVConversations/Display/Requests/BVAuthorRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVAuthorRequest.m @@ -6,21 +6,25 @@ // #import "BVAuthorRequest.h" -#import "BVAuthorInclude.h" +#import "BVAnswersSortOption.h" +#import "BVAuthorIncludeType.h" #import "BVCommaUtil.h" -#import "BVFilter.h" #import "BVLogger.h" +#import "BVMonotonicSortOrder.h" #import "BVPixel.h" +#import "BVQuestionsSortOption.h" +#import "BVRelationalFilterOperator.h" +#import "BVReviewsSortOption.h" #import "BVSort.h" @interface BVAuthorRequest () -@property int limit; -@property int offset; +@property NSUInteger limit; +@property NSUInteger offset; @property(nullable) NSString *search; @property(nonnull) NSMutableArray *filters; -@property(nonnull) NSMutableArray *authorContentTypeStatistics; -@property NSMutableArray *includes; +@property(nonnull) NSMutableArray *authorIncludeTypes; +@property NSMutableArray *includes; @property(nonnull) NSMutableArray *reviewSorts; @property(nonnull) NSMutableArray *questionSorts; @property(nonnull) NSMutableArray *answerSorts; @@ -34,11 +38,11 @@ - (nonnull instancetype)initWithAuthorId:(nonnull NSString *)authorId { } - (nonnull instancetype)initWithAuthorId:(nonnull NSString *)authorId - limit:(int)limit - offset:(int)offset { + limit:(NSUInteger)limit + offset:(NSUInteger)offset { self = [super init]; if (self) { - self.authorContentTypeStatistics = [NSMutableArray array]; + self.authorIncludeTypes = [NSMutableArray array]; self.filters = [NSMutableArray array]; self.includes = [NSMutableArray array]; @@ -48,62 +52,87 @@ - (nonnull instancetype)initWithAuthorId:(nonnull NSString *)authorId _authorId = [BVCommaUtil escape:authorId]; - self.limit = (int)limit; - self.offset = (int)offset; + self.limit = limit; + self.offset = offset; self.filters = [NSMutableArray array]; // filter the request to the given productId - BVFilter *filter = [[BVFilter alloc] initWithString:@"Id" - filterOperator:BVFilterOperatorEqualTo - values:@[ self.authorId ]]; + BVFilter *filter = [[BVFilter alloc] + initWithString:@"Id" + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + values:@[ self.authorId ]]; [self.filters addObject:filter]; } return self; } -- (nonnull instancetype)includeStatistics:(BVAuthorContentType)contentType { - if (contentType == BVAuthorContentTypeReviewComments) { +- (nonnull instancetype)includeStatistics: + (BVAuthorIncludeTypeValue)authorIncludeTypeValue { + if (authorIncludeTypeValue == BVAuthorIncludeTypeValueAuthorReviewComments) { NSAssert(NO, @"Including Review Comment Statistics is not supported " @"with an authors request."); return self; } - [self.authorContentTypeStatistics addObject:@(contentType)]; + [self.authorIncludeTypes + addObject:[BVAuthorIncludeType + includeTypeWithRawValue:authorIncludeTypeValue]]; return self; } -- (nonnull instancetype)includeContent:(BVAuthorContentType)contentType - limit:(int)limit { - BVAuthorInclude *include = - [[BVAuthorInclude alloc] initWithContentType:contentType limit:@(limit)]; +- (nonnull instancetype)includeAuthorIncludeTypeValue: + (BVAuthorIncludeTypeValue)authorIncludeTypeValue + limit:(NSUInteger)limit { + + BVInclude *include = [[BVInclude alloc] + initWithIncludeType:[BVAuthorIncludeType + includeTypeWithRawValue:authorIncludeTypeValue] + includeLimit:@(limit)]; + [self.includes addObject:include]; return self; } -- (nonnull instancetype)sortIncludedReviews:(BVSortOptionReviews)option - order:(BVSortOrder)order { +- (nonnull instancetype) +sortByReviewsSortOptionValue:(BVReviewsSortOptionValue)reviewsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue { BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionReviewUtil toString:option] - order:order]; + initWithSortOption:[BVReviewsSortOption + sortOptionWithRawValue:reviewsSortOptionValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; [self.reviewSorts addObject:sort]; return self; } -- (nonnull instancetype)sortIncludedQuestions:(BVSortOptionQuestions)option - order:(BVSortOrder)order { +- (nonnull instancetype)sortByQuestionsSortOptionValue: + (BVQuestionsSortOptionValue)questionsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue) + monotonicSortOrderValue { BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionQuestionsUtil toString:option] - order:order]; + initWithSortOption:[BVQuestionsSortOption + sortOptionWithRawValue:questionsSortOptionValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; [self.questionSorts addObject:sort]; return self; } -- (nonnull instancetype)sortIncludedAnswers:(BVSortOptionAnswers)option - order:(BVSortOrder)order { +- (nonnull instancetype) +sortByAnswersSortOptionValue:(BVAnswersSortOptionValue)answersSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue { + BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionAnswersUtil toString:option] - order:order]; + initWithSortOption:[BVAnswersSortOption + sortOptionWithRawValue:answersSortOptionValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; [self.answerSorts addObject:sort]; return self; } @@ -168,11 +197,13 @@ - (nonnull NSMutableArray *)createParams { [params addObject:[BVStringKeyValuePair pairWithKey:@"Limit" - value:[NSString stringWithFormat:@"%i", self.limit]]]; - [params addObject:[BVStringKeyValuePair - pairWithKey:@"Offset" - value:[NSString - stringWithFormat:@"%i", self.offset]]]; + value:[NSString + stringWithFormat:@"%i", (int)self.limit]]]; + [params + addObject:[BVStringKeyValuePair + pairWithKey:@"Offset" + value:[NSString + stringWithFormat:@"%i", (int)self.offset]]]; for (BVFilter *filter in self.filters) { [params addObject:[BVStringKeyValuePair @@ -180,13 +211,9 @@ - (nonnull NSMutableArray *)createParams { value:[filter toParameterString]]]; } - NSString *acts = [self statisticsToParams:self.authorContentTypeStatistics]; - if (acts && acts.length > 0) { - [params - addObject:[BVStringKeyValuePair - pairWithKey:@"Stats" - value:[self statisticsToParams: - self.authorContentTypeStatistics]]]; + NSString *stats = [self statisticsToParams:self.authorIncludeTypes]; + if (stats && stats.length > 0) { + [params addObject:[BVStringKeyValuePair pairWithKey:@"Stats" value:stats]]; } if (self.includes.count > 0) { @@ -195,14 +222,13 @@ - (nonnull NSMutableArray *)createParams { value:[self includesToParams:self.includes]]]; } - for (BVAuthorInclude *include in self.includes) { - if (include.limit != nil) { - NSString *key = [NSString - stringWithFormat:@"Limit_%@", - [BVAuthorContentTypeUtil toString:include.type]]; + for (BVInclude *include in self.includes) { + if (include.includeLimit != nil) { + NSString *key = + [NSString stringWithFormat:@"Limit_%@", [include toParameterString]]; BVStringKeyValuePair *pair = [BVStringKeyValuePair pairWithKey:key - value:[NSString stringWithFormat:@"%@", include.limit]]; + value:[NSString stringWithFormat:@"%@", include.includeLimit]]; [params addObject:pair]; } } @@ -226,11 +252,11 @@ - (nonnull NSMutableArray *)createParams { } - (nonnull NSString *)includesToParams: - (nonnull NSArray *)includes { + (nonnull NSArray *)includes { NSMutableArray *strings = [NSMutableArray array]; - for (BVAuthorInclude *include in includes) { - [strings addObject:[include toParamString]]; + for (BVInclude *include in includes) { + [strings addObject:[include toParameterString]]; } NSArray *sortedArray = [strings @@ -240,11 +266,11 @@ - (nonnull NSString *)includesToParams: } - (nonnull NSString *)statisticsToParams: - (nonnull NSArray *)statistics { - NSMutableArray *strings = [NSMutableArray array]; + (nonnull NSArray *)statistics { + NSMutableArray *strings = [NSMutableArray array]; - for (NSNumber *stat in statistics) { - [strings addObject:[BVAuthorContentTypeUtil toString:[stat intValue]]]; + for (BVAuthorIncludeType *stat in statistics) { + [strings addObject:stat.toIncludeTypeParameterString]; } NSArray *sortedArray = [strings @@ -258,7 +284,7 @@ - (nonnull BVStringKeyValuePair *)sortParams:(nonnull NSArray *)sorts NSMutableArray *strings = [NSMutableArray array]; for (BVSort *sort in sorts) { - [strings addObject:[sort toString]]; + [strings addObject:[sort toParameterString]]; } NSString *combined = [strings componentsJoinedByString:@","]; diff --git a/Pod/BVConversations/Display/Requests/BVBaseConversationsResponse.h b/BVSDK/BVConversations/Display/Requests/BVBaseConversationsResponse.h similarity index 100% rename from Pod/BVConversations/Display/Requests/BVBaseConversationsResponse.h rename to BVSDK/BVConversations/Display/Requests/BVBaseConversationsResponse.h diff --git a/Pod/BVConversations/Display/Requests/BVBaseConversationsResponse.m b/BVSDK/BVConversations/Display/Requests/BVBaseConversationsResponse.m similarity index 100% rename from Pod/BVConversations/Display/Requests/BVBaseConversationsResponse.m rename to BVSDK/BVConversations/Display/Requests/BVBaseConversationsResponse.m diff --git a/BVSDK/BVConversations/Display/Requests/BVBaseProductRequest.h b/BVSDK/BVConversations/Display/Requests/BVBaseProductRequest.h new file mode 100644 index 00000000..64caaa3a --- /dev/null +++ b/BVSDK/BVConversations/Display/Requests/BVBaseProductRequest.h @@ -0,0 +1,66 @@ +// +// BVBaseProductRequest.h +// Bazaarvoice SDK +// +// Copyright 2017 Bazaarvoice Inc. All rights reserved. +// + +#import "BVBulkProductResponse.h" +#import "BVConversationDisplay.h" +#import "BVConversationsRequest.h" + +typedef void (^ProductSearchRequestCompletionHandler)( + BVBulkProductResponse *__nonnull response); + +@interface BVBaseProductRequest : BVConversationsRequest + +/// Type of social content to include with the product request. NOTE: +/// BVProductIncludeTypeValue is only supported for statistics, no for Includes. +- (nonnull instancetype)includeProductIncludeTypeValue: + (BVProductIncludeTypeValue)productIncludeTypeValue + limit:(NSUInteger)limit; + +// Includes statistics for the included content type. +- (nonnull instancetype)includeStatistics: + (BVProductIncludeTypeValue)productIncludeTypeValue; + +/// Inclusive filter to add for included reviews. +- (nonnull instancetype) + filterOnReviewFilterValue:(BVReviewFilterValue)reviewFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value; + +/// Inclusive filter to add for included questions. +- (nonnull instancetype) + filterOnQuestionFilterValue:(BVQuestionFilterValue)questionFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value; + +@end + +@interface BVBaseProductsRequest : BVBaseProductRequest + +/// Asynchronous call to fetch data for this request. +- (void)load:(nonnull ProductSearchRequestCompletionHandler)success + failure:(nonnull ConversationsFailureHandler)failure; + +@end + +@interface BVBaseSortableProductRequest : BVBaseProductsRequest + +/// When adding reviews to include, you can add a sort parameter on the included +/// reviews. +- (nonnull instancetype) +sortByReviewsSortOptionValue:(BVReviewsSortOptionValue)reviewsSortOptionValue + monotonicSortOrderValue:(BVMonotonicSortOrderValue)monotonicSortOrderValue; +/// When adding questions to include, you can add a sort parameter on the +/// included questions. +- (nonnull instancetype)sortByQuestionsSortOptionValue: + (BVQuestionsSortOptionValue)questionsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue) + monotonicSortOrderValue; + +@end diff --git a/Pod/BVConversations/Display/Requests/BVBaseProductRequest.m b/BVSDK/BVConversations/Display/Requests/BVBaseProductRequest.m similarity index 50% rename from Pod/BVConversations/Display/Requests/BVBaseProductRequest.m rename to BVSDK/BVConversations/Display/Requests/BVBaseProductRequest.m index 3b12443c..5362cf0a 100644 --- a/Pod/BVConversations/Display/Requests/BVBaseProductRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVBaseProductRequest.m @@ -8,6 +8,14 @@ // #import "BVBaseProductRequest.h" +#import "BVBaseProductRequest_Private.h" +#import "BVMonotonicSortOrder.h" +#import "BVProductIncludeType.h" +#import "BVQuestionFilterType.h" +#import "BVQuestionsSortOption.h" +#import "BVRelationalFilterOperator.h" +#import "BVReviewFilterType.h" +#import "BVReviewsSortOption.h" @interface BVBaseProductRequest () @@ -16,31 +24,31 @@ @interface BVBaseProductRequest () @property(nonnull, nonatomic, strong, readonly) NSMutableArray *questionFilters; @property(nonnull, nonatomic, strong, readonly) - NSMutableArray *PDPContentTypeStatistics; + NSMutableArray *pdpIncludes; @property(nonnull, nonatomic, strong, readonly) - NSMutableArray *includes; + NSMutableArray *includes; @end @implementation BVBaseProductRequest - (instancetype)init { - if (self = [super init]) { + if ((self = [super init])) { _includes = [NSMutableArray array]; _reviewFilters = [NSMutableArray array]; _questionFilters = [NSMutableArray array]; - _PDPContentTypeStatistics = [NSMutableArray array]; + _pdpIncludes = [NSMutableArray array]; } return self; } - (nonnull NSString *)includesToParams: - (nonnull NSArray *)includes { - NSMutableArray *strings = [NSMutableArray array]; + (nonnull NSArray *)includes { + NSMutableArray *strings = [NSMutableArray array]; - for (PDPInclude *include in includes) { - [strings addObject:[include toParamString]]; + for (BVInclude *include in includes) { + [strings addObject:[include toParameterString]]; } NSArray *sortedArray = [strings @@ -50,11 +58,11 @@ - (nonnull NSString *)includesToParams: } - (nonnull NSString *)statisticsToParams: - (nonnull NSArray *)statistics { + (nonnull NSArray *)statistics { NSMutableArray *strings = [NSMutableArray array]; - for (NSNumber *stat in statistics) { - [strings addObject:[PDPContentTypeUtil toString:[stat intValue]]]; + for (BVInclude *stat in statistics) { + [strings addObject:[stat toParameterString]]; } NSArray *sortedArray = [strings @@ -67,40 +75,93 @@ - (nonnull NSString *)endpoint { return @"products.json"; } -- (nonnull instancetype)includeContent:(PDPContentType)contentType - limit:(int)limit { - PDPInclude *include = - [[PDPInclude alloc] initWithContentType:contentType limit:@(limit)]; +- (nonnull instancetype)includeProductIncludeTypeValue: + (BVProductIncludeTypeValue)productIncludeTypeValue + limit:(NSUInteger)limit { + BVInclude *include = [[BVInclude alloc] + initWithIncludeType:[BVProductIncludeType + includeTypeWithRawValue:productIncludeTypeValue] + includeLimit:@(limit)]; + [self.includes addObject:include]; return self; } -- (nonnull instancetype)includeStatistics:(PDPContentType)contentType { - [self.PDPContentTypeStatistics addObject:@(contentType)]; +- (nonnull instancetype)includeStatistics: + (BVProductIncludeTypeValue)productIncludeTypeValue { + + BVInclude *statToInclude = [[BVInclude alloc] + initWithIncludeType:[BVProductIncludeType + includeTypeWithRawValue:productIncludeTypeValue]]; + + [self.pdpIncludes addObject:statToInclude]; return self; } -- (nonnull instancetype)addIncludedReviewsFilter:(BVReviewFilterType)type - filterOperator: - (BVFilterOperator)filterOperator - value:(nonnull NSString *)value { +- (nonnull instancetype) + filterOnReviewFilterValue:(BVReviewFilterValue)reviewFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value { + BVReviewFilterType *includedReviewsFilterType = + [BVReviewFilterType filterTypeWithRawValue:reviewFilterValue]; + + BVRelationalFilterOperator *relationalFilterOperator = + [BVRelationalFilterOperator + filterOperatorWithRawValue:relationalFilterOperatorValue]; + + [self addIncludedReviewsFilterType:includedReviewsFilterType + relationalFilterOperator:relationalFilterOperator + value:value]; + + return self; +} + +- (nonnull instancetype) +addIncludedReviewsFilterType: + (nonnull BVReviewFilterType *)includedReviewsFilterType + relationalFilterOperator: + (nonnull BVFilterOperator *)relationalFilterOperator + value:(nonnull NSString *)value { BVFilter *filter = - [[BVFilter alloc] initWithString:[BVReviewFilterTypeUtil toString:type] - filterOperator:filterOperator - values:@[ value ]]; + [[BVFilter alloc] initWithFilterType:includedReviewsFilterType + filterOperator:relationalFilterOperator + values:@[ value ]]; [self.reviewFilters addObject:filter]; return self; } -- (nonnull instancetype)addIncludedQuestionsFilter:(BVQuestionFilterType)type - filterOperator: - (BVFilterOperator)filterOperator - value:(nonnull NSString *)value { +- (nonnull instancetype) + filterOnQuestionFilterValue:(BVQuestionFilterValue)questionFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value { + + BVQuestionFilterType *includedQuestionsFilterType = + [BVQuestionFilterType filterTypeWithRawValue:questionFilterValue]; + + BVRelationalFilterOperator *relationalFilterOperator = + [BVRelationalFilterOperator + filterOperatorWithRawValue:relationalFilterOperatorValue]; + + [self addIncludedQuestionsFilterType:includedQuestionsFilterType + relationalFilterOperator:relationalFilterOperator + value:value]; + + return self; +} + +- (nonnull instancetype) +addIncludedQuestionsFilterType: + (nonnull BVQuestionFilterType *)includedQuestionsFilterType + relationalFilterOperator: + (nonnull BVFilterOperator *)relationalFilterOperator + value:(nonnull NSString *)value { BVFilter *filter = - [[BVFilter alloc] initWithString:[BVQuestionFilterTypeUtil toString:type] - filterOperator:filterOperator - values:@[ value ]]; + [[BVFilter alloc] initWithFilterType:includedQuestionsFilterType + filterOperator:relationalFilterOperator + values:@[ value ]]; [self.questionFilters addObject:filter]; return self; @@ -127,23 +188,21 @@ - (nonnull NSMutableArray *)createParams { value:[self includesToParams:self.includes]]]; } - NSString *pcts = [self statisticsToParams:self.PDPContentTypeStatistics]; + NSString *pcts = [self statisticsToParams:self.pdpIncludes]; if (pcts && pcts.length > 0) { [params addObject:[BVStringKeyValuePair pairWithKey:@"Stats" - value:[self statisticsToParams: - self.PDPContentTypeStatistics]]]; + value:[self statisticsToParams:self.pdpIncludes]]]; } - for (PDPInclude *include in self.includes) { - if (include.limit != nil) { - NSString *key = [NSString - stringWithFormat:@"Limit_%@", - [PDPContentTypeUtil toString:include.type]]; + for (BVInclude *include in self.includes) { + if (include.includeLimit != nil) { + NSString *key = + [NSString stringWithFormat:@"Limit_%@", [include toParameterString]]; BVStringKeyValuePair *pair = [BVStringKeyValuePair pairWithKey:key - value:[NSString stringWithFormat:@"%@", include.limit]]; + value:[NSString stringWithFormat:@"%@", include.includeLimit]]; [params addObject:pair]; } } @@ -183,7 +242,7 @@ - (void)load:(ProductSearchRequestCompletionHandler)success @implementation BVBaseSortableProductRequest - (instancetype)init { - if (self = [super init]) { + if ((self = [super init])) { self.reviewSorts = [NSMutableArray array]; self.questionSorts = [NSMutableArray array]; self.answerSorts = [NSMutableArray array]; @@ -212,7 +271,7 @@ - (nonnull BVStringKeyValuePair *)sortParams:(nonnull NSArray *)sorts NSMutableArray *strings = [NSMutableArray array]; for (BVSort *sort in sorts) { - [strings addObject:[sort toString]]; + [strings addObject:[sort toParameterString]]; } NSString *combined = [strings componentsJoinedByString:@","]; @@ -220,33 +279,33 @@ - (nonnull BVStringKeyValuePair *)sortParams:(nonnull NSArray *)sorts return [BVStringKeyValuePair pairWithKey:paramKey value:combined]; } -- (nonnull instancetype)sortIncludedReviews:(BVSortOptionReviews)option - order:(BVSortOrder)order { +- (nonnull instancetype) +sortByReviewsSortOptionValue:(BVReviewsSortOptionValue)reviewsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue { BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionReviewUtil toString:option] - order:order]; + initWithSortOption:[BVReviewsSortOption + sortOptionWithRawValue:reviewsSortOptionValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; [self.reviewSorts addObject:sort]; return self; } -- (nonnull instancetype)sortIncludedQuestions:(BVSortOptionQuestions)option - order:(BVSortOrder)order { +- (nonnull instancetype)sortByQuestionsSortOptionValue: + (BVQuestionsSortOptionValue)questionsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue) + monotonicSortOrderValue { BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionQuestionsUtil toString:option] - order:order]; + initWithSortOption:[BVQuestionsSortOption + sortOptionWithRawValue:questionsSortOptionValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; [self.questionSorts addObject:sort]; return self; } -- (nonnull instancetype)sortIncludedAnswers:(BVSortOptionAnswers)option - order:(BVSortOrder)order { - BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionAnswersUtil toString:option] - order:order]; - [self.answerSorts addObject:sort]; - return self; -} - - (nonnull NSMutableArray *)createParams { NSMutableArray *params = [super createParams]; diff --git a/BVSDK/BVConversations/Display/Requests/BVBaseReviewsRequest.h b/BVSDK/BVConversations/Display/Requests/BVBaseReviewsRequest.h new file mode 100644 index 00000000..dd90e514 --- /dev/null +++ b/BVSDK/BVConversations/Display/Requests/BVBaseReviewsRequest.h @@ -0,0 +1,52 @@ +// +// BVBaseReviewsRequest.h +// Bazaarvoice SDK +// +// Copyright 2017 Bazaarvoice Inc. All rights reserved. +// + +#import "BVConversationDisplay.h" +#import "BVConversationsRequest.h" +#import "BVResponse.h" + +@interface BVBaseReviewsRequest < __covariant BVResponseType : id +> : BVConversationsRequest + + - (nonnull instancetype)__unavailable init; +- (nonnull instancetype)initWithID:(nonnull NSString *)ID + limit:(NSUInteger)limit + offset:(NSUInteger)offset; + +@property(nonatomic, assign, readonly) NSUInteger limit; +@property(nonatomic, assign, readonly) NSUInteger offset; +@property(nullable, nonatomic, strong, readonly) NSString *ID; +@property(nullable, nonatomic, strong, readonly) NSString *search; + +- (void)sendReviewResultsAnalytics:(nonnull NSArray *)reviews; +- (void)sendReviewsAnalytics:(nonnull BVReviewsResponse *)reviewsResponse; +- (nonnull instancetype)search:(nonnull NSString *)search; + +- (nonnull instancetype)includeReviewIncludeTypeValue: + (BVReviewIncludeTypeValue)reviewIncludeTypeValue; + +- (nonnull instancetype) +sortByReviewsSortOptionValue:(BVReviewsSortOptionValue)reviewsSortOptionValue + monotonicSortOrderValue:(BVMonotonicSortOrderValue)monotonicSortOrderValue; + +- (nonnull instancetype) + filterOnReviewFilterValue:(BVReviewFilterValue)reviewFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value; +- (nonnull instancetype) + filterOnReviewFilterValue:(BVReviewFilterValue)reviewFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values; + +- (void)load:(nonnull void (^)(BVResponseType __nonnull response))success + failure:(nonnull ConversationsFailureHandler)failure; + +- (nonnull BVResponseType)createResponse:(nonnull NSDictionary *)raw; + +@end diff --git a/Pod/BVConversations/Display/Requests/BVBaseReviewsRequest.m b/BVSDK/BVConversations/Display/Requests/BVBaseReviewsRequest.m similarity index 60% rename from Pod/BVConversations/Display/Requests/BVBaseReviewsRequest.m rename to BVSDK/BVConversations/Display/Requests/BVBaseReviewsRequest.m index 415d11c5..cda8b695 100644 --- a/Pod/BVConversations/Display/Requests/BVBaseReviewsRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVBaseReviewsRequest.m @@ -7,18 +7,23 @@ // #import "BVBaseReviewsRequest.h" -#import "BVAnalyticsManager.h" +#import "BVBaseReviewsRequest_Private.h" #import "BVCommaUtil.h" #import "BVCommon.h" #import "BVFilter.h" +#import "BVMonotonicSortOrder.h" +#import "BVRelationalFilterOperator.h" +#import "BVReviewFilterType.h" +#import "BVReviewIncludeType.h" +#import "BVReviewsSortOption.h" #import "BVSort.h" @implementation BVBaseReviewsRequest - (nonnull instancetype)initWithID:(nonnull NSString *)ID - limit:(int)limit - offset:(int)offset { - if (self = [super init]) { + limit:(NSUInteger)limit + offset:(NSUInteger)offset { + if ((self = [super init])) { [self initDefaultProps:ID]; _limit = limit; _offset = offset; @@ -34,9 +39,13 @@ - (void)initDefaultProps:(NSString *)ID { _includes = [NSMutableArray array]; // filter the request to the given productId - BVFilter *filter = [[BVFilter alloc] initWithString:@"ProductId" - filterOperator:BVFilterOperatorEqualTo - values:@[ _ID ]]; + BVFilter *filter = [[BVFilter alloc] + initWithFilterType:[BVReviewFilterType filterTypeWithRawValue: + BVReviewFilterValueProductId] + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + values:@[ _ID ]]; [self.filters addObject:filter]; } @@ -48,11 +57,13 @@ - (nonnull NSMutableArray *)createParams { [params addObject:[BVStringKeyValuePair pairWithKey:@"Limit" - value:[NSString stringWithFormat:@"%i", self.limit]]]; - [params addObject:[BVStringKeyValuePair - pairWithKey:@"Offset" - value:[NSString - stringWithFormat:@"%i", self.offset]]]; + value:[NSString + stringWithFormat:@"%i", (int)self.limit]]]; + [params + addObject:[BVStringKeyValuePair + pairWithKey:@"Offset" + value:[NSString + stringWithFormat:@"%i", (int)self.offset]]]; for (BVFilter *filter in self.filters) { [params addObject:[BVStringKeyValuePair @@ -60,15 +71,16 @@ - (nonnull NSMutableArray *)createParams { value:[filter toParameterString]]]; } - for (NSString *include in self.includes) { - [params - addObject:[BVStringKeyValuePair pairWithKey:@"Include" value:include]]; + for (BVIncludeType *include in self.includes) { + [params addObject:[BVStringKeyValuePair + pairWithKey:@"Include" + value:[include toIncludeTypeParameterString]]]; } if ([self.sorts count] > 0) { NSMutableArray *sortsAsStrings = [NSMutableArray array]; for (BVSort *sort in self.sorts) { - [sortsAsStrings addObject:[sort toString]]; + [sortsAsStrings addObject:[sort toParameterString]]; } NSString *allTogetherNow = [sortsAsStrings componentsJoinedByString:@","]; [params addObject:[BVStringKeyValuePair pairWithKey:@"Sort" @@ -88,10 +100,13 @@ - (nonnull instancetype)search:(nonnull NSString *)search { return self; } -- (nonnull instancetype)addInclude:(BVReviewIncludeType)include { - [self.includes addObject:[BVReviewIncludeTypeUtil toString:include]]; +- (nonnull instancetype)includeReviewIncludeTypeValue: + (BVReviewIncludeTypeValue)reviewIncludeTypeValue { + + [self.includes addObject:[BVReviewIncludeType + includeTypeWithRawValue:reviewIncludeTypeValue]]; - if (include == BVReviewIncludeTypeProducts) { + if (reviewIncludeTypeValue == BVReviewIncludeTypeValueReviewProducts) { [self addCustomDisplayParameter:@"Stats" withValue:@"Reviews"]; // Always include stats // when requesting products @@ -101,37 +116,42 @@ - (nonnull instancetype)addInclude:(BVReviewIncludeType)include { return self; } -- (nonnull instancetype)addSort:(BVSortOptionProducts)option - order:(BVSortOrder)order { - LOG_DEPRECATED_MESSAGE(@"addSort") - BVSort *sort = [[BVSort alloc] initWithOption:option order:order]; - [self.sorts addObject:sort]; - return self; -} - -- (nonnull instancetype)addReviewSort:(BVSortOptionReviews)option - order:(BVSortOrder)order { +- (nonnull instancetype) +sortByReviewsSortOptionValue:(BVReviewsSortOptionValue)reviewsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue { BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionReviewUtil toString:option] - order:order]; + initWithSortOption:[BVReviewsSortOption + sortOptionWithRawValue:reviewsSortOptionValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; [self.sorts addObject:sort]; return self; } -- (nonnull instancetype)addFilter:(BVReviewFilterType)type - filterOperator:(BVFilterOperator)filterOperator - value:(nonnull NSString *)value { - [self addFilter:type filterOperator:filterOperator values:@[ value ]]; +- (nonnull instancetype) + filterOnReviewFilterValue:(BVReviewFilterValue)reviewFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value { + [self filterOnReviewFilterValue:reviewFilterValue + relationalFilterOperatorValue:relationalFilterOperatorValue + values:@[ value ]]; return self; } -- (nonnull instancetype)addFilter:(BVReviewFilterType)type - filterOperator:(BVFilterOperator)filterOperator - values:(nonnull NSArray *)values { - BVFilter *filter = - [[BVFilter alloc] initWithString:[BVReviewFilterTypeUtil toString:type] - filterOperator:filterOperator - values:values]; +- (nonnull instancetype) + filterOnReviewFilterValue:(BVReviewFilterValue)reviewFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values { + BVFilter *filter = [[BVFilter alloc] + initWithFilterType:[BVReviewFilterType + filterTypeWithRawValue:reviewFilterValue] + filterOperator: + [BVRelationalFilterOperator + filterOperatorWithRawValue:relationalFilterOperatorValue] + values:values]; [self.filters addObject:filter]; return self; } diff --git a/BVSDK/BVConversations/Display/Requests/BVBulkProductRequest.h b/BVSDK/BVConversations/Display/Requests/BVBulkProductRequest.h new file mode 100644 index 00000000..0461a8d1 --- /dev/null +++ b/BVSDK/BVConversations/Display/Requests/BVBulkProductRequest.h @@ -0,0 +1,28 @@ +// +// BVBulkProductRequest.h +// Bazaarvoice SDK +// +// Copyright 2017 Bazaarvoice Inc. All rights reserved. +// + +#import "BVBaseProductRequest.h" +#import "BVConversationDisplay.h" + +@interface BVBulkProductRequest : BVBaseSortableProductRequest + +- (nonnull instancetype) +sortByProductsSortOptionValue:(BVProductsSortOptionValue)productsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue; +- (nonnull instancetype) + filterOnProductFilterValue:(BVProductFilterValue)productFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value; +- (nonnull instancetype) + filterOnProductFilterValue:(BVProductFilterValue)productFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values; + +@end diff --git a/BVSDK/BVConversations/Display/Requests/BVBulkProductRequest.m b/BVSDK/BVConversations/Display/Requests/BVBulkProductRequest.m new file mode 100644 index 00000000..fecd7f69 --- /dev/null +++ b/BVSDK/BVConversations/Display/Requests/BVBulkProductRequest.m @@ -0,0 +1,122 @@ +// +// BVBulkProductRequest.m +// Bazaarvoice SDK +// +// Copyright 2017 Bazaarvoice Inc. All rights reserved. +// + +#import "BVBulkProductRequest.h" +#import "BVMonotonicSortOrder.h" +#import "BVProductFilterType.h" +#import "BVProductsSortOption.h" +#import "BVRelationalFilterOperator.h" + +@interface BVBulkProductRequest () + +@property(nonnull) NSMutableArray *sorts; +@property(nonnull, nonatomic, strong) NSMutableArray *filters; + +@end + +@implementation BVBulkProductRequest + +- (instancetype)init { + if ((self = [super init])) { + _sorts = [NSMutableArray new]; + _filters = [NSMutableArray new]; + } + + return self; +} + +- (nonnull instancetype) +sortByProductsSortOptionValue:(BVProductsSortOptionValue)productsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue { + BVSort *sort = [[BVSort alloc] + initWithSortOption:[BVProductsSortOption + sortOptionWithRawValue:monotonicSortOrderValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; + [self.sorts addObject:sort]; + return self; +} + +- (void)load:(ProductSearchRequestCompletionHandler)success + failure:(ConversationsFailureHandler)failure { + [self loadContent:self + completion:^(NSDictionary *__nonnull response) { + BVBulkProductResponse *productsResponse = + [[BVBulkProductResponse alloc] initWithApiResponse:response]; + // invoke success callback on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + success(productsResponse); + }); + + } + failure:failure]; +} + +- (nonnull NSMutableArray *)createParams { + NSMutableArray *params = [super createParams]; + if ([self.sorts count] > 0) { + NSMutableArray *sortsAsStrings = [NSMutableArray array]; + for (BVSort *sort in self.sorts) { + [sortsAsStrings addObject:[sort toParameterString]]; + } + NSString *allTogetherNow = [sortsAsStrings componentsJoinedByString:@","]; + [params addObject:[BVStringKeyValuePair pairWithKey:@"Sort" + value:allTogetherNow]]; + } + + for (BVFilter *filter in self.filters) { + [params addObject:[BVStringKeyValuePair + pairWithKey:@"Filter" + value:[filter toParameterString]]]; + } + + return params; +} + +- (nonnull instancetype) + filterOnProductFilterValue:(BVProductFilterValue)productFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value { + [self filterOnProductFilterValue:productFilterValue + relationalFilterOperatorValue:relationalFilterOperatorValue + values:@[ value ]]; + return self; +} + +- (nonnull instancetype) + filterOnProductFilterValue:(BVProductFilterValue)productFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values { + BVProductFilterType *productFilterType = + [BVProductFilterType filterTypeWithRawValue:productFilterValue]; + + BVRelationalFilterOperator *relationalFilterOperator = + [BVRelationalFilterOperator + filterOperatorWithRawValue:relationalFilterOperatorValue]; + + [self addProductFilterType:productFilterType + relationalFilterOperator:relationalFilterOperator + values:values]; + return self; +} + +- (nonnull instancetype) + addProductFilterType:(nonnull BVProductFilterType *)productFilterType +relationalFilterOperator:(nonnull BVFilterOperator *)relationalFilterOperator + values:(nonnull NSArray *)values { + BVFilter *filter = + [[BVFilter alloc] initWithFilterType:productFilterType + filterOperator:relationalFilterOperator + values:values]; + [self.filters addObject:filter]; + return self; +} + +@end diff --git a/Pod/BVConversations/Display/Requests/BVBulkRatingsRequest.h b/BVSDK/BVConversations/Display/Requests/BVBulkRatingsRequest.h similarity index 58% rename from Pod/BVConversations/Display/Requests/BVBulkRatingsRequest.h rename to BVSDK/BVConversations/Display/Requests/BVBulkRatingsRequest.h index 7e2b923a..800f3271 100644 --- a/Pod/BVConversations/Display/Requests/BVBulkRatingsRequest.h +++ b/BVSDK/BVConversations/Display/Requests/BVBulkRatingsRequest.h @@ -5,19 +5,11 @@ // Copyright © 2016 Bazaarvoice. All rights reserved. // -#import "BVBulkRatingsFilterType.h" #import "BVBulkRatingsResponse.h" +#import "BVConversationDisplay.h" #import "BVConversationsRequest.h" -#import "BVFilterOperator.h" -#import "PDPInclude.h" #import -typedef NS_ENUM(NSInteger, BulkRatingsStatsType) { - BulkRatingsStatsTypeReviews, - BulkRatingsStatsTypeNativeReviews, - BulkRatingsStatsTypeAll -}; - typedef void (^BulkRatingsSuccessHandler)( BVBulkRatingsResponse *__nonnull response); @@ -30,16 +22,23 @@ typedef void (^BulkRatingsSuccessHandler)( - (nonnull instancetype) initWithProductIds:(nonnull NSArray *)productIds - statistics:(enum BulkRatingsStatsType)statistics; + statistics:(BVBulkRatingIncludeTypeValue)bulkRatingIncludeTypeValue; - (nonnull instancetype)__unavailable init; - (nonnull NSString *)endpoint; - (void)load: (nonnull void (^)(BVBulkRatingsResponse *__nonnull response))success failure:(nonnull ConversationsFailureHandler)failure; -- (nonnull instancetype)addFilter:(BVBulkRatingsFilterType)type - filterOperator:(BVFilterOperator)filterOperator - values:(nonnull NSArray *)values; +- (nonnull instancetype) +filterOnBulkRatingFilterValue:(BVBulkRatingFilterValue)bulkRatingFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value; +- (nonnull instancetype) +filterOnBulkRatingFilterValue:(BVBulkRatingFilterValue)bulkRatingFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values; - (nonnull NSMutableArray *)createParams; @end diff --git a/BVSDK/BVConversations/Display/Requests/BVBulkRatingsRequest.m b/BVSDK/BVConversations/Display/Requests/BVBulkRatingsRequest.m new file mode 100644 index 00000000..bca7cc94 --- /dev/null +++ b/BVSDK/BVConversations/Display/Requests/BVBulkRatingsRequest.m @@ -0,0 +1,159 @@ +// +// BVBulkRatingsRequest.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVBulkRatingsRequest.h" +#import "BVBulkRatingFilterType.h" +#import "BVBulkRatingIncludeType.h" +#import "BVCommaUtil.h" +#import "BVLogger.h" +#import "BVRelationalFilterOperator.h" + +@interface BVBulkRatingsRequest () + +@property(nonnull) NSArray *productIds; +@property(nonnull) NSMutableArray *statistics; +@property(nonnull) NSMutableArray *filters; + +@end + +@implementation BVBulkRatingsRequest + +- (nonnull NSString *)endpoint { + return @"statistics.json"; +} + +- (nonnull instancetype) +initWithProductIds:(nonnull NSArray *)productIds + statistics:(BVBulkRatingIncludeTypeValue)bulkRatingIncludeTypeValue { + self = [super init]; + if (self) { + self.productIds = [BVCommaUtil escapeMultiple:productIds]; + + /// This is a single element array for now... + self.statistics = [NSMutableArray arrayWithArray:@[ + [BVBulkRatingIncludeType + includeTypeWithRawValue:bulkRatingIncludeTypeValue] + ]]; + + self.filters = [NSMutableArray array]; + BVFilter *filter = [[BVFilter alloc] + initWithFilterType:[BVBulkRatingFilterType + filterTypeWithRawValue: + BVBulkRatingFilterValueBulkRatingProductId] + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + values:productIds]; + [self.filters addObject:filter]; + } + return self; +} + +- (void)load: + (nonnull void (^)(BVBulkRatingsResponse *__nonnull response))success + failure:(nonnull ConversationsFailureHandler)failure { + if ([self.productIds count] > 100) { + // invalid request + [self sendError:[self tooManyProductsError:self.productIds] + failureCallback:failure]; + } else { + [self loadBulkRatings:self completion:success failure:failure]; + } +} + +- (void) +loadBulkRatings:(nonnull BVConversationsRequest *)request + completion: + (nonnull void (^)(BVBulkRatingsResponse *__nonnull response))completion + failure: + (nonnull void (^)(NSArray *__nonnull errors))failure { + [self loadContent:request + completion:^(NSDictionary *__nonnull response) { + + BVBulkRatingsResponse *bulkRatingsResponse = + [[BVBulkRatingsResponse alloc] initWithApiResponse:response]; + // invoke success callback on main thread + dispatch_async(dispatch_get_main_queue(), ^{ + completion(bulkRatingsResponse); + }); + + } + failure:failure]; +} + +- (nonnull instancetype) +addBulkRatingsFilterType:(nonnull BVBulkRatingFilterType *)bulkRatingsFilterType +relationalFilterOperator: + (nonnull BVRelationalFilterOperator *)relationalFilterOperator + values:(nonnull NSArray *)values { + BVFilter *filter = + [[BVFilter alloc] initWithFilterType:bulkRatingsFilterType + filterOperator:relationalFilterOperator + values:values]; + [self.filters addObject:filter]; + return self; +} + +- (nonnull instancetype) +filterOnBulkRatingFilterValue:(BVBulkRatingFilterValue)bulkRatingFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value { + [self filterOnBulkRatingFilterValue:bulkRatingFilterValue + relationalFilterOperatorValue:relationalFilterOperatorValue + values:@[ value ]]; + return self; +} + +- (nonnull instancetype) +filterOnBulkRatingFilterValue:(BVBulkRatingFilterValue)bulkRatingFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values { + BVBulkRatingFilterType *bulkRatingsFilterType = + [BVBulkRatingFilterType filterTypeWithRawValue:bulkRatingFilterValue]; + BVRelationalFilterOperator *relationalFilterOperator = + [BVRelationalFilterOperator + filterOperatorWithRawValue:relationalFilterOperatorValue]; + [self addBulkRatingsFilterType:bulkRatingsFilterType + relationalFilterOperator:relationalFilterOperator + values:values]; + return self; +} + +- (nonnull NSMutableArray *)createParams { + NSMutableArray *params = [super createParams]; + + [params addObject:[BVStringKeyValuePair + pairWithKey:@"Stats" + value:[self statsToString:self.statistics]]]; + + for (BVFilter *filter in self.filters) { + [params addObject:[BVStringKeyValuePair + pairWithKey:@"Filter" + value:[filter toParameterString]]]; + } + + return params; +} + +- (nonnull NSString *)statsToString: + (NSMutableArray *)statistics { + NSMutableArray *statisticsStringArray = + [NSMutableArray arrayWithCapacity:statistics.count]; + + for (BVBulkRatingIncludeType *stat in statistics) { + [statisticsStringArray addObject:[stat toIncludeTypeParameterString]]; + } + + NSArray *sortedArray = [statisticsStringArray + sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + + return [sortedArray componentsJoinedByString:@","]; +} + +@end diff --git a/Pod/BVConversations/Display/Requests/BVCommentsRequest.h b/BVSDK/BVConversations/Display/Requests/BVCommentsRequest.h similarity index 55% rename from Pod/BVConversations/Display/Requests/BVCommentsRequest.h rename to BVSDK/BVConversations/Display/Requests/BVCommentsRequest.h index dbac15db..bf5eb220 100644 --- a/Pod/BVConversations/Display/Requests/BVCommentsRequest.h +++ b/BVSDK/BVConversations/Display/Requests/BVCommentsRequest.h @@ -7,13 +7,9 @@ // Copyright © 2017 Bazaarvoice. All rights reserved. // -#import "BVCommentFilterType.h" -#import "BVCommentIncludeType.h" #import "BVCommentsResponse.h" +#import "BVConversationDisplay.h" #import "BVConversationsRequest.h" -#import "BVFilter.h" -#import "BVSort.h" -#import "BVSortOptionsComments.h" @interface BVCommentsRequest : BVConversationsRequest @@ -22,11 +18,6 @@ @property(nullable, readonly) NSString *commentId; @property(nonatomic, assign, readonly) UInt16 limit; @property(nonatomic, assign, readonly) UInt16 offset; -@property(nonnull, nonatomic, strong, readonly) NSMutableArray *sorts; -@property(nonnull, nonatomic, strong, readonly) - NSMutableArray *filters; -@property(nonnull, nonatomic, strong, readonly) - NSMutableArray *includes; - (nonnull instancetype)initWithProductId:(nonnull NSString *)productId andReviewId:(nonnull NSString *)reviewId @@ -37,18 +28,25 @@ - (nonnull instancetype)__unavailable init; -- (nonnull instancetype)addCommentSort:(BVSortOptionComments)option - order:(BVSortOrder)order; - -- (nonnull instancetype)addFilter:(BVCommentFilterType)type - filterOperator:(BVFilterOperator)filterOperator - value:(nonnull NSString *)value; - -- (nonnull instancetype)addFilter:(BVCommentFilterType)type - filterOperator:(BVFilterOperator)filterOperator - values:(nonnull NSArray *)values; - -- (nonnull instancetype)addInclude:(BVCommentIncludeType)include; +- (nonnull instancetype) +sortByCommentsSortOptionValue:(BVCommentsSortOptionValue)commentsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue; + +- (nonnull instancetype) + filterOnCommentFilterValue:(BVCommentFilterValue)commentFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value; + +- (nonnull instancetype) + filterOnCommentFilterValue:(BVCommentFilterValue)commentFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values; + +- (nonnull instancetype)includeCommentIncludeTypeValue: + (BVCommentIncludeTypeValue)commentIncludeTypeValue; /// Make an asynch http request to fethre the Author's profile data. See the /// BVAuthorResponse model for availble fields. diff --git a/Pod/BVConversations/Display/Requests/BVCommentsRequest.m b/BVSDK/BVConversations/Display/Requests/BVCommentsRequest.m similarity index 55% rename from Pod/BVConversations/Display/Requests/BVCommentsRequest.m rename to BVSDK/BVConversations/Display/Requests/BVCommentsRequest.m index 9ac9c3e8..14c7d583 100644 --- a/Pod/BVConversations/Display/Requests/BVCommentsRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVCommentsRequest.m @@ -8,8 +8,14 @@ // #import "BVCommentsRequest.h" +#import "BVCommentFilterType.h" +#import "BVCommentIncludeType.h" +#import "BVCommentsRequest_Private.h" +#import "BVCommentsSortOption.h" #import "BVImpressionEvent.h" +#import "BVMonotonicSortOrder.h" #import "BVPixel.h" +#import "BVRelationalFilterOperator.h" @implementation BVCommentsRequest @@ -114,19 +120,32 @@ - (nonnull NSMutableArray *)createParams { @"the supplied initiaizers."); if (self.productId) { - [params - addObject:[BVStringKeyValuePair - pairWithKey:@"Filter" - value:[NSString stringWithFormat:@"ProductId:%@", - self.productId]]]; + + BVFilter *commentProductIdFilter = [[BVFilter alloc] + initWithFilterType: + [BVCommentFilterType + filterTypeWithRawValue:BVCommentFilterValueCommentProductId] + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + value:self.productId]; + + [self.filters addObject:commentProductIdFilter]; } if (self.reviewId) { - [params - addObject:[BVStringKeyValuePair - pairWithKey:@"Filter" - value:[NSString stringWithFormat:@"ReviewId:%@", - self.reviewId]]]; + + BVFilter *commentReviewIdFilter = [[BVFilter alloc] + initWithFilterType: + [BVCommentFilterType + filterTypeWithRawValue:BVCommentFilterValueCommentReviewId] + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + value:self.reviewId]; + + [self.filters addObject:commentReviewIdFilter]; + [params addObject:[BVStringKeyValuePair pairWithKey:@"Limit" value:[NSString @@ -138,13 +157,16 @@ - (nonnull NSMutableArray *)createParams { } if (self.commentId) { - BVFilter *commentIdFilter = - [[BVFilter alloc] initWithType:BVProductFilterTypeId - filterOperator:BVFilterOperatorEqualTo - value:self.commentId]; - [params addObject:[BVStringKeyValuePair - pairWithKey:@"Filter" - value:[commentIdFilter toParameterString]]]; + BVFilter *commentIdFilter = [[BVFilter alloc] + initWithFilterType: + [BVCommentFilterType + filterTypeWithRawValue:BVCommentFilterValueCommentId] + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + value:self.commentId]; + + [self.filters addObject:commentIdFilter]; } for (BVFilter *filter in self.filters) { @@ -166,44 +188,76 @@ - (nonnull NSMutableArray *)createParams { return params; } -- (nonnull instancetype)addCommentSort:(BVSortOptionComments)option - order:(BVSortOrder)order { +- (nonnull instancetype) +sortByCommentsSortOptionValue:(BVCommentsSortOptionValue)commentsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue { BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionsCommentUtil toString:option] - order:order]; + initWithSortOption:[BVCommentsSortOption + sortOptionWithRawValue:commentsSortOptionValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; [self.sorts addObject:sort]; return self; } -- (nonnull instancetype)addFilter:(BVCommentFilterType)type - filterOperator:(BVFilterOperator)filterOperator - value:(nonnull NSString *)value { - [self addFilter:type filterOperator:filterOperator values:@[ value ]]; +- (nonnull instancetype) + filterOnCommentFilterValue:(BVCommentFilterValue)commentFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value { + [self filterOnCommentFilterValue:commentFilterValue + relationalFilterOperatorValue:relationalFilterOperatorValue + values:@[ value ]]; return self; } -- (nonnull instancetype)addInclude:(BVCommentIncludeType)include { - [self.includes addObject:[BVCommentIncludeTypeUtil toString:include]]; +- (nonnull instancetype) + filterOnCommentFilterValue:(BVCommentFilterValue)commentFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values { + BVCommentFilterType *commentsRequestFilterType = + [BVCommentFilterType filterTypeWithRawValue:commentFilterValue]; + BVRelationalFilterOperator *relationalFilterOperatorType = + [BVRelationalFilterOperator + filterOperatorWithRawValue:relationalFilterOperatorValue]; + [self addCommentsRequestFilterType:commentsRequestFilterType + relationalFilterOperator:relationalFilterOperatorType + values:values]; return self; } -- (nonnull instancetype)addFilter:(BVCommentFilterType)type - filterOperator:(BVFilterOperator)filterOperator - values:(nonnull NSArray *)values { +- (nonnull instancetype) +addCommentsRequestFilterType: + (nonnull BVCommentFilterType *)commentsRequestFilterType + relationalFilterOperator: + (nonnull BVRelationalFilterOperator *)filterOperator + values:(nonnull NSArray *)values { BVFilter *filter = - [[BVFilter alloc] initWithString:[BVCommentFilterTypeUtil toString:type] - filterOperator:filterOperator - values:values]; + [[BVFilter alloc] initWithFilterType:commentsRequestFilterType + filterOperator:filterOperator + values:values]; [self.filters addObject:filter]; return self; } +- (nonnull instancetype)includeCommentIncludeTypeValue: + (BVCommentIncludeTypeValue)commentIncludeTypeValue { + + BVCommentIncludeType *commentIncludeType = + [BVCommentIncludeType includeTypeWithRawValue:commentIncludeTypeValue]; + + [self.includes addObject:commentIncludeType]; + return self; +} + - (nonnull BVStringKeyValuePair *)sortParams:(nonnull NSArray *)sorts withKey:(NSString *)paramKey { NSMutableArray *strings = [NSMutableArray array]; for (BVSort *sort in sorts) { - [strings addObject:[sort toString]]; + [strings addObject:[sort toParameterString]]; } NSString *combined = [strings componentsJoinedByString:@","]; @@ -211,8 +265,17 @@ - (nonnull BVStringKeyValuePair *)sortParams:(nonnull NSArray *)sorts return [BVStringKeyValuePair pairWithKey:paramKey value:combined]; } -- (nonnull NSString *)includesToParams:(nonnull NSArray *)includes { - NSArray *sortedArray = [includes +- (nonnull NSString *)includesToParams: + (nonnull NSArray *)includes { + + NSMutableArray *includesStringArray = + [NSMutableArray arrayWithCapacity:includes.count]; + + for (BVIncludeType *include in includes) { + [includesStringArray addObject:[include toIncludeTypeParameterString]]; + } + + NSArray *sortedArray = [includesStringArray sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; return [sortedArray componentsJoinedByString:@","]; diff --git a/Pod/BVConversations/Display/Requests/BVConversationsRequest.h b/BVSDK/BVConversations/Display/Requests/BVConversationsRequest.h similarity index 87% rename from Pod/BVConversations/Display/Requests/BVConversationsRequest.h rename to BVSDK/BVConversations/Display/Requests/BVConversationsRequest.h index 9e8449aa..4a0ce4a6 100644 --- a/Pod/BVConversations/Display/Requests/BVConversationsRequest.h +++ b/BVSDK/BVConversations/Display/Requests/BVConversationsRequest.h @@ -22,10 +22,6 @@ typedef void (^ConversationsFailureHandler)( - (nonnull NSString *)endpoint; + (nonnull NSString *)commonEndpoint; -- (nonnull instancetype)addAdditionalField:(nonnull NSString *)fieldName - value:(nonnull NSString *)value - __deprecated_msg("use addCustomDisplayParameter instead."); - /** This method adds extra user provided query parameters to a submission request, and will be urlencoded. diff --git a/Pod/BVConversations/Display/Requests/BVConversationsRequest.m b/BVSDK/BVConversations/Display/Requests/BVConversationsRequest.m similarity index 91% rename from Pod/BVConversations/Display/Requests/BVConversationsRequest.m rename to BVSDK/BVConversations/Display/Requests/BVConversationsRequest.m index b33b7b8b..a8864eaf 100644 --- a/Pod/BVConversations/Display/Requests/BVConversationsRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVConversationsRequest.m @@ -6,9 +6,9 @@ // #import "BVConversationsRequest.h" -#import "BVAnalyticsManager.h" #import "BVConversationsErrorResponse.h" #import "BVDiagnosticHelpers.h" +#import "BVNetworkingManager.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" @@ -19,6 +19,10 @@ @interface BVConversationsRequest () @implementation BVConversationsRequest +- (instancetype)init { + return ((self = [super init])); +} + - (nonnull NSMutableArray *)createParams { NSMutableArray *params = [NSMutableArray array]; @@ -108,7 +112,16 @@ + (nonnull NSString *)commonEndpoint { [[BVLogger sharedLogger] verbose:[NSString stringWithFormat:@"GET: %@", urlRequest.URL]]; - NSURLSession *session = [NSURLSession sharedSession]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURLSessionDataTask *task = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *__nullable data, @@ -125,6 +138,14 @@ + (nonnull NSString *)commonEndpoint { // start the request [task resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:task + fromBVObject:self + withURLSession:session]; + } } - (void) diff --git a/Pod/BVConversations/Display/Requests/BVProductDisplayPageRequest.h b/BVSDK/BVConversations/Display/Requests/BVProductDisplayPageRequest.h similarity index 97% rename from Pod/BVConversations/Display/Requests/BVProductDisplayPageRequest.h rename to BVSDK/BVConversations/Display/Requests/BVProductDisplayPageRequest.h index 17723163..b0718054 100644 --- a/Pod/BVConversations/Display/Requests/BVProductDisplayPageRequest.h +++ b/BVSDK/BVConversations/Display/Requests/BVProductDisplayPageRequest.h @@ -6,7 +6,6 @@ // #import "BVBaseProductRequest.h" -#import "PDPContentType.h" #import typedef void (^ProductRequestCompletionHandler)( diff --git a/Pod/BVConversations/Display/Requests/BVProductDisplayPageRequest.m b/BVSDK/BVConversations/Display/Requests/BVProductDisplayPageRequest.m similarity index 89% rename from Pod/BVConversations/Display/Requests/BVProductDisplayPageRequest.m rename to BVSDK/BVConversations/Display/Requests/BVProductDisplayPageRequest.m index 8d3ca383..1c955356 100644 --- a/Pod/BVConversations/Display/Requests/BVProductDisplayPageRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVProductDisplayPageRequest.m @@ -6,11 +6,10 @@ // #import "BVProductDisplayPageRequest.h" -#import "BVAnalyticsManager.h" #import "BVCommaUtil.h" -#import "BVFilterOperator.h" #import "BVPixel.h" #import "BVProductFilterType.h" +#import "BVRelationalFilterOperator.h" @interface BVProductDisplayPageRequest () @@ -104,10 +103,15 @@ - (nonnull NSMutableArray *)createParams { NSMutableArray *params = [super createParams]; BVFilter *filter = [[BVFilter alloc] - initWithString:[BVProductFilterTypeUtil toString:BVProductFilterTypeId] - filterOperator:BVFilterOperatorEqualTo - values:@[ self.productId ]]; + initWithFilterType:[BVProductFilterType filterTypeWithRawValue: + BVProductFilterValueProductId] + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + values:@[ self.productId ]]; + NSString *filterValue = [filter toParameterString]; + [params addObject:[BVStringKeyValuePair pairWithKey:@"Filter" value:filterValue]]; diff --git a/Pod/BVConversations/Display/Requests/BVProductTextSearchRequest.h b/BVSDK/BVConversations/Display/Requests/BVProductTextSearchRequest.h similarity index 100% rename from Pod/BVConversations/Display/Requests/BVProductTextSearchRequest.h rename to BVSDK/BVConversations/Display/Requests/BVProductTextSearchRequest.h diff --git a/Pod/BVConversations/Display/Requests/BVProductTextSearchRequest.m b/BVSDK/BVConversations/Display/Requests/BVProductTextSearchRequest.m similarity index 95% rename from Pod/BVConversations/Display/Requests/BVProductTextSearchRequest.m rename to BVSDK/BVConversations/Display/Requests/BVProductTextSearchRequest.m index b520b734..584a4af5 100644 --- a/Pod/BVConversations/Display/Requests/BVProductTextSearchRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVProductTextSearchRequest.m @@ -17,7 +17,7 @@ @interface BVProductTextSearchRequest () @implementation BVProductTextSearchRequest - (instancetype)initWithSearchText:(NSString *)searchText { - if (self = [super init]) { + if ((self = [super init])) { _searchText = searchText; } diff --git a/Pod/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.h b/BVSDK/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.h similarity index 51% rename from Pod/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.h rename to BVSDK/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.h index 63ac794c..6d953438 100644 --- a/Pod/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.h +++ b/BVSDK/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.h @@ -5,14 +5,9 @@ // Copyright © 2016 Bazaarvoice. All rights reserved. // +#import "BVConversationDisplay.h" #import "BVConversationsRequest.h" -#import "BVFilterOperator.h" -#import "BVQuestionFilterType.h" #import "BVQuestionsAndAnswersResponse.h" -#import "BVSort.h" -#import "BVSortOptionAnswers.h" -#import "BVSortOptionQuestions.h" -#import typedef void (^QuestionsAndAnswersSuccessHandler)( BVQuestionsAndAnswersResponse *__nonnull response); @@ -28,23 +23,28 @@ typedef void (^QuestionsAndAnswersSuccessHandler)( @property(nonnull, readonly) NSString *productId; - (nonnull instancetype)initWithProductId:(nonnull NSString *)productId - limit:(int)limit - offset:(int)offset; + limit:(NSUInteger)limit + offset:(NSUInteger)offset; - (nonnull instancetype)__unavailable init; -- (nonnull instancetype)addSort:(BVSortOptionProducts)option - order:(BVSortOrder)order - __deprecated_msg("use sortQuestions and sortAnswers instead"); +- (nonnull instancetype)sortByQuestionsSortOptionValue: + (BVQuestionsSortOptionValue)questionsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue) + monotonicSortOrderValue; -- (nonnull instancetype)addQuestionSort:(BVSortOptionQuestions)option - order:(BVSortOrder)order; +- (nonnull instancetype) + filterOnQuestionFilterValue:(BVQuestionFilterValue)questionFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value; + +- (nonnull instancetype) + filterOnQuestionFilterValue:(BVQuestionFilterValue)questionFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values; -- (nonnull instancetype)addFilter:(BVQuestionFilterType)type - filterOperator:(BVFilterOperator)filterOperator - value:(nonnull NSString *)value; -- (nonnull instancetype)addFilter:(BVQuestionFilterType)type - filterOperator:(BVFilterOperator)filterOperator - values:(nonnull NSArray *)values; - (nonnull instancetype)search:(nonnull NSString *)search; - (void)load:(nonnull void (^)( diff --git a/Pod/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.m b/BVSDK/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.m similarity index 58% rename from Pod/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.m rename to BVSDK/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.m index e9e5bdad..648b0b7b 100644 --- a/Pod/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVQuestionsAndAnswersRequest.m @@ -8,12 +8,15 @@ #import "BVQuestionsAndAnswersRequest.h" #import "BVCommaUtil.h" #import "BVCommon.h" -#import "BVFilter.h" +#import "BVMonotonicSortOrder.h" +#import "BVQuestionFilterType.h" +#import "BVQuestionsSortOption.h" +#import "BVRelationalFilterOperator.h" @interface BVQuestionsAndAnswersRequest () -@property int limit; -@property int offset; +@property NSUInteger limit; +@property NSUInteger offset; @property(nullable) NSString *search; @property(nonnull) NSMutableArray *filters; @property(nonnull) NSMutableArray *sorts; @@ -23,57 +26,85 @@ @interface BVQuestionsAndAnswersRequest () @implementation BVQuestionsAndAnswersRequest - (nonnull instancetype)initWithProductId:(nonnull NSString *)productId - limit:(int)limit - offset:(int)offset { + limit:(NSUInteger)limit + offset:(NSUInteger)offset { self = [super init]; if (self) { _productId = [BVCommaUtil escape:productId]; - self.limit = (int)limit; - self.offset = (int)offset; + self.limit = limit; + self.offset = offset; self.filters = [NSMutableArray array]; self.sorts = [NSMutableArray array]; // filter the request to the given productId - BVFilter *filter = [[BVFilter alloc] initWithString:@"ProductId" - filterOperator:BVFilterOperatorEqualTo - values:@[ self.productId ]]; + BVFilter *filter = [[BVFilter alloc] + initWithFilterType: + [BVQuestionFilterType + filterTypeWithRawValue:BVQuestionFilterValueQuestionProductId] + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + values:@[ self.productId ]]; [self.filters addObject:filter]; } return self; } -- (nonnull instancetype)addSort:(BVSortOptionProducts)option - order:(BVSortOrder)order { - LOG_DEPRECATED_MESSAGE(@"addSort") - BVSort *sort = [[BVSort alloc] initWithOption:option order:order]; +- (nonnull instancetype)sortByQuestionsSortOptionValue: + (BVQuestionsSortOptionValue)questionsSortOptionValue + monotonicSortOrderValue: + (BVMonotonicSortOrderValue) + monotonicSortOrderValue { + BVSort *sort = [[BVSort alloc] + initWithSortOption:[BVQuestionsSortOption + sortOptionWithRawValue:questionsSortOptionValue] + sortOrder:[BVMonotonicSortOrder + sortOrderWithRawValue:monotonicSortOrderValue]]; [self.sorts addObject:sort]; return self; } -- (nonnull instancetype)addQuestionSort:(BVSortOptionQuestions)option - order:(BVSortOrder)order { - BVSort *sort = [[BVSort alloc] - initWithOptionString:[BVSortOptionQuestionsUtil toString:option] - order:order]; - [self.sorts addObject:sort]; +- (nonnull instancetype) + filterOnQuestionFilterValue:(BVQuestionFilterValue)questionFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + value:(nonnull NSString *)value { + [self filterOnQuestionFilterValue:questionFilterValue + relationalFilterOperatorValue:relationalFilterOperatorValue + values:@[ value ]]; return self; } -- (nonnull instancetype)addFilter:(BVQuestionFilterType)type - filterOperator:(BVFilterOperator)filterOperator - value:(nonnull NSString *)value { - [self addFilter:type filterOperator:filterOperator values:@[ value ]]; +- (nonnull instancetype) + filterOnQuestionFilterValue:(BVQuestionFilterValue)questionFilterValue +relationalFilterOperatorValue: + (BVRelationalFilterOperatorValue)relationalFilterOperatorValue + values:(nonnull NSArray *)values { + + BVQuestionFilterType *questionFilterType = + [BVQuestionFilterType filterTypeWithRawValue:questionFilterValue]; + + BVRelationalFilterOperator *relationalFilterOperator = + [BVRelationalFilterOperator + filterOperatorWithRawValue:relationalFilterOperatorValue]; + + [self addQuestionFilterType:questionFilterType + relationalFilterOperator:relationalFilterOperator + values:values]; + return self; } -- (nonnull instancetype)addFilter:(BVQuestionFilterType)type - filterOperator:(BVFilterOperator)filterOperator - values:(nonnull NSArray *)values { +- (nonnull instancetype) + addQuestionFilterType:(nonnull BVQuestionFilterType *)questionFilterType +relationalFilterOperator: + (nonnull BVRelationalFilterOperator *)relationalFilterOperator + values:(nonnull NSArray *)values { BVFilter *filter = - [[BVFilter alloc] initWithString:[BVQuestionFilterTypeUtil toString:type] - filterOperator:filterOperator - values:values]; + [[BVFilter alloc] initWithFilterType:questionFilterType + filterOperator:relationalFilterOperator + values:values]; [self.filters addObject:filter]; return self; } @@ -160,11 +191,13 @@ - (nonnull NSMutableArray *)createParams { [params addObject:[BVStringKeyValuePair pairWithKey:@"Limit" - value:[NSString stringWithFormat:@"%i", self.limit]]]; - [params addObject:[BVStringKeyValuePair - pairWithKey:@"Offset" - value:[NSString - stringWithFormat:@"%i", self.offset]]]; + value:[NSString + stringWithFormat:@"%i", (int)self.limit]]]; + [params + addObject:[BVStringKeyValuePair + pairWithKey:@"Offset" + value:[NSString + stringWithFormat:@"%i", (int)self.offset]]]; for (BVFilter *filter in self.filters) { [params addObject:[BVStringKeyValuePair @@ -175,7 +208,7 @@ - (nonnull NSMutableArray *)createParams { if ([self.sorts count] > 0) { NSMutableArray *sortsAsStrings = [NSMutableArray array]; for (BVSort *sort in self.sorts) { - [sortsAsStrings addObject:[sort toString]]; + [sortsAsStrings addObject:[sort toParameterString]]; } NSString *allTogetherNow = [sortsAsStrings componentsJoinedByString:@","]; [params addObject:[BVStringKeyValuePair pairWithKey:@"Sort" diff --git a/Pod/BVConversations/Display/Requests/BVReviewsRequest.h b/BVSDK/BVConversations/Display/Requests/BVReviewsRequest.h similarity index 76% rename from Pod/BVConversations/Display/Requests/BVReviewsRequest.h rename to BVSDK/BVConversations/Display/Requests/BVReviewsRequest.h index fdd91de8..2ec81fda 100644 --- a/Pod/BVConversations/Display/Requests/BVReviewsRequest.h +++ b/BVSDK/BVConversations/Display/Requests/BVReviewsRequest.h @@ -6,11 +6,7 @@ // #import "BVBaseReviewsRequest.h" -#import "BVFilterOperator.h" -#import "BVReviewFilterType.h" #import "BVReviewsResponse.h" -#import "BVSort.h" -#import "BVSortOptionReviews.h" #import typedef void (^ReviewRequestCompletionHandler)( @@ -26,8 +22,8 @@ typedef void (^ReviewRequestCompletionHandler)( @property(nonnull, readonly) NSString *productId; - (nonnull instancetype)initWithProductId:(nonnull NSString *)productId - limit:(int)limit - offset:(int)offset; + limit:(NSUInteger)limit + offset:(NSUInteger)offset; - (nonnull instancetype)__unavailable init; @end diff --git a/Pod/BVConversations/Display/Requests/BVReviewsRequest.m b/BVSDK/BVConversations/Display/Requests/BVReviewsRequest.m similarity index 82% rename from Pod/BVConversations/Display/Requests/BVReviewsRequest.m rename to BVSDK/BVConversations/Display/Requests/BVReviewsRequest.m index 50354ed9..9431c176 100644 --- a/Pod/BVConversations/Display/Requests/BVReviewsRequest.m +++ b/BVSDK/BVConversations/Display/Requests/BVReviewsRequest.m @@ -14,8 +14,8 @@ @implementation BVReviewsRequest - (nonnull instancetype)initWithProductId:(nonnull NSString *)productId - limit:(int)limit - offset:(int)offset { + limit:(NSUInteger)limit + offset:(NSUInteger)offset { return self = [super initWithID:productId limit:limit offset:offset]; } diff --git a/BVSDK/BVConversations/Display/Requests/Private/BVBaseProductRequest_Private.h b/BVSDK/BVConversations/Display/Requests/Private/BVBaseProductRequest_Private.h new file mode 100644 index 00000000..c93dba03 --- /dev/null +++ b/BVSDK/BVConversations/Display/Requests/Private/BVBaseProductRequest_Private.h @@ -0,0 +1,22 @@ +// +// BVBaseProductRequest_Private.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVBaseProductRequest.h" +#import "BVInclude.h" + +#ifndef BVBASEPRODUCTREQUEST_PRIVATE_H +#define BVBASEPRODUCTREQUEST_PRIVATE_H + +@interface BVBaseProductRequest () + +- (nonnull NSString *)statisticsToParams: + (nonnull NSArray *)statistics; +- (nonnull NSString *)includesToParams:(nonnull NSArray *)includes; + +@end + +#endif /* BVBASEPRODUCTREQUEST_PRIVATE_H */ diff --git a/BVSDK/BVConversations/Display/Requests/Private/BVBaseReviewsRequest_Private.h b/BVSDK/BVConversations/Display/Requests/Private/BVBaseReviewsRequest_Private.h new file mode 100644 index 00000000..c66a7a57 --- /dev/null +++ b/BVSDK/BVConversations/Display/Requests/Private/BVBaseReviewsRequest_Private.h @@ -0,0 +1,26 @@ +// +// BVBaseReviewsRequest_Private.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVBaseReviewsRequest.h" +#import "BVFilter.h" +#import "BVIncludeType.h" +#import "BVSort.h" + +#ifndef BVBASEREVIEWSREQUEST_PRIVATE_H +#define BVBASEREVIEWSREQUEST_PRIVATE_H + +@interface BVBaseReviewsRequest () + +@property(nonnull, nonatomic, strong, readonly) NSMutableArray *sorts; +@property(nonnull, nonatomic, strong, readonly) + NSMutableArray *filters; +@property(nonnull, nonatomic, strong, readonly) + NSMutableArray *includes; + +@end + +#endif /* BVBASEREVIEWSREQUEST_PRIVATE_H */ diff --git a/BVSDK/BVConversations/Display/Requests/Private/BVCommentsRequest_Private.h b/BVSDK/BVConversations/Display/Requests/Private/BVCommentsRequest_Private.h new file mode 100644 index 00000000..8930b61b --- /dev/null +++ b/BVSDK/BVConversations/Display/Requests/Private/BVCommentsRequest_Private.h @@ -0,0 +1,26 @@ +// +// BVCommentsRequest_Private.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVCommentsRequest.h" +#import "BVFilter.h" +#import "BVIncludeType.h" +#import "BVSort.h" + +#ifndef BVCOMMENTSREQUEST_PRIVATE_H +#define BVCOMMENTSREQUEST_PRIVATE_H + +@interface BVCommentsRequest () + +@property(nonnull, nonatomic, strong, readonly) NSMutableArray *sorts; +@property(nonnull, nonatomic, strong, readonly) + NSMutableArray *filters; +@property(nonnull, nonatomic, strong, readonly) + NSMutableArray *includes; + +@end + +#endif /* BVCOMMENTSREQUEST_PRIVATE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVAnswersSortOptionValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVAnswersSortOptionValue.h new file mode 100644 index 00000000..6056e988 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVAnswersSortOptionValue.h @@ -0,0 +1,37 @@ +// +// BVAnswersSortOptionValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVANSWERSSORTOPTIONVALUE_H +#define BVANSWERSSORTOPTIONVALUE_H + +/* + The allowable sorting types for answers included in a + `BVQuestionsAndAnswersRequest` request. + */ +typedef NS_ENUM(NSInteger, BVAnswersSortOptionValue) { + BVAnswersSortOptionValueAnswerId, + BVAnswersSortOptionValueAnswerAuthorId, + BVAnswersSortOptionValueAnswerCampaignId, + BVAnswersSortOptionValueAnswerContentLocale, + BVAnswersSortOptionValueAnswerHasPhotos, + BVAnswersSortOptionValueAnswerIsBestAnswer, + BVAnswersSortOptionValueAnswerIsFeatured, + BVAnswersSortOptionValueAnswerLastModeratedTime, + BVAnswersSortOptionValueAnswerLastModificationTime, + BVAnswersSortOptionValueAnswerProductId, + BVAnswersSortOptionValueAnswerQuestionId, + BVAnswersSortOptionValueAnswerSubmissionId, + BVAnswersSortOptionValueAnswerSubmissionTime, + BVAnswersSortOptionValueAnswerTotalFeedbackCount, + BVAnswersSortOptionValueAnswerTotalNegativeFeedbackCount, + BVAnswersSortOptionValueAnswerTotalPositiveFeedbackCount, + BVAnswersSortOptionValueAnswerUserLocation +}; + +#endif /* BVANSWERSSORTOPTIONVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVAuthorIncludeTypeValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVAuthorIncludeTypeValue.h new file mode 100644 index 00000000..df6fd98b --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVAuthorIncludeTypeValue.h @@ -0,0 +1,21 @@ +// +// BVAuthorIncludeTypeValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVAUTHORINCLUDETYPEVALUE_H +#define BVAUTHORINCLUDETYPEVALUE_H + +/// Types of Bazaarvoice content that can be included with a Profile. +typedef NS_ENUM(NSInteger, BVAuthorIncludeTypeValue) { + BVAuthorIncludeTypeValueAuthorReviews, + BVAuthorIncludeTypeValueAuthorQuestions, + BVAuthorIncludeTypeValueAuthorAnswers, + BVAuthorIncludeTypeValueAuthorReviewComments +}; + +#endif /* BVAUTHORINCLUDETYPEVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVBulkRatingFilterValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVBulkRatingFilterValue.h new file mode 100644 index 00000000..97a714eb --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVBulkRatingFilterValue.h @@ -0,0 +1,21 @@ +// +// BVBulkRatingFilterValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVBULKRATINGFILTERVALUE_H +#define BVBULKRATINGFILTERVALUE_H + +/* + Filter a `BVBulkRatingsRequest`. + */ +typedef NS_ENUM(NSInteger, BVBulkRatingFilterValue) { + BVBulkRatingFilterValueBulkRatingProductId, + BVBulkRatingFilterValueBulkRatingContentLocale +}; + +#endif /* BVBULKRATINGFILTERVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVBulkRatingIncludeTypeValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVBulkRatingIncludeTypeValue.h new file mode 100644 index 00000000..3ed77b00 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVBulkRatingIncludeTypeValue.h @@ -0,0 +1,19 @@ +// +// BVBulkRatingIncludeTypeValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVBULKRATINGINCLUDETYPEVALUE_H +#define BVBULKRATINGINCLUDETYPEVALUE_H + +typedef NS_ENUM(NSInteger, BVBulkRatingIncludeTypeValue) { + BVBulkRatingIncludeTypeValueBulkRatingReviews, + BVBulkRatingIncludeTypeValueBulkRatingNativeReviews, + BVBulkRatingIncludeTypeValueBulkRatingAll +}; + +#endif /* BVBULKRATINGINCLUDETYPEVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentFilterValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentFilterValue.h new file mode 100644 index 00000000..620f36c0 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentFilterValue.h @@ -0,0 +1,38 @@ +// +// BVCommentFilterValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVCOMMENTFILTERVALUE_H +#define BVCOMMENTFILTERVALUE_H + +/** + The allowable filter types for `BVCommentsRequest` requests. + API Refernce: + https://developer.bazaarvoice.com/conversations-api/reference/v5.4/comments/comment-display#filter-options + */ +typedef NS_ENUM(NSInteger, BVCommentFilterValue) { + BVCommentFilterValueCommentId, + BVCommentFilterValueCommentAuthorId, + BVCommentFilterValueCommentCampaignId, + BVCommentFilterValueCommentCategoryAncestorId, + BVCommentFilterValueCommentContentLocale, + BVCommentFilterValueCommentIsFeatured, + BVCommentFilterValueCommentLastModeratedTime, + BVCommentFilterValueCommentLastModificationTime, + BVCommentFilterValueCommentModeratorCode, + BVCommentFilterValueCommentProductId, + BVCommentFilterValueCommentReviewId, + BVCommentFilterValueCommentSubmissionId, + BVCommentFilterValueCommentSubmissionTime, + BVCommentFilterValueCommentTotalFeedbackCount, + BVCommentFilterValueCommentTotalNegativeFeedbackCount, + BVCommentFilterValueCommentTotalPositiveFeedbackCount, + BVCommentFilterValueCommentUserLocation +}; + +#endif /* BVCOMMENTFILTERVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentIncludeTypeValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentIncludeTypeValue.h new file mode 100644 index 00000000..6fdd4282 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentIncludeTypeValue.h @@ -0,0 +1,20 @@ +// +// BVCommentIncludeTypeValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVCOMMENTINCLUDETYPEVALUE_H +#define BVCOMMENTINCLUDETYPEVALUE_H + +/// Types of Bazaarvoice content that can be included with a Profile. +typedef NS_ENUM(NSInteger, BVCommentIncludeTypeValue) { + BVCommentIncludeTypeValueCommentAuthors, + BVCommentIncludeTypeValueCommentProducts, + BVCommentIncludeTypeValueCommentReviews +}; + +#endif /* BVCOMMENTINCLUDETYPEVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentsSortOptionValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentsSortOptionValue.h new file mode 100644 index 00000000..ed3e27e9 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVCommentsSortOptionValue.h @@ -0,0 +1,36 @@ +// +// BVCommentsSortOptionValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVCOMMENTSSORTOPTIONVALUE_H +#define BVCOMMENTSSORTOPTIONVALUE_H + +/** + The allowable sort types for `BVCommentsRequest` requests. + API Reference: + https://developer.bazaarvoice.com/conversations-api/reference/v5.4/comments/comment-display#sort-options + */ +typedef NS_ENUM(NSInteger, BVCommentsSortOptionValue) { + BVCommentsSortOptionValueCommentId, + BVCommentsSortOptionValueCommentAuthorId, + BVCommentsSortOptionValueCommentCampaignId, + BVCommentsSortOptionValueCommentContentLocale, + BVCommentsSortOptionValueCommentIsFeatured, + BVCommentsSortOptionValueCommentLastModeratedTime, + BVCommentsSortOptionValueCommentLastModificationTime, + BVCommentsSortOptionValueCommentProductId, + BVCommentsSortOptionValueCommentReviewId, + BVCommentsSortOptionValueCommentSubmissionId, + BVCommentsSortOptionValueCommentSubmissionTime, + BVCommentsSortOptionValueCommentTotalFeedbackCount, + BVCommentsSortOptionValueCommentTotalNegativeFeedbackCount, + BVCommentsSortOptionValueCommentTotalPositiveFeedbackCount, + BVCommentsSortOptionValueCommentUserLocation +}; + +#endif /* BVCOMMENTSSORTOPTIONVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVConversationDisplay.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVConversationDisplay.h new file mode 100644 index 00000000..7cea96be --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVConversationDisplay.h @@ -0,0 +1,19 @@ +// +// BVConversationDisplay.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVCONVERSATIONDISPLAY_H +#define BVCONVERSATIONDISPLAY_H + +#import +#import +#import +#import +#import + +#endif /* BVCONVERSATIONDISPLAY_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVFilterOperatorValues.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVFilterOperatorValues.h new file mode 100644 index 00000000..5572e63c --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVFilterOperatorValues.h @@ -0,0 +1,15 @@ +// +// BVFilterOperators.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVFILTEROPERATORS_H +#define BVFILTEROPERATORS_H + +#import + +#endif /* BVFILTEROPERATORS_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVFilterTypeValues.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVFilterTypeValues.h new file mode 100644 index 00000000..d10248d0 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVFilterTypeValues.h @@ -0,0 +1,19 @@ +// +// BVFilterValues.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVFILTERVALUES_H +#define BVFILTERVALUES_H + +#import +#import +#import +#import +#import + +#endif /* BVFILTERVALUES_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVIncludeTypeValues.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVIncludeTypeValues.h new file mode 100644 index 00000000..851e99ab --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVIncludeTypeValues.h @@ -0,0 +1,19 @@ +// +// BVIncludeTypeValues.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVINCLUDETYPEVALUES_H +#define BVINCLUDETYPEVALUES_H + +#import +#import +#import +#import +#import + +#endif /* BVINCLUDETYPEVALUES_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVMonotonicSortOrderValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVMonotonicSortOrderValue.h new file mode 100644 index 00000000..9fb45132 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVMonotonicSortOrderValue.h @@ -0,0 +1,21 @@ +// +// BVMonotonicSortOrderValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVMONOTONICSORTORDERVALUE_H +#define BVMONOTONICSORTORDERVALUE_H + +/* + Sort ordering can be `ascending` or `descending`. + */ +typedef NS_ENUM(NSInteger, BVMonotonicSortOrderValue) { + BVMonotonicSortOrderValueAscending, + BVMonotonicSortOrderValueDescending +}; + +#endif /* BVMONOTONICSORTORDERVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductFilterValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductFilterValue.h new file mode 100644 index 00000000..3a6ce0a3 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductFilterValue.h @@ -0,0 +1,35 @@ +// +// BVProductFilterValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVPRODUCTFILTERVALUE_H +#define BVPRODUCTFILTERVALUE_H + +/* + The allowable filter types for `BVProductDisplayPageRequest` requests. + */ +typedef NS_ENUM(NSInteger, BVProductFilterValue) { + BVProductFilterValueProductId, + BVProductFilterValueProductAverageOverallRating, + BVProductFilterValueProductCategoryAncestorId, + BVProductFilterValueProductCategoryId, + BVProductFilterValueProductIsActive, + BVProductFilterValueProductIsDisabled, + BVProductFilterValueProductLastAnswerTime, + BVProductFilterValueProductLastQuestionTime, + BVProductFilterValueProductLastReviewTime, + BVProductFilterValueProductLastStoryTime, + BVProductFilterValueProductName, + BVProductFilterValueProductRatingsOnlyReviewCount, + BVProductFilterValueProductTotalAnswerCount, + BVProductFilterValueProductTotalQuestionCount, + BVProductFilterValueProductTotalReviewCount, + BVProductFilterValueProductTotalStoryCount +}; + +#endif /* BVPRODUCTFILTERVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductIncludeTypeValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductIncludeTypeValue.h new file mode 100644 index 00000000..2900626d --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductIncludeTypeValue.h @@ -0,0 +1,19 @@ +// +// BVProductIncludeTypeValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVPDPINCLUDETYPEVALUE_H +#define BVPDPINCLUDETYPEVALUE_H + +typedef NS_ENUM(NSInteger, BVProductIncludeTypeValue) { + BVProductIncludeTypeValueReviews, + BVProductIncludeTypeValueQuestions, + BVProductIncludeTypeValueAnswers +}; + +#endif /* BVPDPINCLUDETYPEVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductsSortOptionValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductsSortOptionValue.h new file mode 100644 index 00000000..aaeb9051 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVProductsSortOptionValue.h @@ -0,0 +1,37 @@ +// +// BVProductsSortOptionValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVPRODUCTSSORTOPTIONVALUE_H +#define BVPRODUCTSSORTOPTIONVALUE_H + +/* + The allowable sort types for `BVReviewsRequest` and + `BVQuestionsAndAnswersRequests` requests. + */ +typedef NS_ENUM(NSInteger, BVProductsSortOptionValue) { + BVProductsSortOptionValueProductId, + BVProductsSortOptionValueProductAverageOverallRating, + BVProductsSortOptionValueProductRating, + BVProductsSortOptionValueProductCategoryId, + BVProductsSortOptionValueProductIsActive, + BVProductsSortOptionValueProductIsDisabled, + BVProductsSortOptionValueProductLastAnswerTime, + BVProductsSortOptionValueProductLastQuestionTime, + BVProductsSortOptionValueProductLastReviewTime, + BVProductsSortOptionValueProductLastStoryTime, + BVProductsSortOptionValueProductName, + BVProductsSortOptionValueProductRatingsOnlyReviewCount, + BVProductsSortOptionValueProductTotalAnswerCount, + BVProductsSortOptionValueProductTotalQuestionCount, + BVProductsSortOptionValueProductTotalReviewCount, + BVProductsSortOptionValueProductTotalStoryCount, + BVProductsSortOptionValueProductHelpfulness +}; + +#endif /* BVPRODUCTSSORTOPTIONVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVQuestionFilterValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVQuestionFilterValue.h new file mode 100644 index 00000000..290ce453 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVQuestionFilterValue.h @@ -0,0 +1,47 @@ +// +// BVQuestionFilterValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVQUESTIONFILTERVALUE_H +#define BVQUESTIONFILTERVALUE_H + +/* + The allowable filter types for `BVQuestionsAndAnswersRequest` requests. + */ +typedef NS_ENUM(NSInteger, BVQuestionFilterValue) { + BVQuestionFilterValueQuestionId, + BVQuestionFilterValueQuestionAuthorId, + BVQuestionFilterValueQuestionCampaignId, + BVQuestionFilterValueQuestionCategoryAncestorId, + BVQuestionFilterValueQuestionCategoryId, + BVQuestionFilterValueQuestionContentLocale, + BVQuestionFilterValueQuestionHasAnswers, + BVQuestionFilterValueQuestionHasBestAnswer, + BVQuestionFilterValueQuestionHasBrandAnswers, + BVQuestionFilterValueQuestionHasPhotos, + BVQuestionFilterValueQuestionHasStaffAnswers, + BVQuestionFilterValueQuestionHasTags, + BVQuestionFilterValueQuestionHasVideos, + BVQuestionFilterValueQuestionIsFeatured, + BVQuestionFilterValueQuestionIsSubjectActive, + BVQuestionFilterValueQuestionLastApprovedAnswerSubmissionTime, + BVQuestionFilterValueQuestionLastModeratedTime, + BVQuestionFilterValueQuestionLastModificationTime, + BVQuestionFilterValueQuestionModeratorCode, + BVQuestionFilterValueQuestionProductId, + BVQuestionFilterValueQuestionSubmissionId, + BVQuestionFilterValueQuestionSubmissionTime, + BVQuestionFilterValueQuestionSummary, + BVQuestionFilterValueQuestionTotalAnswerCount, + BVQuestionFilterValueQuestionTotalFeedbackCount, + BVQuestionFilterValueQuestionTotalNegativeFeedbackCount, + BVQuestionFilterValueQuestionTotalPositiveFeedbackCount, + BVQuestionFilterValueQuestionUserLocation +}; + +#endif /* BVQUESTIONFILTERVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVQuestionsSortOptionValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVQuestionsSortOptionValue.h new file mode 100644 index 00000000..2f234cd4 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVQuestionsSortOptionValue.h @@ -0,0 +1,43 @@ +// +// BVQuestionsSortOptionValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVQUESTIONSSORTOPTIONVALUE_H +#define BVQUESTIONSSORTOPTIONVALUE_H + +/* + The allowable sort types for `BVQuestionsAndAnswersRequests` requests. + */ +typedef NS_ENUM(NSInteger, BVQuestionsSortOptionValue) { + BVQuestionsSortOptionValueQuestionId, + BVQuestionsSortOptionValueQuestionAuthorId, + BVQuestionsSortOptionValueQuestionCampaignId, + BVQuestionsSortOptionValueQuestionContentLocale, + BVQuestionsSortOptionValueQuestionHasAnswers, + BVQuestionsSortOptionValueQuestionHasBestAnswer, + BVQuestionsSortOptionValueQuestionHasPhotos, + BVQuestionsSortOptionValueQuestionHasStaffAnswers, + BVQuestionsSortOptionValueQuestionIsFeatured, + BVQuestionsSortOptionValueQuestionIsSubjectActive, + BVQuestionsSortOptionValueQuestionLastApprovedAnswerSubmissionTime, + BVQuestionsSortOptionValueQuestionModeratorCode, + BVQuestionsSortOptionValueQuestionLastModeratedTime, + BVQuestionsSortOptionValueQuestionLastModificationTime, + BVQuestionsSortOptionValueQuestionProductId, + BVQuestionsSortOptionValueQuestionSubmissionId, + BVQuestionsSortOptionValueQuestionSubmissionTime, + BVQuestionsSortOptionValueQuestionSummary, + BVQuestionsSortOptionValueQuestionTotalAnswerCount, + BVQuestionsSortOptionValueQuestionTotalFeedbackCount, + BVQuestionsSortOptionValueQuestionTotalNegativeFeedbackCount, + BVQuestionsSortOptionValueQuestionTotalPositiveFeedbackCount, + BVQuestionsSortOptionValueQuestionUserLocation, + BVQuestionsSortOptionValueQuestionHasVideos, // PRR Only +}; + +#endif /* BVQUESTIONSSORTOPTIONVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVRelationalFilterOperatorValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVRelationalFilterOperatorValue.h new file mode 100644 index 00000000..1eb39911 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVRelationalFilterOperatorValue.h @@ -0,0 +1,28 @@ +// +// BVRelationalFilterOperatorValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVRELATIONALFILTEROPERATORVALUE_H +#define BVRELATIONALFILTEROPERATORVALUE_H + +/* + BVRelationalFilterOperator describes the operator used on filters added to + request objects. For example: to search reviews that have ratings greater than + 3, you would use the BVRelationalFilterOperatorValueGreaterThanOrEqualTo + operator. + */ +typedef NS_ENUM(NSInteger, BVRelationalFilterOperatorValue) { + BVRelationalFilterOperatorValueGreaterThan, + BVRelationalFilterOperatorValueGreaterThanOrEqualTo, + BVRelationalFilterOperatorValueLessThan, + BVRelationalFilterOperatorValueLessThanOrEqualTo, + BVRelationalFilterOperatorValueEqualTo, + BVRelationalFilterOperatorValueNotEqualTo, +}; + +#endif /* BVRELATIONALFILTEROPERATORVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewFilterValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewFilterValue.h new file mode 100644 index 00000000..eedf9647 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewFilterValue.h @@ -0,0 +1,45 @@ +// +// BVReviewFilterValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVREVIEWFILTERVALUE_H +#define BVREVIEWFILTERVALUE_H + +/** + The allowable filter types for `BVReviewsRequest` requests. + */ +typedef NS_ENUM(NSInteger, BVReviewFilterValue) { + BVReviewFilterValueId, + BVReviewFilterValueAuthorId, + BVReviewFilterValueCampaignId, + BVReviewFilterValueCategoryAncestorId, + BVReviewFilterValueContentLocale, + BVReviewFilterValueHasComments, + BVReviewFilterValueHasPhotos, + BVReviewFilterValueHasTags, + BVReviewFilterValueHasVideos, + BVReviewFilterValueIsFeatured, + BVReviewFilterValueIsRatingsOnly, + BVReviewFilterValueIsRecommended, + BVReviewFilterValueIsSubjectActive, + BVReviewFilterValueIsSyndicated, + BVReviewFilterValueLastModeratedTime, + BVReviewFilterValueLastModificationTime, + BVReviewFilterValueModeratorCode, + BVReviewFilterValueProductId, + BVReviewFilterValueRating, + BVReviewFilterValueSubmissionId, + BVReviewFilterValueSubmissionTime, + BVReviewFilterValueTotalCommentCount, + BVReviewFilterValueTotalFeedbackCount, + BVReviewFilterValueTotalNegativeFeedbackCount, + BVReviewFilterValueTotalPositiveFeedbackCount, + BVReviewFilterValueUserLocation +}; + +#endif /* BVREVIEWFILTERVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewIncludeTypeValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewIncludeTypeValue.h new file mode 100644 index 00000000..6acde243 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewIncludeTypeValue.h @@ -0,0 +1,19 @@ +// +// BVReviewIncludeTypeValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVREVIEWINCLUDETYPEVALUE_H +#define BVREVIEWINCLUDETYPEVALUE_H + +/// Types of Bazaarvoice content that can be included with a Profile. +typedef NS_ENUM(NSInteger, BVReviewIncludeTypeValue) { + BVReviewIncludeTypeValueReviewProducts, + BVReviewIncludeTypeValueReviewComments +}; + +#endif /* BVREVIEWINCLUDETYPEVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewsSortOptionValue.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewsSortOptionValue.h new file mode 100644 index 00000000..8b35908b --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVReviewsSortOptionValue.h @@ -0,0 +1,44 @@ +// +// BVReviewsSortOptionValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVREVIEWSSORTOPTIONVALUE_H +#define BVREVIEWSSORTOPTIONVALUE_H + +/* + The allowable sort types for `BVReviewsRequest` requests. + */ +typedef NS_ENUM(NSInteger, BVReviewsSortOptionValue) { + BVReviewsSortOptionValueReviewId, + BVReviewsSortOptionValueReviewAuthorId, + BVReviewsSortOptionValueReviewCampaignId, + BVReviewsSortOptionValueReviewContentLocale, + BVReviewsSortOptionValueReviewHasComments, + BVReviewsSortOptionValueReviewHasPhotos, + BVReviewsSortOptionValueReviewHasTags, + BVReviewsSortOptionValueReviewHasVideos, + BVReviewsSortOptionValueReviewHelpfulness, + BVReviewsSortOptionValueReviewIsFeatured, + BVReviewsSortOptionValueReviewIsRatingsOnly, + BVReviewsSortOptionValueReviewIsRecommended, + BVReviewsSortOptionValueReviewIsSubjectActive, + BVReviewsSortOptionValueReviewIsSyndicated, + BVReviewsSortOptionValueReviewLastModeratedTime, + BVReviewsSortOptionValueReviewLastModificationTime, + BVReviewsSortOptionValueReviewProductId, + BVReviewsSortOptionValueReviewRating, + BVReviewsSortOptionValueReviewSubmissionId, + BVReviewsSortOptionValueReviewSubmissionTime, + BVReviewsSortOptionValueReviewTotalCommentCount, + BVReviewsSortOptionValueReviewTotalFeedbackCount, + BVReviewsSortOptionValueReviewTotalNegativeFeedbackCount, + BVReviewsSortOptionValueReviewTotalPositiveFeedbackCount, + BVReviewsSortOptionValueReviewUserLocation +}; + +#endif /* BVREVIEWSSORTOPTIONVALUE_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVSortOptionValues.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVSortOptionValues.h new file mode 100644 index 00000000..5e931f52 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVSortOptionValues.h @@ -0,0 +1,19 @@ +// +// BVSortOptionValues.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVSORTOPTIONVALUES_H +#define BVSORTOPTIONVALUES_H + +#import +#import +#import +#import +#import + +#endif /* BVSORTOPTIONVALUES_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/BVSortOrderValues.h b/BVSDK/BVConversations/Display/Sorting & Filtering/BVSortOrderValues.h new file mode 100644 index 00000000..86b9b501 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/BVSortOrderValues.h @@ -0,0 +1,15 @@ +// +// BVSortOrderValues.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVSORTORDERVALUES_H +#define BVSORTORDERVALUES_H + +#import + +#endif /* BVSORTORDERVALUES_H */ diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAnswersSortOption.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAnswersSortOption.h new file mode 100644 index 00000000..0cb98430 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAnswersSortOption.h @@ -0,0 +1,16 @@ +// +// BVAnswersSortOption.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVAnswersSortOptionValue.h" +#import "BVSortOption.h" + +@interface BVAnswersSortOption : BVSortOption + +- (nonnull instancetype)initWithAnswersSortOptionValue: + (BVAnswersSortOptionValue)answersSortOptionValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAnswersSortOption.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAnswersSortOption.m new file mode 100644 index 00000000..fd4459ef --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAnswersSortOption.m @@ -0,0 +1,93 @@ +// +// BVAnswersSortOption.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVAnswersSortOption.h" + +@interface BVAnswersSortOption () +@property(nonnull, nonatomic, strong) NSString *value; +@end + +@implementation BVAnswersSortOption + ++ (nonnull NSString *)toSortOptionParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVAnswersSortOption sortOptionWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)sortOptionWithRawValue:(NSInteger)rawValue { + return [[BVAnswersSortOption alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVAnswersSortOptionValueAnswerId: + self.value = @"Id"; + break; + case BVAnswersSortOptionValueAnswerAuthorId: + self.value = @"AuthorId"; + break; + case BVAnswersSortOptionValueAnswerCampaignId: + self.value = @"CampaignId"; + break; + case BVAnswersSortOptionValueAnswerContentLocale: + self.value = @"ContentLocale"; + break; + case BVAnswersSortOptionValueAnswerHasPhotos: + self.value = @"HasPhotos"; + break; + case BVAnswersSortOptionValueAnswerIsBestAnswer: + self.value = @"IsBestAnswer"; + break; + case BVAnswersSortOptionValueAnswerIsFeatured: + self.value = @"IsFeatured"; + break; + case BVAnswersSortOptionValueAnswerLastModeratedTime: + self.value = @"LastModeratedTime"; + break; + case BVAnswersSortOptionValueAnswerLastModificationTime: + self.value = @"LastModificationTime"; + break; + case BVAnswersSortOptionValueAnswerProductId: + self.value = @"ProductId"; + break; + case BVAnswersSortOptionValueAnswerQuestionId: + self.value = @"QuestionId"; + break; + case BVAnswersSortOptionValueAnswerSubmissionId: + self.value = @"SubmissionId"; + break; + case BVAnswersSortOptionValueAnswerSubmissionTime: + self.value = @"SubmissionTime"; + break; + case BVAnswersSortOptionValueAnswerTotalFeedbackCount: + self.value = @"TotalFeedbackCount"; + break; + case BVAnswersSortOptionValueAnswerTotalNegativeFeedbackCount: + self.value = @"TotalNegativeFeedbackCount"; + break; + case BVAnswersSortOptionValueAnswerTotalPositiveFeedbackCount: + self.value = @"TotalPositiveFeedbackCount"; + break; + case BVAnswersSortOptionValueAnswerUserLocation: + self.value = @"UserLocation"; + break; + } + } + return self; +} + +- (nonnull NSString *)toSortOptionParameterString { + return self.value; +} + +- (nonnull instancetype)initWithAnswersSortOptionValue: + (BVAnswersSortOptionValue)answersSortOptionValue { + return [BVAnswersSortOption sortOptionWithRawValue:answersSortOptionValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAuthorIncludeType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAuthorIncludeType.h new file mode 100644 index 00000000..f7d9115d --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAuthorIncludeType.h @@ -0,0 +1,16 @@ +// +// BVAuthorIncludeType.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVAuthorIncludeTypeValue.h" +#import "BVIncludeType.h" + +@interface BVAuthorIncludeType : BVIncludeType + +- (nonnull instancetype)initWithAuthorIncludeTypeValue: + (BVAuthorIncludeTypeValue)authorIncludeTypeValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAuthorIncludeType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAuthorIncludeType.m new file mode 100644 index 00000000..a2100888 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVAuthorIncludeType.m @@ -0,0 +1,54 @@ +// +// BVAuthorIncludeType.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVAuthorIncludeType.h" + +@interface BVAuthorIncludeType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVAuthorIncludeType + ++ (nonnull NSString *)toIncludeTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVAuthorIncludeType includeTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)includeTypeWithRawValue:(NSInteger)rawValue { + return [[BVAuthorIncludeType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVAuthorIncludeTypeValueAuthorReviews: + self.value = @"Reviews"; + break; + case BVAuthorIncludeTypeValueAuthorAnswers: + self.value = @"Answers"; + break; + case BVAuthorIncludeTypeValueAuthorQuestions: + self.value = @"Questions"; + break; + case BVAuthorIncludeTypeValueAuthorReviewComments: + self.value = @"Comments"; + break; + } + } + return self; +} + +- (nonnull NSString *)toIncludeTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithAuthorIncludeTypeValue: + (BVAuthorIncludeTypeValue)authorIncludeTypeValue { + return [BVAuthorIncludeType includeTypeWithRawValue:authorIncludeTypeValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingFilterType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingFilterType.h new file mode 100644 index 00000000..793452cb --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingFilterType.h @@ -0,0 +1,16 @@ +// +// BVBulkRatingFilterType.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVBulkRatingFilterValue.h" +#import "BVFilterType.h" + +@interface BVBulkRatingFilterType : BVFilterType + +- (nonnull instancetype)initWithBulkRatingFilterValue: + (BVBulkRatingFilterValue)bulkRatingFilterValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingFilterType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingFilterType.m new file mode 100644 index 00000000..ea97716f --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingFilterType.m @@ -0,0 +1,49 @@ +// +// BVBulkRatingFilterType.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVBulkRatingFilterType.h" + +@interface BVBulkRatingFilterType () +@property(nonnull, nonatomic, strong) NSString *value; +@end + +@implementation BVBulkRatingFilterType + ++ (nonnull NSString *)toFilterTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVBulkRatingFilterType filterTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)filterTypeWithRawValue:(NSInteger)rawValue { + return [[BVBulkRatingFilterType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVBulkRatingFilterValueBulkRatingContentLocale: + self.value = @"ContentLocale"; + break; + case BVBulkRatingFilterValueBulkRatingProductId: + self.value = @"ProductId"; + break; + } + } + return self; +} + +- (nonnull NSString *)toFilterTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithBulkRatingFilterValue: + (BVBulkRatingFilterValue)bulkRatingFilterValue { + return + [[BVBulkRatingFilterType alloc] initWithRawValue:bulkRatingFilterValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingIncludeType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingIncludeType.h new file mode 100644 index 00000000..26ff15c5 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingIncludeType.h @@ -0,0 +1,16 @@ +// +// BVBulkRatingIncludeType.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVBulkRatingIncludeTypeValue.h" +#import "BVIncludeType.h" + +@interface BVBulkRatingIncludeType : BVIncludeType + +- (nonnull instancetype)initWithBulkRatingIncludeTypeValue: + (BVBulkRatingIncludeTypeValue)bulkRatingIncludeTypeValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingIncludeType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingIncludeType.m new file mode 100644 index 00000000..c7108a99 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVBulkRatingIncludeType.m @@ -0,0 +1,52 @@ +// +// BVBulkRatingIncludeType.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVBulkRatingIncludeType.h" + +@interface BVBulkRatingIncludeType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVBulkRatingIncludeType + ++ (nonnull NSString *)toIncludeTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVBulkRatingIncludeType includeTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)includeTypeWithRawValue:(NSInteger)rawValue { + return [[BVBulkRatingIncludeType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVBulkRatingIncludeTypeValueBulkRatingReviews: + self.value = @"Reviews"; + break; + case BVBulkRatingIncludeTypeValueBulkRatingNativeReviews: + self.value = @"NativeReviews"; + break; + case BVBulkRatingIncludeTypeValueBulkRatingAll: + self.value = @"Reviews,NativeReviews"; + break; + } + } + return self; +} + +- (nonnull NSString *)toIncludeTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithBulkRatingIncludeTypeValue: + (BVBulkRatingIncludeTypeValue)bulkRatingIncludeTypeValue { + return [BVBulkRatingIncludeType + includeTypeWithRawValue:bulkRatingIncludeTypeValue]; +} + +@end diff --git a/Pod/BVConversations/Display/BVCommaUtil.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommaUtil.h similarity index 100% rename from Pod/BVConversations/Display/BVCommaUtil.h rename to BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommaUtil.h diff --git a/Pod/BVConversations/Display/BVCommaUtil.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommaUtil.m similarity index 100% rename from Pod/BVConversations/Display/BVCommaUtil.m rename to BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommaUtil.m diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentFilterType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentFilterType.h new file mode 100644 index 00000000..b143d0c6 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentFilterType.h @@ -0,0 +1,16 @@ +// +// BVCommentFilterValue.h +// Conversations +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentFilterValue.h" +#import "BVFilterType.h" + +@interface BVCommentFilterType : BVFilterType + +- (nonnull instancetype)initWithCommentFilterValue: + (BVCommentFilterValue)commentFilterValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentFilterType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentFilterType.m new file mode 100644 index 00000000..83b5daa3 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentFilterType.m @@ -0,0 +1,94 @@ +// +// BVCommentFilterValue.m +// Conversations +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentFilterType.h" + +@interface BVCommentFilterType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVCommentFilterType + ++ (nonnull NSString *)toFilterTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVCommentFilterType filterTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)filterTypeWithRawValue:(NSInteger)rawValue { + return [[BVCommentFilterType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVCommentFilterValueCommentId: + self.value = @"Id"; + break; + case BVCommentFilterValueCommentAuthorId: + self.value = @"AuthorId"; + break; + case BVCommentFilterValueCommentCampaignId: + self.value = @"CampaignId"; + break; + case BVCommentFilterValueCommentCategoryAncestorId: + self.value = @"CategoryAncestorId"; + break; + case BVCommentFilterValueCommentContentLocale: + self.value = @"ContentLocale"; + break; + case BVCommentFilterValueCommentIsFeatured: + self.value = @"IsFeatured"; + break; + case BVCommentFilterValueCommentLastModeratedTime: + self.value = @"LastModeratedTime"; + break; + case BVCommentFilterValueCommentLastModificationTime: + self.value = @"LastModificationTime"; + break; + case BVCommentFilterValueCommentModeratorCode: + self.value = @"ModeratorCode"; + break; + case BVCommentFilterValueCommentProductId: + self.value = @"ProductId"; + break; + case BVCommentFilterValueCommentReviewId: + self.value = @"ReviewId"; + break; + case BVCommentFilterValueCommentSubmissionId: + self.value = @"SubmissionId"; + break; + case BVCommentFilterValueCommentSubmissionTime: + self.value = @"SubmissionTime"; + break; + case BVCommentFilterValueCommentTotalFeedbackCount: + self.value = @"TotalFeedbackCount"; + break; + case BVCommentFilterValueCommentTotalNegativeFeedbackCount: + self.value = @"TotalNegativeFeedbackCount"; + break; + case BVCommentFilterValueCommentTotalPositiveFeedbackCount: + self.value = @"TotalPositiveFeedbackCount"; + break; + case BVCommentFilterValueCommentUserLocation: + self.value = @"UserLocation"; + break; + } + } + return self; +} + +- (nonnull NSString *)toFilterTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithCommentFilterValue: + (BVCommentFilterValue)commentFilterValue { + return [[BVCommentFilterType alloc] + initWithCommentFilterValue:commentFilterValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentIncludeType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentIncludeType.h new file mode 100644 index 00000000..478ff192 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentIncludeType.h @@ -0,0 +1,16 @@ +// +// BVCommentIncludeType.h +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentIncludeTypeValue.h" +#import "BVIncludeType.h" + +@interface BVCommentIncludeType : BVIncludeType + +- (nonnull instancetype)initWithCommentIncludeTypeValue: + (BVCommentIncludeTypeValue)commentIncludeTypeValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentIncludeType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentIncludeType.m new file mode 100644 index 00000000..28b37b48 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentIncludeType.m @@ -0,0 +1,53 @@ + + +// +// BVCommentIncludeType.m +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentIncludeType.h" + +@interface BVCommentIncludeType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVCommentIncludeType + ++ (nonnull NSString *)toIncludeTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVCommentIncludeType includeTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)includeTypeWithRawValue:(NSInteger)rawValue { + return [[BVCommentIncludeType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVCommentIncludeTypeValueCommentAuthors: + self.value = @"Authors"; + break; + case BVCommentIncludeTypeValueCommentProducts: + self.value = @"Products"; + break; + case BVCommentIncludeTypeValueCommentReviews: + self.value = @"Reviews"; + break; + } + } + return self; +} + +- (nonnull NSString *)toIncludeTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithCommentIncludeTypeValue: + (BVCommentIncludeTypeValue)commentIncludeTypeValue { + return [BVCommentIncludeType includeTypeWithRawValue:commentIncludeTypeValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentsSortOption.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentsSortOption.h new file mode 100644 index 00000000..015d4021 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentsSortOption.h @@ -0,0 +1,16 @@ +// +// BVCommentsSortOption.h +// Conversations +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentsSortOptionValue.h" +#import "BVSortOption.h" + +@interface BVCommentsSortOption : BVSortOption + +- (nonnull instancetype)initWithSortOptionCommentsValue: + (BVCommentsSortOptionValue)commentsSortOptionValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentsSortOption.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentsSortOption.m new file mode 100644 index 00000000..3679f415 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVCommentsSortOption.m @@ -0,0 +1,87 @@ +// +// BVCommentsSortOption.m +// Conversations +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVCommentsSortOption.h" + +@interface BVCommentsSortOption () +@property(nonnull, nonatomic, strong) NSString *value; +@end + +@implementation BVCommentsSortOption + ++ (nonnull NSString *)toSortOptionParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVCommentsSortOption sortOptionWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)sortOptionWithRawValue:(NSInteger)rawValue { + return [[BVCommentsSortOption alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVCommentsSortOptionValueCommentId: + self.value = @"Id"; + break; + case BVCommentsSortOptionValueCommentAuthorId: + self.value = @"AuthorId"; + break; + case BVCommentsSortOptionValueCommentCampaignId: + self.value = @"CampaignId"; + break; + case BVCommentsSortOptionValueCommentContentLocale: + self.value = @"ContentLocale"; + break; + case BVCommentsSortOptionValueCommentIsFeatured: + self.value = @"IsFeatured"; + break; + case BVCommentsSortOptionValueCommentLastModeratedTime: + self.value = @"LastModeratedTime"; + break; + case BVCommentsSortOptionValueCommentLastModificationTime: + self.value = @"LastModificationTime"; + break; + case BVCommentsSortOptionValueCommentProductId: + self.value = @"ProductId"; + break; + case BVCommentsSortOptionValueCommentReviewId: + self.value = @"ReviewId"; + break; + case BVCommentsSortOptionValueCommentSubmissionId: + self.value = @"SubmissionId"; + break; + case BVCommentsSortOptionValueCommentSubmissionTime: + self.value = @"SubmissionTime"; + break; + case BVCommentsSortOptionValueCommentTotalFeedbackCount: + self.value = @"TotalFeedbackCount"; + break; + case BVCommentsSortOptionValueCommentTotalNegativeFeedbackCount: + self.value = @"TotalNegativeFeedbackCount"; + break; + case BVCommentsSortOptionValueCommentTotalPositiveFeedbackCount: + self.value = @"TotalPositiveFeedbackCount"; + break; + case BVCommentsSortOptionValueCommentUserLocation: + self.value = @"UserLocation"; + break; + } + } + return self; +} + +- (nonnull NSString *)toSortOptionParameterString { + return self.value; +} + +- (nonnull instancetype)initWithSortOptionCommentsValue: + (BVCommentsSortOptionValue)commentsSortOptionValue { + return [BVCommentsSortOption sortOptionWithRawValue:commentsSortOptionValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilter.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilter.h new file mode 100644 index 00000000..097516d9 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilter.h @@ -0,0 +1,38 @@ +// +// Filters.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import + +@protocol BVFilterTypeProtocol ++ (nonnull NSString *)toFilterTypeParameterStringWithRawValue: + (NSInteger)rawValue; +- (nonnull NSString *)toFilterTypeParameterString; +@end + +@protocol BVFilterOperatorProtocol ++ (nonnull NSString *)toFilterOperatorParameterStringWithRawValue: + (NSInteger)rawValue; +- (nonnull NSString *)toFilterOperatorParameterString; +@end + +@interface BVFilter : NSObject + +- (nonnull id)initWithFilterType:(nonnull id)filterType + filterOperator: + (nonnull id)filterOperator + values:(nonnull NSArray *)values; +- (nonnull id)initWithFilterType:(nonnull id)filterType + filterOperator: + (nonnull id)filterOperator + value:(nonnull NSString *)value; +- (nonnull id)initWithString:(nonnull NSString *)string + filterOperator: + (nonnull id)filterOperator + values:(nonnull NSArray *)values; +- (nonnull NSString *)toParameterString; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilter.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilter.m new file mode 100644 index 00000000..f7bccf9c --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilter.m @@ -0,0 +1,67 @@ +// +// Filters.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVFilter.h" +#import "BVCommaUtil.h" + +@interface BVFilter () + +@property(nonnull) NSString *filterType; +@property(nonnull) NSString *filterOp; +@property(nonnull) NSArray *values; + +@end + +@implementation BVFilter + +- (nonnull id)initWithFilterType:(nonnull id)filterType + filterOperator: + (nonnull id)filterOperator + value:(nonnull NSString *)value { + if ((self = [super init])) { + self.filterType = [filterType toFilterTypeParameterString]; + self.filterOp = [filterOperator toFilterOperatorParameterString]; + self.values = [BVCommaUtil escapeMultiple:@[ value ]]; + } + return self; +} + +- (nonnull id)initWithFilterType:(nonnull id)filterType + filterOperator: + (nonnull id)filterOperator + values:(nonnull NSArray *)values { + if ((self = [super init])) { + self.filterType = [filterType toFilterTypeParameterString]; + self.filterOp = [filterOperator toFilterOperatorParameterString]; + self.values = [BVCommaUtil escapeMultiple:values]; + } + return self; +} + +- (nonnull id)initWithString:(nonnull NSString *)str + filterOperator: + (nonnull id)filterOperator + values:(nonnull NSArray *)values { + if ((self = [super init])) { + self.filterType = str; + self.filterOp = [filterOperator toFilterOperatorParameterString]; + self.values = [BVCommaUtil escapeMultiple:values]; + } + return self; +} + +- (NSString *)toParameterString { + NSString *start = self.filterType; + NSString *middle = self.filterOp; + NSArray *sortedArray = [self.values + sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)]; + NSString *end = [sortedArray componentsJoinedByString:@","]; + + return [NSString stringWithFormat:@"%@:%@:%@", start, middle, end]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterOperator.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterOperator.h new file mode 100644 index 00000000..6749a225 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterOperator.h @@ -0,0 +1,16 @@ +// +// FilterOperator.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVFilter.h" + +@interface BVFilterOperator : NSObject + ++ (nonnull instancetype)filterOperatorWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)__unavailable init; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterOperator.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterOperator.m new file mode 100644 index 00000000..c1b07e56 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterOperator.m @@ -0,0 +1,37 @@ +// +// FilterOperator.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVFilterOperator.h" + +@implementation BVFilterOperator + ++ (nonnull instancetype)filterOperatorWithRawValue:(NSInteger)rawValue { + NSAssert(NO, + @"filterOperatorWithRawValue: should be overriden by the subclass"); + return [super init]; +} + ++ (nonnull NSString *)toFilterOperatorParameterStringWithRawValue: + (NSInteger)rawValue { + NSAssert(NO, @"toFilterOperatorParameterStringWithRawValue: should be " + @"overriden by the subclass"); + return @""; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + // Discards the rawValue as this should be overridden and mapped appropriately + // by the subclass. + return [super init]; +} + +- (nonnull NSString *)toFilterOperatorParameterString { + NSAssert(NO, + @"toFilterTypeParameterString should be overriden by the subclass"); + return @""; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterType.h new file mode 100644 index 00000000..12567ebc --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterType.h @@ -0,0 +1,16 @@ +// +// BVFilterType.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVFilter.h" + +@interface BVFilterType : NSObject + ++ (nonnull instancetype)filterTypeWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)__unavailable init; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterType.m new file mode 100644 index 00000000..bce6cd63 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVFilterType.m @@ -0,0 +1,36 @@ +// +// BVFilterType.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVFilterType.h" + +@implementation BVFilterType + ++ (nonnull instancetype)filterTypeWithRawValue:(NSInteger)rawValue { + NSAssert(NO, @"filterTypeWithRawValue: should be overriden by the subclass"); + return [super init]; +} + ++ (nonnull NSString *)toFilterTypeParameterStringWithRawValue: + (NSInteger)rawValue { + NSAssert(NO, @"toFilterTypeParameterStringWithRawValue: should be overriden " + @"by the subclass"); + return @""; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + // Discards the rawValue as this should be overridden and mapped appropriately + // by the subclass. + return [super init]; +} + +- (nonnull NSString *)toFilterTypeParameterString { + NSAssert(NO, + @"toFilterTypeParameterString should be overriden by the subclass"); + return @""; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVInclude.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVInclude.h new file mode 100644 index 00000000..8aae4591 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVInclude.h @@ -0,0 +1,26 @@ +// +// BVInclude.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +@protocol BVIncludeTypeProtocol ++ (nonnull NSString *)toIncludeTypeParameterStringWithRawValue: + (NSInteger)rawValue; +- (nonnull NSString *)toIncludeTypeParameterString; +@end + +@interface BVInclude : NSObject + +@property(nullable, strong, readonly) NSNumber *includeLimit; + +- (nonnull id)initWithIncludeType: + (nonnull id)includeType; +- (nonnull id)initWithIncludeType:(nonnull id)includeType + includeLimit:(nullable NSNumber *)includeLimit; +- (nonnull NSString *)toParameterString; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVInclude.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVInclude.m new file mode 100644 index 00000000..76f7c377 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVInclude.m @@ -0,0 +1,35 @@ +// +// BVIncludeType.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVInclude.h" + +@interface BVInclude () +@property(nonnull, strong) NSString *includeType; +@property(nullable, strong, readwrite) NSNumber *includeLimit; +@end + +@implementation BVInclude + +- (nonnull id)initWithIncludeType: + (nonnull id)includeType { + return [self initWithIncludeType:includeType includeLimit:nil]; +} + +- (nonnull id)initWithIncludeType:(nonnull id)includeType + includeLimit:(nullable NSNumber *)includeLimit { + if ((self = [super init])) { + self.includeType = [includeType toIncludeTypeParameterString]; + self.includeLimit = includeLimit; + } + return self; +} + +- (nonnull NSString *)toParameterString { + return self.includeType; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVIncludeType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVIncludeType.h new file mode 100644 index 00000000..098d3dc1 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVIncludeType.h @@ -0,0 +1,16 @@ +// +// BVIncludeType.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVInclude.h" + +@interface BVIncludeType : NSObject + ++ (nonnull instancetype)includeTypeWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)__unavailable init; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVIncludeType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVIncludeType.m new file mode 100644 index 00000000..0409cabd --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVIncludeType.m @@ -0,0 +1,36 @@ +// +// BVIncludeType.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVIncludeType.h" + +@implementation BVIncludeType + ++ (nonnull instancetype)includeTypeWithRawValue:(NSInteger)rawValue { + NSAssert(NO, @"includeTypeWithRawValue: should be overriden by the subclass"); + return [super init]; +} + ++ (nonnull NSString *)toIncludeTypeParameterStringWithRawValue: + (NSInteger)rawValue { + NSAssert(NO, @"toIncludeTypeParameterStringWithRawValue: should be overriden " + @"by the subclass"); + return @""; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + // Discards the rawValue as this should be overridden and mapped appropriately + // by the subclass. + return [super init]; +} + +- (nonnull NSString *)toIncludeTypeParameterString { + NSAssert(NO, + @"toIncludeTypeParameterString should be overriden by the subclass"); + return @""; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVMonotonicSortOrder.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVMonotonicSortOrder.h new file mode 100644 index 00000000..ced47853 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVMonotonicSortOrder.h @@ -0,0 +1,16 @@ +// +// BVMonotonicSortOrder.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVMonotonicSortOrderValue.h" +#import "BVSortOrder.h" + +@interface BVMonotonicSortOrder : BVSortOrder + +- (nonnull instancetype)initWithMonotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVMonotonicSortOrder.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVMonotonicSortOrder.m new file mode 100644 index 00000000..4cb260ad --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVMonotonicSortOrder.m @@ -0,0 +1,48 @@ +// +// BVMonotonicSortOrder.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVMonotonicSortOrder.h" + +@interface BVMonotonicSortOrder () +@property(nonnull, nonatomic, strong) NSString *value; +@end + +@implementation BVMonotonicSortOrder + ++ (nonnull NSString *)toSortOrderParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVMonotonicSortOrder sortOrderWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)sortOrderWithRawValue:(NSInteger)rawValue { + return [[BVMonotonicSortOrder alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVMonotonicSortOrderValueAscending: + self.value = @"asc"; + break; + case BVMonotonicSortOrderValueDescending: + self.value = @"desc"; + break; + } + } + return self; +} + +- (nonnull NSString *)toSortOrderParameterString { + return self.value; +} + +- (nonnull instancetype)initWithMonotonicSortOrderValue: + (BVMonotonicSortOrderValue)monotonicSortOrderValue { + return [BVMonotonicSortOrder sortOrderWithRawValue:monotonicSortOrderValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductFilterType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductFilterType.h new file mode 100644 index 00000000..4d0ec890 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductFilterType.h @@ -0,0 +1,16 @@ +// +// BVProductFilterType.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVFilterType.h" +#import "BVProductFilterValue.h" + +@interface BVProductFilterType : BVFilterType + +- (nonnull instancetype)initWithProductFilterValue: + (BVProductFilterValue)productFilterValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductFilterType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductFilterType.m new file mode 100644 index 00000000..6fd6443e --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductFilterType.m @@ -0,0 +1,90 @@ +// +// BVProductFilterType.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVProductFilterType.h" + +@interface BVProductFilterType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVProductFilterType + ++ (nonnull NSString *)toFilterTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVProductFilterType filterTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)filterTypeWithRawValue:(NSInteger)rawValue { + return [[BVProductFilterType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVProductFilterValueProductId: + self.value = @"Id"; + break; + case BVProductFilterValueProductAverageOverallRating: + self.value = @"AverageOverallRating"; + break; + case BVProductFilterValueProductCategoryAncestorId: + self.value = @"CategoryAncestorId"; + break; + case BVProductFilterValueProductCategoryId: + self.value = @"CategoryId"; + break; + case BVProductFilterValueProductIsActive: + self.value = @"IsActive"; + break; + case BVProductFilterValueProductIsDisabled: + self.value = @"IsDisabled"; + break; + case BVProductFilterValueProductLastAnswerTime: + self.value = @"LastAnswerTime"; + break; + case BVProductFilterValueProductLastQuestionTime: + self.value = @"LastQuestionTime"; + break; + case BVProductFilterValueProductLastReviewTime: + self.value = @"LastReviewTime"; + break; + case BVProductFilterValueProductLastStoryTime: + self.value = @"LastStoryTime"; + break; + case BVProductFilterValueProductName: + self.value = @"Name"; + break; + case BVProductFilterValueProductRatingsOnlyReviewCount: + self.value = @"RatingsOnlyReviewCount"; + break; + case BVProductFilterValueProductTotalAnswerCount: + self.value = @"TotalAnswerCount"; + break; + case BVProductFilterValueProductTotalQuestionCount: + self.value = @"TotalQuestionCount"; + break; + case BVProductFilterValueProductTotalReviewCount: + self.value = @"TotalReviewCount"; + break; + case BVProductFilterValueProductTotalStoryCount: + self.value = @"TotalStoryCount"; + break; + } + } + return self; +} + +- (nonnull NSString *)toFilterTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithProductFilterValue: + (BVProductFilterValue)productFilterValue { + return [[BVProductFilterType alloc] initWithRawValue:productFilterValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductIncludeType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductIncludeType.h new file mode 100644 index 00000000..405a839d --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductIncludeType.h @@ -0,0 +1,16 @@ +// +// BVProductIncludeType.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVIncludeType.h" +#import "BVProductIncludeTypeValue.h" + +@interface BVProductIncludeType : BVIncludeType + +- (nonnull instancetype)initWithPDPIncludeTypeValue: + (BVProductIncludeTypeValue)pdpIncludeTypeValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductIncludeType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductIncludeType.m new file mode 100644 index 00000000..1ddfe0f2 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductIncludeType.m @@ -0,0 +1,51 @@ +// +// BVProductIncludeType.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVProductIncludeType.h" + +@interface BVProductIncludeType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVProductIncludeType + ++ (nonnull NSString *)toIncludeTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVProductIncludeType includeTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)includeTypeWithRawValue:(NSInteger)rawValue { + return [[BVProductIncludeType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVProductIncludeTypeValueReviews: + self.value = @"Reviews"; + break; + case BVProductIncludeTypeValueAnswers: + self.value = @"Answers"; + break; + case BVProductIncludeTypeValueQuestions: + self.value = @"Questions"; + break; + } + } + return self; +} + +- (nonnull NSString *)toIncludeTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithPDPIncludeTypeValue: + (BVProductIncludeTypeValue)pdpIncludeTypeValue { + return [BVProductIncludeType includeTypeWithRawValue:pdpIncludeTypeValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductsSortOption.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductsSortOption.h new file mode 100644 index 00000000..8d4c2457 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductsSortOption.h @@ -0,0 +1,16 @@ +// +// BVProductsSortOption.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVProductsSortOptionValue.h" +#import "BVSortOption.h" + +@interface BVProductsSortOption : BVSortOption + +- (nonnull instancetype)initWithProductsSortOptionValue: + (BVProductsSortOptionValue)productsSortOptionValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductsSortOption.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductsSortOption.m new file mode 100644 index 00000000..6ad15686 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVProductsSortOption.m @@ -0,0 +1,93 @@ +// +// BVProductsSortOption.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVProductsSortOption.h" + +@interface BVProductsSortOption () +@property(nonnull, nonatomic, strong) NSString *value; +@end + +@implementation BVProductsSortOption + ++ (nonnull NSString *)toSortOptionParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVProductsSortOption sortOptionWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)sortOptionWithRawValue:(NSInteger)rawValue { + return [[BVProductsSortOption alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVProductsSortOptionValueProductId: + self.value = @"Id"; + break; + case BVProductsSortOptionValueProductAverageOverallRating: + self.value = @"AverageOverallRating"; + break; + case BVProductsSortOptionValueProductRating: + self.value = @"Rating"; + break; + case BVProductsSortOptionValueProductCategoryId: + self.value = @"CategoryId"; + break; + case BVProductsSortOptionValueProductIsActive: + self.value = @"IsActive"; + break; + case BVProductsSortOptionValueProductIsDisabled: + self.value = @"IsDisabled"; + break; + case BVProductsSortOptionValueProductLastAnswerTime: + self.value = @"LastAnswerTime"; + break; + case BVProductsSortOptionValueProductLastQuestionTime: + self.value = @"LastQuestionTime"; + break; + case BVProductsSortOptionValueProductLastReviewTime: + self.value = @"LastReviewTime"; + break; + case BVProductsSortOptionValueProductLastStoryTime: + self.value = @"LastStoryTime"; + break; + case BVProductsSortOptionValueProductName: + self.value = @"Name"; + break; + case BVProductsSortOptionValueProductRatingsOnlyReviewCount: + self.value = @"RatingsOnlyReviewCount"; + break; + case BVProductsSortOptionValueProductTotalAnswerCount: + self.value = @"TotalAnswerCount"; + break; + case BVProductsSortOptionValueProductTotalQuestionCount: + self.value = @"TotalQuestionCount"; + break; + case BVProductsSortOptionValueProductTotalReviewCount: + self.value = @"TotalReviewCount"; + break; + case BVProductsSortOptionValueProductTotalStoryCount: + self.value = @"TotalStoryCount"; + break; + case BVProductsSortOptionValueProductHelpfulness: + self.value = @"Helpfulness"; + break; + } + } + return self; +} + +- (nonnull NSString *)toSortOptionParameterString { + return self.value; +} + +- (nonnull instancetype)initWithProductsSortOptionValue: + (BVProductsSortOptionValue)productsSortOptionValue { + return [BVProductsSortOption sortOptionWithRawValue:productsSortOptionValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionFilterType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionFilterType.h new file mode 100644 index 00000000..a6e37dcd --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionFilterType.h @@ -0,0 +1,16 @@ +// +// BVQuestionFilterType.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVFilterType.h" +#import "BVQuestionFilterValue.h" + +@interface BVQuestionFilterType : BVFilterType + +- (nonnull instancetype)initWithQuestionFilterValue: + (BVQuestionFilterValue)questionFilterValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionFilterType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionFilterType.m new file mode 100644 index 00000000..62d33e1d --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionFilterType.m @@ -0,0 +1,126 @@ +// +// BVQuestionFilterType.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVQuestionFilterType.h" + +@interface BVQuestionFilterType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVQuestionFilterType + ++ (nonnull NSString *)toFilterTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVQuestionFilterType filterTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)filterTypeWithRawValue:(NSInteger)rawValue { + return [[BVQuestionFilterType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVQuestionFilterValueQuestionId: + self.value = @"Id"; + break; + case BVQuestionFilterValueQuestionAuthorId: + self.value = @"AuthorId"; + break; + case BVQuestionFilterValueQuestionCampaignId: + self.value = @"CampaignId"; + break; + case BVQuestionFilterValueQuestionCategoryAncestorId: + self.value = @"CategoryAncestorId"; + break; + case BVQuestionFilterValueQuestionCategoryId: + self.value = @"CategoryId"; + break; + case BVQuestionFilterValueQuestionContentLocale: + self.value = @"ContentLocale"; + break; + case BVQuestionFilterValueQuestionHasAnswers: + self.value = @"HasAnswers"; + break; + case BVQuestionFilterValueQuestionHasBestAnswer: + self.value = @"HasBestAnswer"; + break; + case BVQuestionFilterValueQuestionHasBrandAnswers: + self.value = @"HasBrandAnswers"; + break; + case BVQuestionFilterValueQuestionHasPhotos: + self.value = @"HasPhotos"; + break; + case BVQuestionFilterValueQuestionHasStaffAnswers: + self.value = @"HasStaffAnswers"; + break; + case BVQuestionFilterValueQuestionHasTags: + self.value = @"HasTags"; + break; + case BVQuestionFilterValueQuestionHasVideos: + self.value = @"HasVideos"; + break; + case BVQuestionFilterValueQuestionIsFeatured: + self.value = @"IsFeatured"; + break; + case BVQuestionFilterValueQuestionIsSubjectActive: + self.value = @"IsSubjectActive"; + break; + case BVQuestionFilterValueQuestionLastApprovedAnswerSubmissionTime: + self.value = @"LastApprovedAnswerSubmissionTime"; + break; + case BVQuestionFilterValueQuestionLastModeratedTime: + self.value = @"LastModeratedTime"; + break; + case BVQuestionFilterValueQuestionLastModificationTime: + self.value = @"LastModificationTime"; + break; + case BVQuestionFilterValueQuestionModeratorCode: + self.value = @"ModeratorCode"; + break; + case BVQuestionFilterValueQuestionProductId: + self.value = @"ProductId"; + break; + case BVQuestionFilterValueQuestionSubmissionId: + self.value = @"SubmissionId"; + break; + case BVQuestionFilterValueQuestionSubmissionTime: + self.value = @"SubmissionTime"; + break; + case BVQuestionFilterValueQuestionSummary: + self.value = @"Summary"; + break; + case BVQuestionFilterValueQuestionTotalAnswerCount: + self.value = @"TotalAnswerCount"; + break; + case BVQuestionFilterValueQuestionTotalFeedbackCount: + self.value = @"TotalFeedbackCount"; + break; + case BVQuestionFilterValueQuestionTotalNegativeFeedbackCount: + self.value = @"TotalNegativeFeedbackCount"; + break; + case BVQuestionFilterValueQuestionTotalPositiveFeedbackCount: + self.value = @"TotalPositiveFeedbackCount"; + break; + case BVQuestionFilterValueQuestionUserLocation: + self.value = @"UserLocation"; + break; + } + } + return self; +} + +- (nonnull NSString *)toFilterTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithQuestionFilterValue: + (BVQuestionFilterValue)questionFilterValue { + return [BVQuestionFilterType filterTypeWithRawValue:questionFilterValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionsSortOption.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionsSortOption.h new file mode 100644 index 00000000..f617c30d --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionsSortOption.h @@ -0,0 +1,16 @@ +// +// BVQuestionsSortOption.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVQuestionsSortOptionValue.h" +#import "BVSortOption.h" + +@interface BVQuestionsSortOption : BVSortOption + +- (nonnull instancetype)initWithQuestionsSortOptionValue: + (BVQuestionsSortOptionValue)questionsSortOptionValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionsSortOption.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionsSortOption.m new file mode 100644 index 00000000..750df0d9 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVQuestionsSortOption.m @@ -0,0 +1,115 @@ +// +// BVQuestionsSortOption.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVQuestionsSortOption.h" + +@interface BVQuestionsSortOption () +@property(nonnull, nonatomic, strong) NSString *value; +@end + +@implementation BVQuestionsSortOption + ++ (nonnull NSString *)toSortOptionParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVQuestionsSortOption sortOptionWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)sortOptionWithRawValue:(NSInteger)rawValue { + return [[BVQuestionsSortOption alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVQuestionsSortOptionValueQuestionId: + self.value = @"Id"; + break; + case BVQuestionsSortOptionValueQuestionAuthorId: + self.value = @"AuthorId"; + break; + case BVQuestionsSortOptionValueQuestionCampaignId: + self.value = @"CampaignId"; + break; + case BVQuestionsSortOptionValueQuestionContentLocale: + self.value = @"ContentLocale"; + break; + case BVQuestionsSortOptionValueQuestionHasAnswers: + self.value = @"HasAnswers"; + break; + case BVQuestionsSortOptionValueQuestionHasBestAnswer: + self.value = @"HasBestAnswer"; + break; + case BVQuestionsSortOptionValueQuestionHasPhotos: + self.value = @"HasPhotos"; + break; + case BVQuestionsSortOptionValueQuestionHasStaffAnswers: + self.value = @"HasStaffAnswers"; + break; + case BVQuestionsSortOptionValueQuestionIsFeatured: + self.value = @"IsFeatured"; + break; + case BVQuestionsSortOptionValueQuestionIsSubjectActive: + self.value = @"IsSubjectActive"; + break; + case BVQuestionsSortOptionValueQuestionLastApprovedAnswerSubmissionTime: + self.value = @"LastApprovedAnswerSubmissionTime"; + break; + case BVQuestionsSortOptionValueQuestionLastModeratedTime: + self.value = @"LastModeratedTime"; + break; + case BVQuestionsSortOptionValueQuestionLastModificationTime: + self.value = @"LastModificationTime"; + break; + case BVQuestionsSortOptionValueQuestionModeratorCode: + self.value = @"ModeratorCode"; + break; + case BVQuestionsSortOptionValueQuestionProductId: + self.value = @"ProductId"; + break; + case BVQuestionsSortOptionValueQuestionSubmissionId: + self.value = @"SubmissionId"; + break; + case BVQuestionsSortOptionValueQuestionSubmissionTime: + self.value = @"SubmissionTime"; + break; + case BVQuestionsSortOptionValueQuestionSummary: + self.value = @"Summary"; + break; + case BVQuestionsSortOptionValueQuestionTotalAnswerCount: + self.value = @"TotalAnswerCount"; + break; + case BVQuestionsSortOptionValueQuestionTotalFeedbackCount: + self.value = @"TotalFeedbackCount"; + break; + case BVQuestionsSortOptionValueQuestionTotalNegativeFeedbackCount: + self.value = @"TotalNegativeFeedbackCount"; + break; + case BVQuestionsSortOptionValueQuestionTotalPositiveFeedbackCount: + self.value = @"TotalPositiveFeedbackCount"; + break; + case BVQuestionsSortOptionValueQuestionUserLocation: + self.value = @"UserLocation"; + break; + case BVQuestionsSortOptionValueQuestionHasVideos: + self.value = @"HasVideos"; + break; + } + } + return self; +} + +- (nonnull NSString *)toSortOptionParameterString { + return self.value; +} + +- (nonnull instancetype)initWithQuestionsSortOptionValue: + (BVQuestionsSortOptionValue)questionsSortOptionValue { + return + [BVQuestionsSortOption sortOptionWithRawValue:questionsSortOptionValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVRelationalFilterOperator.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVRelationalFilterOperator.h new file mode 100644 index 00000000..37b710d9 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVRelationalFilterOperator.h @@ -0,0 +1,16 @@ +// +// BVRelationalFilterOperator.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVFilterOperator.h" +#import "BVRelationalFilterOperatorValue.h" + +@interface BVRelationalFilterOperator : BVFilterOperator + +- (nonnull instancetype)initWithRelationalFilterValue: + (BVRelationalFilterOperatorValue)relationalFilterValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVRelationalFilterOperator.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVRelationalFilterOperator.m new file mode 100644 index 00000000..a241dc00 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVRelationalFilterOperator.m @@ -0,0 +1,61 @@ +// +// BVRelationalFilterOperator.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVRelationalFilterOperator.h" + +@interface BVRelationalFilterOperator () +@property(nonnull, nonatomic, strong) NSString *value; +@end + +@implementation BVRelationalFilterOperator + ++ (nonnull NSString *)toFilterOperatorParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVRelationalFilterOperator filterOperatorWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)filterOperatorWithRawValue:(NSInteger)rawValue { + return [[BVRelationalFilterOperator alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVRelationalFilterOperatorValueGreaterThan: + self.value = @"gt"; + break; + case BVRelationalFilterOperatorValueGreaterThanOrEqualTo: + self.value = @"gte"; + break; + case BVRelationalFilterOperatorValueLessThan: + self.value = @"lt"; + break; + case BVRelationalFilterOperatorValueLessThanOrEqualTo: + self.value = @"lte"; + break; + case BVRelationalFilterOperatorValueEqualTo: + self.value = @"eq"; + break; + case BVRelationalFilterOperatorValueNotEqualTo: + self.value = @"neq"; + break; + } + } + return self; +} + +- (nonnull NSString *)toFilterOperatorParameterString { + return self.value; +} + +- (nonnull instancetype)initWithRelationalFilterValue: + (BVRelationalFilterOperatorValue)relationalFilterValue { + return [BVRelationalFilterOperator + filterOperatorWithRawValue:relationalFilterValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewFilterType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewFilterType.h new file mode 100644 index 00000000..1664fc6e --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewFilterType.h @@ -0,0 +1,16 @@ +// +// BVReviewFilterType.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVFilterType.h" +#import "BVReviewFilterValue.h" + +@interface BVReviewFilterType : BVFilterType + +- (nonnull instancetype)initWithReviewFilterValue: + (BVReviewFilterValue)reviewFilterValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewFilterType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewFilterType.m new file mode 100644 index 00000000..25c03875 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewFilterType.m @@ -0,0 +1,120 @@ +// +// BVReviewFilterType.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVReviewFilterType.h" + +@interface BVReviewFilterType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVReviewFilterType + ++ (nonnull NSString *)toFilterTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVReviewFilterType filterTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)filterTypeWithRawValue:(NSInteger)rawValue { + return [[BVReviewFilterType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVReviewFilterValueId: + self.value = @"Id"; + break; + case BVReviewFilterValueAuthorId: + self.value = @"AuthorId"; + break; + case BVReviewFilterValueCampaignId: + self.value = @"CampaignId"; + break; + case BVReviewFilterValueCategoryAncestorId: + self.value = @"CategoryAncestorId"; + break; + case BVReviewFilterValueContentLocale: + self.value = @"ContentLocale"; + break; + case BVReviewFilterValueHasComments: + self.value = @"HasComments"; + break; + case BVReviewFilterValueHasPhotos: + self.value = @"HasPhotos"; + break; + case BVReviewFilterValueHasTags: + self.value = @"HasTags"; + break; + case BVReviewFilterValueHasVideos: + self.value = @"HasVideos"; + break; + case BVReviewFilterValueIsFeatured: + self.value = @"IsFeatured"; + break; + case BVReviewFilterValueIsRatingsOnly: + self.value = @"IsRatingsOnly"; + break; + case BVReviewFilterValueIsRecommended: + self.value = @"IsRecommended"; + break; + case BVReviewFilterValueIsSubjectActive: + self.value = @"IsSubjectActive"; + break; + case BVReviewFilterValueIsSyndicated: + self.value = @"IsSyndicated"; + break; + case BVReviewFilterValueLastModeratedTime: + self.value = @"LastModeratedTime"; + break; + case BVReviewFilterValueLastModificationTime: + self.value = @"LastModificationTime"; + break; + case BVReviewFilterValueModeratorCode: + self.value = @"ModeratorCode"; + break; + case BVReviewFilterValueProductId: + self.value = @"ProductId"; + break; + case BVReviewFilterValueRating: + self.value = @"Rating"; + break; + case BVReviewFilterValueSubmissionId: + self.value = @"SubmissionId"; + break; + case BVReviewFilterValueSubmissionTime: + self.value = @"SubmissionTime"; + break; + case BVReviewFilterValueTotalCommentCount: + self.value = @"TotalCommentCount"; + break; + case BVReviewFilterValueTotalFeedbackCount: + self.value = @"TotalFeedbackCount"; + break; + case BVReviewFilterValueTotalNegativeFeedbackCount: + self.value = @"TotalNegativeFeedbackCount"; + break; + case BVReviewFilterValueTotalPositiveFeedbackCount: + self.value = @"TotalPositiveFeedbackCount"; + break; + case BVReviewFilterValueUserLocation: + self.value = @"UserLocation"; + break; + } + } + return self; +} + +- (nonnull NSString *)toFilterTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithReviewFilterValue: + (BVReviewFilterValue)reviewFilterValue { + return [BVReviewFilterType filterTypeWithRawValue:reviewFilterValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewIncludeType.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewIncludeType.h new file mode 100644 index 00000000..088cbb62 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewIncludeType.h @@ -0,0 +1,16 @@ +// +// BVReviewIncludeType.h +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVIncludeType.h" +#import "BVReviewIncludeTypeValue.h" + +@interface BVReviewIncludeType : BVIncludeType + +- (nonnull instancetype)initWithReviewIncludeTypeValue: + (BVReviewIncludeTypeValue)reviewIncludeTypeValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewIncludeType.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewIncludeType.m new file mode 100644 index 00000000..075c9af3 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewIncludeType.m @@ -0,0 +1,48 @@ +// +// BVReviewIncludeType.m +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +#import "BVReviewIncludeType.h" + +@interface BVReviewIncludeType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVReviewIncludeType + ++ (nonnull NSString *)toIncludeTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVReviewIncludeType includeTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)includeTypeWithRawValue:(NSInteger)rawValue { + return [[BVReviewIncludeType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVReviewIncludeTypeValueReviewProducts: + self.value = @"Products"; + break; + case BVReviewIncludeTypeValueReviewComments: + self.value = @"Comments"; + break; + } + } + return self; +} + +- (nonnull NSString *)toIncludeTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithReviewIncludeTypeValue: + (BVReviewIncludeTypeValue)reviewIncludeTypeValue { + return [BVReviewIncludeType includeTypeWithRawValue:reviewIncludeTypeValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewsSortOption.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewsSortOption.h new file mode 100644 index 00000000..3c19c89c --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewsSortOption.h @@ -0,0 +1,16 @@ +// +// BVReviewsSortOption.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVReviewsSortOptionValue.h" +#import "BVSortOption.h" + +@interface BVReviewsSortOption : BVSortOption + +- (nonnull instancetype)initWithReviewsSortOptionValue: + (BVReviewsSortOptionValue)reviewsSortOptionValue; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewsSortOption.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewsSortOption.m new file mode 100644 index 00000000..1622ae8e --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVReviewsSortOption.m @@ -0,0 +1,117 @@ +// +// BVReviewsSortOption.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVReviewsSortOption.h" + +@interface BVReviewsSortOption () +@property(nonnull, nonatomic, strong) NSString *value; +@end + +@implementation BVReviewsSortOption + ++ (nonnull NSString *)toSortOptionParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVReviewsSortOption sortOptionWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)sortOptionWithRawValue:(NSInteger)rawValue { + return [[BVReviewsSortOption alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVReviewsSortOptionValueReviewId: + self.value = @"Id"; + break; + case BVReviewsSortOptionValueReviewAuthorId: + self.value = @"AuthorId"; + break; + case BVReviewsSortOptionValueReviewCampaignId: + self.value = @"CampaignId"; + break; + case BVReviewsSortOptionValueReviewContentLocale: + self.value = @"ContentLocale"; + break; + case BVReviewsSortOptionValueReviewHasComments: + self.value = @"HasComments"; + break; + case BVReviewsSortOptionValueReviewHasPhotos: + self.value = @"HasPhotos"; + break; + case BVReviewsSortOptionValueReviewHasTags: + self.value = @"HasTags"; + break; + case BVReviewsSortOptionValueReviewHasVideos: + self.value = @"HasVideos"; + break; + case BVReviewsSortOptionValueReviewHelpfulness: + self.value = @"Helpfulness"; + break; + case BVReviewsSortOptionValueReviewIsFeatured: + self.value = @"IsFeatured"; + break; + case BVReviewsSortOptionValueReviewIsRatingsOnly: + self.value = @"IsRatingsOnly"; + break; + case BVReviewsSortOptionValueReviewIsRecommended: + self.value = @"IsRecommended"; + break; + case BVReviewsSortOptionValueReviewIsSubjectActive: + self.value = @"IsSubjectActive"; + break; + case BVReviewsSortOptionValueReviewIsSyndicated: + self.value = @"IsSyndicated"; + break; + case BVReviewsSortOptionValueReviewLastModeratedTime: + self.value = @"LastModeratedTime"; + break; + case BVReviewsSortOptionValueReviewLastModificationTime: + self.value = @"LastModificationTime"; + break; + case BVReviewsSortOptionValueReviewProductId: + self.value = @"ProductId"; + break; + case BVReviewsSortOptionValueReviewRating: + self.value = @"Rating"; + break; + case BVReviewsSortOptionValueReviewSubmissionId: + self.value = @"SubmissionId"; + break; + case BVReviewsSortOptionValueReviewSubmissionTime: + self.value = @"SubmissionTime"; + break; + case BVReviewsSortOptionValueReviewTotalCommentCount: + self.value = @"TotalCommentCount"; + break; + case BVReviewsSortOptionValueReviewTotalFeedbackCount: + self.value = @"TotalFeedbackCount"; + break; + case BVReviewsSortOptionValueReviewTotalNegativeFeedbackCount: + self.value = @"TotalNegativeFeedbackCount"; + break; + case BVReviewsSortOptionValueReviewTotalPositiveFeedbackCount: + self.value = @"TotalPositiveFeedbackCount"; + break; + case BVReviewsSortOptionValueReviewUserLocation: + self.value = @"UserLocation"; + break; + } + } + return self; +} + +- (nonnull NSString *)toSortOptionParameterString { + return self.value; +} + +- (nonnull instancetype)initWithReviewsSortOptionValue: + (BVReviewsSortOptionValue)reviewsSortOptionValue { + return [BVReviewsSortOption sortOptionWithRawValue:reviewsSortOptionValue]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSort.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSort.h new file mode 100644 index 00000000..7613d9af --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSort.h @@ -0,0 +1,31 @@ +// +// Sorts.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import + +@protocol BVSortOptionProtocol ++ (nonnull NSString *)toSortOptionParameterStringWithRawValue: + (NSInteger)rawValue; +- (nonnull NSString *)toSortOptionParameterString; +@end + +@protocol BVSortOrderProtocol ++ (nonnull NSString *)toSortOrderParameterStringWithRawValue: + (NSInteger)rawValue; +- (nonnull NSString *)toSortOrderParameterString; +@end + +@interface BVSort : NSObject + +- (nonnull id)initWithSortOption:(nonnull id)sortOption + sortOrder:(nonnull id)sortOrder; +- (nonnull id)initWithSortOptionString:(nonnull NSString *)sortOptionString + sortOrder: + (nonnull id)sortOrder; +- (nonnull NSString *)toParameterString; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSort.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSort.m new file mode 100644 index 00000000..dae02500 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSort.m @@ -0,0 +1,42 @@ +// +// Sorts.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVSort.h" + +@interface BVSort () + +@property(nonnull) NSString *sortOption; +@property(nonnull) NSString *sortOrder; + +@end + +@implementation BVSort + +- (nonnull id)initWithSortOption:(nonnull id)sortOption + sortOrder:(nonnull id)sortOrder { + if ((self = [super init])) { + self.sortOption = [sortOption toSortOptionParameterString]; + self.sortOrder = [sortOrder toSortOrderParameterString]; + } + return self; +} + +- (nonnull id)initWithSortOptionString:(nonnull NSString *)sortOptionString + sortOrder: + (nonnull id)sortOrder { + if ((self = [super init])) { + self.sortOption = sortOptionString; + self.sortOrder = [sortOrder toSortOrderParameterString]; + } + return self; +} + +- (nonnull NSString *)toParameterString { + return [NSString stringWithFormat:@"%@:%@", self.sortOption, self.sortOrder]; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOption.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOption.h new file mode 100644 index 00000000..db4b5e3e --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOption.h @@ -0,0 +1,16 @@ +// +// BVSortOption.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVSort.h" + +@interface BVSortOption : NSObject + ++ (nonnull instancetype)sortOptionWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)__unavailable init; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOption.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOption.m new file mode 100644 index 00000000..099819c4 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOption.m @@ -0,0 +1,36 @@ +// +// BVSortOption.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVSortOption.h" + +@implementation BVSortOption + ++ (nonnull instancetype)sortOptionWithRawValue:(NSInteger)rawValue { + NSAssert(NO, @"sortOptionWithRawValue: should be overriden by the subclass"); + return [super init]; +} + ++ (nonnull NSString *)toSortOptionParameterStringWithRawValue: + (NSInteger)rawValue { + NSAssert(NO, @"toSortOptionParameterStringWithRawValue: should be " + @"overriden by the subclass"); + return @""; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + // Discards the rawValue as this should be overridden and mapped appropriately + // by the subclass. + return [super init]; +} + +- (nonnull NSString *)toSortOptionParameterString { + NSAssert(NO, + @"toSortOptionParameterString should be overriden by the subclass"); + return @""; +} + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOrder.h b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOrder.h new file mode 100644 index 00000000..6f580dfd --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOrder.h @@ -0,0 +1,16 @@ +// +// BVSortOrder.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVSort.h" + +@interface BVSortOrder : NSObject + ++ (nonnull instancetype)sortOrderWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue; +- (nonnull instancetype)__unavailable init; + +@end diff --git a/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOrder.m b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOrder.m new file mode 100644 index 00000000..19a54c82 --- /dev/null +++ b/BVSDK/BVConversations/Display/Sorting & Filtering/Private/BVSortOrder.m @@ -0,0 +1,36 @@ +// +// BVSortOrder.m +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVSortOrder.h" + +@implementation BVSortOrder + ++ (nonnull instancetype)sortOrderWithRawValue:(NSInteger)rawValue { + NSAssert(NO, @"sortOrderWithRawValue: should be overriden by the subclass"); + return [super init]; +} + ++ (nonnull NSString *)toSortOrderParameterStringWithRawValue: + (NSInteger)rawValue { + NSAssert(NO, @"toSortOrderParameterStringWithRawValue: should be " + @"overriden by the subclass"); + return @""; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + // Discards the rawValue as this should be overridden and mapped appropriately + // by the subclass. + return [super init]; +} + +- (nonnull NSString *)toSortOrderParameterString { + NSAssert(NO, + @"toSortOrderParameterString should be overriden by the subclass"); + return @""; +} + +@end diff --git a/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.h b/BVSDK/BVConversations/Submission/Answer/BVAnswerSubmission.h similarity index 100% rename from Pod/BVConversations/Submission/Answer/BVAnswerSubmission.h rename to BVSDK/BVConversations/Submission/Answer/BVAnswerSubmission.h diff --git a/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.m b/BVSDK/BVConversations/Submission/Answer/BVAnswerSubmission.m similarity index 92% rename from Pod/BVConversations/Submission/Answer/BVAnswerSubmission.m rename to BVSDK/BVConversations/Submission/Answer/BVAnswerSubmission.m index ff09bd7d..47fc8d66 100644 --- a/Pod/BVConversations/Submission/Answer/BVAnswerSubmission.m +++ b/BVSDK/BVConversations/Submission/Answer/BVAnswerSubmission.m @@ -8,6 +8,7 @@ #import "BVAnswerSubmission.h" #import "BVAnswerSubmissionErrorResponse.h" #import "BVCommon.h" +#import "BVNetworkingManager.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" #import "BVUploadablePhoto.h" @@ -136,7 +137,16 @@ - (void)submitAnswerWithPhotoUrls:(BVSubmissionAction)action verbose:[NSString stringWithFormat:@"POST: %@\n with BODY: %@", urlString, parameters]]; - NSURLSession *session = [NSURLSession sharedSession]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, @@ -217,6 +227,14 @@ - (void)submitAnswerWithPhotoUrls:(BVSubmissionAction)action // start uploading answer [postDataTask resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:postDataTask + fromBVObject:self + withURLSession:session]; + } } - (nonnull NSDictionary *) @@ -248,17 +266,17 @@ - (void)submitAnswerWithPhotoUrls:(BVSubmissionAction)action parameters[@"userid"] = self.userId; parameters[@"userlocation"] = self.userLocation; - int photoIndex = 0; + NSUInteger photoIndex = 0; for (NSString *url in photoUrls) { - NSString *key = [NSString stringWithFormat:@"photourl_%i", photoIndex]; + NSString *key = [NSString stringWithFormat:@"photourl_%i", (int)photoIndex]; parameters[key] = url; photoIndex += 1; } - int captionIndex = 0; + NSUInteger captionIndex = 0; for (NSString *caption in photoCaptions) { NSString *key = - [NSString stringWithFormat:@"photocaption_%i", captionIndex]; + [NSString stringWithFormat:@"photocaption_%i", (int)captionIndex]; parameters[key] = caption; captionIndex += 1; } diff --git a/Pod/BVConversations/Submission/Answer/BVAnswerSubmissionErrorResponse.h b/BVSDK/BVConversations/Submission/Answer/BVAnswerSubmissionErrorResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Answer/BVAnswerSubmissionErrorResponse.h rename to BVSDK/BVConversations/Submission/Answer/BVAnswerSubmissionErrorResponse.h diff --git a/Pod/BVConversations/Submission/Answer/BVAnswerSubmissionErrorResponse.m b/BVSDK/BVConversations/Submission/Answer/BVAnswerSubmissionErrorResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Answer/BVAnswerSubmissionErrorResponse.m rename to BVSDK/BVConversations/Submission/Answer/BVAnswerSubmissionErrorResponse.m diff --git a/Pod/BVConversations/Submission/Answer/BVAnswerSubmissionResponse.h b/BVSDK/BVConversations/Submission/Answer/BVAnswerSubmissionResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Answer/BVAnswerSubmissionResponse.h rename to BVSDK/BVConversations/Submission/Answer/BVAnswerSubmissionResponse.h diff --git a/Pod/BVConversations/Submission/Answer/BVAnswerSubmissionResponse.m b/BVSDK/BVConversations/Submission/Answer/BVAnswerSubmissionResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Answer/BVAnswerSubmissionResponse.m rename to BVSDK/BVConversations/Submission/Answer/BVAnswerSubmissionResponse.m diff --git a/Pod/BVConversations/Submission/Answer/BVSubmittedAnswer.h b/BVSDK/BVConversations/Submission/Answer/BVSubmittedAnswer.h similarity index 100% rename from Pod/BVConversations/Submission/Answer/BVSubmittedAnswer.h rename to BVSDK/BVConversations/Submission/Answer/BVSubmittedAnswer.h diff --git a/Pod/BVConversations/Submission/Answer/BVSubmittedAnswer.m b/BVSDK/BVConversations/Submission/Answer/BVSubmittedAnswer.m similarity index 100% rename from Pod/BVConversations/Submission/Answer/BVSubmittedAnswer.m rename to BVSDK/BVConversations/Submission/Answer/BVSubmittedAnswer.m diff --git a/Pod/BVConversations/Submission/Auth/BVSubmittedUAS.h b/BVSDK/BVConversations/Submission/Auth/BVSubmittedUAS.h similarity index 100% rename from Pod/BVConversations/Submission/Auth/BVSubmittedUAS.h rename to BVSDK/BVConversations/Submission/Auth/BVSubmittedUAS.h diff --git a/Pod/BVConversations/Submission/Auth/BVSubmittedUAS.m b/BVSDK/BVConversations/Submission/Auth/BVSubmittedUAS.m similarity index 100% rename from Pod/BVConversations/Submission/Auth/BVSubmittedUAS.m rename to BVSDK/BVConversations/Submission/Auth/BVSubmittedUAS.m diff --git a/Pod/BVConversations/Submission/Auth/BVUASSubmission.h b/BVSDK/BVConversations/Submission/Auth/BVUASSubmission.h similarity index 100% rename from Pod/BVConversations/Submission/Auth/BVUASSubmission.h rename to BVSDK/BVConversations/Submission/Auth/BVUASSubmission.h diff --git a/Pod/BVConversations/Submission/Auth/BVUASSubmission.m b/BVSDK/BVConversations/Submission/Auth/BVUASSubmission.m similarity index 87% rename from Pod/BVConversations/Submission/Auth/BVUASSubmission.m rename to BVSDK/BVConversations/Submission/Auth/BVUASSubmission.m index 6193a518..edfbeac4 100644 --- a/Pod/BVConversations/Submission/Auth/BVUASSubmission.m +++ b/BVSDK/BVConversations/Submission/Auth/BVUASSubmission.m @@ -8,6 +8,7 @@ #import "BVUASSubmission.h" #import "BVCommon.h" #import "BVDiagnosticHelpers.h" +#import "BVNetworkingManager.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" #import "BVUASSubmissionErrorResponse.h" @@ -46,7 +47,16 @@ - (void)submit:(nonnull UASSubmissionCompletion)success verbose:[NSString stringWithFormat:@"POST: %@\n with BODY: %@", urlString, parameters]]; - NSURLSession *session = [NSURLSession sharedSession]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, @@ -127,6 +137,14 @@ - (void)submit:(nonnull UASSubmissionCompletion)success // start submission for UAS [postDataTask resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:postDataTask + fromBVObject:self + withURLSession:session]; + } } - (nonnull NSDictionary *)createUASSubmissionParameters { diff --git a/Pod/BVConversations/Submission/Auth/BVUASSubmissionErrorResponse.h b/BVSDK/BVConversations/Submission/Auth/BVUASSubmissionErrorResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Auth/BVUASSubmissionErrorResponse.h rename to BVSDK/BVConversations/Submission/Auth/BVUASSubmissionErrorResponse.h diff --git a/Pod/BVConversations/Submission/Auth/BVUASSubmissionErrorResponse.m b/BVSDK/BVConversations/Submission/Auth/BVUASSubmissionErrorResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Auth/BVUASSubmissionErrorResponse.m rename to BVSDK/BVConversations/Submission/Auth/BVUASSubmissionErrorResponse.m diff --git a/Pod/BVConversations/Submission/Auth/BVUASSubmissionResponse.h b/BVSDK/BVConversations/Submission/Auth/BVUASSubmissionResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Auth/BVUASSubmissionResponse.h rename to BVSDK/BVConversations/Submission/Auth/BVUASSubmissionResponse.h diff --git a/Pod/BVConversations/Submission/Auth/BVUASSubmissionResponse.m b/BVSDK/BVConversations/Submission/Auth/BVUASSubmissionResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Auth/BVUASSubmissionResponse.m rename to BVSDK/BVConversations/Submission/Auth/BVUASSubmissionResponse.m diff --git a/Pod/BVConversations/Submission/BVBaseUGCSubmission.h b/BVSDK/BVConversations/Submission/BVBaseUGCSubmission.h similarity index 100% rename from Pod/BVConversations/Submission/BVBaseUGCSubmission.h rename to BVSDK/BVConversations/Submission/BVBaseUGCSubmission.h diff --git a/Pod/BVConversations/Submission/BVBaseUGCSubmission.m b/BVSDK/BVConversations/Submission/BVBaseUGCSubmission.m similarity index 100% rename from Pod/BVConversations/Submission/BVBaseUGCSubmission.m rename to BVSDK/BVConversations/Submission/BVBaseUGCSubmission.m diff --git a/Pod/BVConversations/Submission/BVFieldError.h b/BVSDK/BVConversations/Submission/BVFieldError.h similarity index 100% rename from Pod/BVConversations/Submission/BVFieldError.h rename to BVSDK/BVConversations/Submission/BVFieldError.h diff --git a/Pod/BVConversations/Submission/BVFieldError.m b/BVSDK/BVConversations/Submission/BVFieldError.m similarity index 100% rename from Pod/BVConversations/Submission/BVFieldError.m rename to BVSDK/BVConversations/Submission/BVFieldError.m diff --git a/Pod/BVConversations/Submission/BVFormField.h b/BVSDK/BVConversations/Submission/BVFormField.h similarity index 100% rename from Pod/BVConversations/Submission/BVFormField.h rename to BVSDK/BVConversations/Submission/BVFormField.h diff --git a/Pod/BVConversations/Submission/BVFormField.m b/BVSDK/BVConversations/Submission/BVFormField.m similarity index 100% rename from Pod/BVConversations/Submission/BVFormField.m rename to BVSDK/BVConversations/Submission/BVFormField.m diff --git a/Pod/BVConversations/Submission/BVFormFieldOptions.h b/BVSDK/BVConversations/Submission/BVFormFieldOptions.h similarity index 100% rename from Pod/BVConversations/Submission/BVFormFieldOptions.h rename to BVSDK/BVConversations/Submission/BVFormFieldOptions.h diff --git a/Pod/BVConversations/Submission/BVFormFieldOptions.m b/BVSDK/BVConversations/Submission/BVFormFieldOptions.m similarity index 100% rename from Pod/BVConversations/Submission/BVFormFieldOptions.m rename to BVSDK/BVConversations/Submission/BVFormFieldOptions.m diff --git a/Pod/BVConversations/Submission/BVFormInputType.h b/BVSDK/BVConversations/Submission/BVFormInputType.h similarity index 100% rename from Pod/BVConversations/Submission/BVFormInputType.h rename to BVSDK/BVConversations/Submission/BVFormInputType.h diff --git a/Pod/BVConversations/Submission/BVFormInputType.m b/BVSDK/BVConversations/Submission/BVFormInputType.m similarity index 100% rename from Pod/BVConversations/Submission/BVFormInputType.m rename to BVSDK/BVConversations/Submission/BVFormInputType.m diff --git a/Pod/BVConversations/Submission/BVSubmission.h b/BVSDK/BVConversations/Submission/BVSubmission.h similarity index 100% rename from Pod/BVConversations/Submission/BVSubmission.h rename to BVSDK/BVConversations/Submission/BVSubmission.h diff --git a/Pod/BVConversations/Submission/BVSubmission.m b/BVSDK/BVConversations/Submission/BVSubmission.m similarity index 95% rename from Pod/BVConversations/Submission/BVSubmission.m rename to BVSDK/BVConversations/Submission/BVSubmission.m index 224227cd..d7ea7c80 100644 --- a/Pod/BVConversations/Submission/BVSubmission.m +++ b/BVSDK/BVConversations/Submission/BVSubmission.m @@ -9,8 +9,15 @@ #import "BVSubmission.h" #import "BVLogger.h" +@interface BVSubmission () +@end + @implementation BVSubmission +- (instancetype)init { + return ((self = [super init])); +} + - (void)sendError:(nonnull NSError *)error failureCallback:(ConversationsFailureHandler)failure { [[BVLogger sharedLogger] printError:error]; diff --git a/Pod/BVConversations/Submission/BVSubmissionAction.h b/BVSDK/BVConversations/Submission/BVSubmissionAction.h similarity index 100% rename from Pod/BVConversations/Submission/BVSubmissionAction.h rename to BVSDK/BVConversations/Submission/BVSubmissionAction.h diff --git a/Pod/BVConversations/Submission/BVSubmissionAction.m b/BVSDK/BVConversations/Submission/BVSubmissionAction.m similarity index 100% rename from Pod/BVConversations/Submission/BVSubmissionAction.m rename to BVSDK/BVConversations/Submission/BVSubmissionAction.m diff --git a/Pod/BVConversations/Submission/BVSubmissionErrorCode.h b/BVSDK/BVConversations/Submission/BVSubmissionErrorCode.h similarity index 100% rename from Pod/BVConversations/Submission/BVSubmissionErrorCode.h rename to BVSDK/BVConversations/Submission/BVSubmissionErrorCode.h diff --git a/Pod/BVConversations/Submission/BVSubmissionErrorResponse.h b/BVSDK/BVConversations/Submission/BVSubmissionErrorResponse.h similarity index 100% rename from Pod/BVConversations/Submission/BVSubmissionErrorResponse.h rename to BVSDK/BVConversations/Submission/BVSubmissionErrorResponse.h diff --git a/Pod/BVConversations/Submission/BVSubmissionErrorResponse.m b/BVSDK/BVConversations/Submission/BVSubmissionErrorResponse.m similarity index 100% rename from Pod/BVConversations/Submission/BVSubmissionErrorResponse.m rename to BVSDK/BVConversations/Submission/BVSubmissionErrorResponse.m diff --git a/Pod/BVConversations/Submission/BVSubmissionResponse.h b/BVSDK/BVConversations/Submission/BVSubmissionResponse.h similarity index 100% rename from Pod/BVConversations/Submission/BVSubmissionResponse.h rename to BVSDK/BVConversations/Submission/BVSubmissionResponse.h diff --git a/Pod/BVConversations/Submission/BVSubmissionResponse.m b/BVSDK/BVConversations/Submission/BVSubmissionResponse.m similarity index 100% rename from Pod/BVConversations/Submission/BVSubmissionResponse.m rename to BVSDK/BVConversations/Submission/BVSubmissionResponse.m diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmission.h b/BVSDK/BVConversations/Submission/Comment/BVCommentSubmission.h similarity index 100% rename from Pod/BVConversations/Submission/Comment/BVCommentSubmission.h rename to BVSDK/BVConversations/Submission/Comment/BVCommentSubmission.h diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmission.m b/BVSDK/BVConversations/Submission/Comment/BVCommentSubmission.m similarity index 92% rename from Pod/BVConversations/Submission/Comment/BVCommentSubmission.m rename to BVSDK/BVConversations/Submission/Comment/BVCommentSubmission.m index 07ce34f7..92854a57 100644 --- a/Pod/BVConversations/Submission/Comment/BVCommentSubmission.m +++ b/BVSDK/BVConversations/Submission/Comment/BVCommentSubmission.m @@ -7,6 +7,7 @@ #import "BVCommentSubmission.h" #import "BVCommentSubmissionErrorResponse.h" +#import "BVNetworkingManager.h" #import "BVSDKConfiguration.h" #import "BVSubmissionErrorResponse.h" #import "BVUploadablePhoto.h" @@ -136,7 +137,16 @@ - (void)submitCommentWithPhotoUrls:(BVSubmissionAction)action verbose:[NSString stringWithFormat:@"POST: %@\n with BODY: %@", urlString, parameters]]; - NSURLSession *session = [NSURLSession sharedSession]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, @@ -217,6 +227,14 @@ - (void)submitCommentWithPhotoUrls:(BVSubmissionAction)action // start uploading comment [postDataTask resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:postDataTask + fromBVObject:self + withURLSession:session]; + } } - (nonnull NSDictionary *) @@ -250,17 +268,17 @@ - (void)submitCommentWithPhotoUrls:(BVSubmissionAction)action parameters[@"userid"] = self.userId; parameters[@"userlocation"] = self.userLocation; - int photoIndex = 0; + NSUInteger photoIndex = 0; for (NSString *url in photoUrls) { - NSString *key = [NSString stringWithFormat:@"photourl_%i", photoIndex]; + NSString *key = [NSString stringWithFormat:@"photourl_%i", (int)photoIndex]; parameters[key] = url; photoIndex += 1; } - int captionIndex = 0; + NSUInteger captionIndex = 0; for (NSString *caption in photoCaptions) { NSString *key = - [NSString stringWithFormat:@"photocaption_%i", captionIndex]; + [NSString stringWithFormat:@"photocaption_%i", (int)captionIndex]; parameters[key] = caption; captionIndex += 1; } diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.h b/BVSDK/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.h rename to BVSDK/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.h diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.m b/BVSDK/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.m rename to BVSDK/BVConversations/Submission/Comment/BVCommentSubmissionErrorResponse.m diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.h b/BVSDK/BVConversations/Submission/Comment/BVCommentSubmissionResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.h rename to BVSDK/BVConversations/Submission/Comment/BVCommentSubmissionResponse.h diff --git a/Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.m b/BVSDK/BVConversations/Submission/Comment/BVCommentSubmissionResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Comment/BVCommentSubmissionResponse.m rename to BVSDK/BVConversations/Submission/Comment/BVCommentSubmissionResponse.m diff --git a/Pod/BVConversations/Submission/Comment/BVSubmittedComment.h b/BVSDK/BVConversations/Submission/Comment/BVSubmittedComment.h similarity index 100% rename from Pod/BVConversations/Submission/Comment/BVSubmittedComment.h rename to BVSDK/BVConversations/Submission/Comment/BVSubmittedComment.h diff --git a/Pod/BVConversations/Submission/Comment/BVSubmittedComment.m b/BVSDK/BVConversations/Submission/Comment/BVSubmittedComment.m similarity index 100% rename from Pod/BVConversations/Submission/Comment/BVSubmittedComment.m rename to BVSDK/BVConversations/Submission/Comment/BVSubmittedComment.m diff --git a/Pod/BVConversations/Submission/Feedback/BVFeedbackSubmission.h b/BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmission.h similarity index 100% rename from Pod/BVConversations/Submission/Feedback/BVFeedbackSubmission.h rename to BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmission.h diff --git a/Pod/BVConversations/Submission/Feedback/BVFeedbackSubmission.m b/BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmission.m similarity index 91% rename from Pod/BVConversations/Submission/Feedback/BVFeedbackSubmission.m rename to BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmission.m index 67488d43..30853050 100644 --- a/Pod/BVConversations/Submission/Feedback/BVFeedbackSubmission.m +++ b/BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmission.m @@ -6,6 +6,7 @@ // #import "BVFeedbackSubmission.h" +#import "BVNetworkingManager.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" @@ -53,7 +54,16 @@ - (void)submit:(nonnull FeedbackSubmissionCompletion)success verbose:[NSString stringWithFormat:@"POST: %@\n with BODY: %@", urlString, parameters]]; - NSURLSession *session = [NSURLSession sharedSession]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, @@ -125,6 +135,14 @@ - (void)submit:(nonnull FeedbackSubmissionCompletion)success // start uploading answer [postDataTask resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:postDataTask + fromBVObject:self + withURLSession:session]; + } } - (void)trackFeedbackEvent { diff --git a/Pod/BVConversations/Submission/Feedback/BVFeedbackSubmissionResponse.h b/BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmissionResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Feedback/BVFeedbackSubmissionResponse.h rename to BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmissionResponse.h diff --git a/Pod/BVConversations/Submission/Feedback/BVFeedbackSubmissionResponse.m b/BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmissionResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Feedback/BVFeedbackSubmissionResponse.m rename to BVSDK/BVConversations/Submission/Feedback/BVFeedbackSubmissionResponse.m diff --git a/Pod/BVConversations/Submission/Feedback/BVSubmittedFeedback.h b/BVSDK/BVConversations/Submission/Feedback/BVSubmittedFeedback.h similarity index 100% rename from Pod/BVConversations/Submission/Feedback/BVSubmittedFeedback.h rename to BVSDK/BVConversations/Submission/Feedback/BVSubmittedFeedback.h diff --git a/Pod/BVConversations/Submission/Feedback/BVSubmittedFeedback.m b/BVSDK/BVConversations/Submission/Feedback/BVSubmittedFeedback.m similarity index 100% rename from Pod/BVConversations/Submission/Feedback/BVSubmittedFeedback.m rename to BVSDK/BVConversations/Submission/Feedback/BVSubmittedFeedback.m diff --git a/Pod/BVConversations/Submission/NSError+BVSubmissionErrorCodeParser.h b/BVSDK/BVConversations/Submission/NSError+BVSubmissionErrorCodeParser.h similarity index 100% rename from Pod/BVConversations/Submission/NSError+BVSubmissionErrorCodeParser.h rename to BVSDK/BVConversations/Submission/NSError+BVSubmissionErrorCodeParser.h diff --git a/Pod/BVConversations/Submission/NSError+BVSubmissionErrorCodeParser.m b/BVSDK/BVConversations/Submission/NSError+BVSubmissionErrorCodeParser.m similarity index 100% rename from Pod/BVConversations/Submission/NSError+BVSubmissionErrorCodeParser.m rename to BVSDK/BVConversations/Submission/NSError+BVSubmissionErrorCodeParser.m diff --git a/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.h b/BVSDK/BVConversations/Submission/Photo/BVUploadablePhoto.h similarity index 97% rename from Pod/BVConversations/Submission/Photo/BVUploadablePhoto.h rename to BVSDK/BVConversations/Submission/Photo/BVUploadablePhoto.h index 8b938699..6d733855 100644 --- a/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.h +++ b/BVSDK/BVConversations/Submission/Photo/BVUploadablePhoto.h @@ -22,7 +22,7 @@ typedef NS_ENUM(NSInteger, BVPhotoContentType) { @property(nonnull, readonly) UIImage *photo; @property(nullable, readonly) NSString *photoCaption; -@property(readwrite) int +@property(readwrite) NSUInteger maxImageBytes; // Set by BVUploadablePhoto itself, but is here for testing - (nonnull instancetype)initWithPhoto:(nonnull UIImage *)photo diff --git a/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.m b/BVSDK/BVConversations/Submission/Photo/BVUploadablePhoto.m similarity index 92% rename from Pod/BVConversations/Submission/Photo/BVUploadablePhoto.m rename to BVSDK/BVConversations/Submission/Photo/BVUploadablePhoto.m index 18683640..9b254d37 100644 --- a/Pod/BVConversations/Submission/Photo/BVUploadablePhoto.m +++ b/BVSDK/BVConversations/Submission/Photo/BVUploadablePhoto.m @@ -6,13 +6,13 @@ // #import "BVUploadablePhoto.h" -#import "BVAnalyticsManager.h" #import "BVConversationsRequest.h" +#import "BVNetworkingManager.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" #import "BVSubmissionErrorResponse.h" -static int const MAX_IMAGE_BYTES = 5 * 1024 * 1024; // BV API max is 5MB +static NSUInteger const MAX_IMAGE_BYTES = 5 * 1024 * 1024; // BV API max is 5MB @interface BVUploadablePhoto () @@ -25,8 +25,7 @@ @implementation BVUploadablePhoto - (nonnull instancetype)initWithPhoto:(nonnull UIImage *)photo photoCaption:(nullable NSString *)caption { - self = [super init]; - if (self) { + if ((self = [super init])) { self.photo = photo; self.photoCaption = caption; self.maxImageBytes = MAX_IMAGE_BYTES; @@ -94,7 +93,15 @@ - (void)uploadForContentType:(BVPhotoContentType)type forHTTPHeaderField:@"Content-Length"]; [request setHTTPBody:body]; - NSURLSession *session = [NSURLSession sharedSession]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; [[BVLogger sharedLogger] verbose:[NSString stringWithFormat:@"POST: %@\n", urlString]]; @@ -187,6 +194,14 @@ - (void)uploadForContentType:(BVPhotoContentType)type // start photo upload [sessionTask resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:sessionTask + fromBVObject:self + withURLSession:session]; + } } - (nonnull NSData *)nsDataForPhoto { diff --git a/Pod/BVConversations/Submission/Question/BVQuestionSubmission.h b/BVSDK/BVConversations/Submission/Question/BVQuestionSubmission.h similarity index 100% rename from Pod/BVConversations/Submission/Question/BVQuestionSubmission.h rename to BVSDK/BVConversations/Submission/Question/BVQuestionSubmission.h diff --git a/Pod/BVConversations/Submission/Question/BVQuestionSubmission.m b/BVSDK/BVConversations/Submission/Question/BVQuestionSubmission.m similarity index 92% rename from Pod/BVConversations/Submission/Question/BVQuestionSubmission.m rename to BVSDK/BVConversations/Submission/Question/BVQuestionSubmission.m index daa60f0d..36acbf43 100644 --- a/Pod/BVConversations/Submission/Question/BVQuestionSubmission.m +++ b/BVSDK/BVConversations/Submission/Question/BVQuestionSubmission.m @@ -6,6 +6,7 @@ // #import "BVQuestionSubmission.h" +#import "BVNetworkingManager.h" #import "BVQuestionSubmissionErrorResponse.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" @@ -133,7 +134,16 @@ - (void)submitForReal:(QuestionSubmissionCompletion)success verbose:[NSString stringWithFormat:@"POST: %@\n with BODY: %@", urlString, parameters]]; - NSURLSession *session = [NSURLSession sharedSession]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, @@ -215,6 +225,14 @@ - (void)submitForReal:(QuestionSubmissionCompletion)success // start uploading question [postDataTask resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:postDataTask + fromBVObject:self + withURLSession:session]; + } } - (nonnull NSDictionary *) @@ -262,17 +280,17 @@ - (void)submitForReal:(QuestionSubmissionCompletion)success [self.agreedToTermsAndConditions boolValue] ? @"true" : @"false"; } - int photoIndex = 0; + NSUInteger photoIndex = 0; for (NSString *url in photoUrls) { - NSString *key = [NSString stringWithFormat:@"photourl_%i", photoIndex]; + NSString *key = [NSString stringWithFormat:@"photourl_%i", (int)photoIndex]; parameters[key] = url; photoIndex += 1; } - int captionIndex = 0; + NSUInteger captionIndex = 0; for (NSString *caption in photoCaptions) { NSString *key = - [NSString stringWithFormat:@"photocaption_%i", captionIndex]; + [NSString stringWithFormat:@"photocaption_%i", (int)captionIndex]; parameters[key] = caption; captionIndex += 1; } diff --git a/Pod/BVConversations/Submission/Question/BVQuestionSubmissionErrorResponse.h b/BVSDK/BVConversations/Submission/Question/BVQuestionSubmissionErrorResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Question/BVQuestionSubmissionErrorResponse.h rename to BVSDK/BVConversations/Submission/Question/BVQuestionSubmissionErrorResponse.h diff --git a/Pod/BVConversations/Submission/Question/BVQuestionSubmissionErrorResponse.m b/BVSDK/BVConversations/Submission/Question/BVQuestionSubmissionErrorResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Question/BVQuestionSubmissionErrorResponse.m rename to BVSDK/BVConversations/Submission/Question/BVQuestionSubmissionErrorResponse.m diff --git a/Pod/BVConversations/Submission/Question/BVQuestionSubmissionResponse.h b/BVSDK/BVConversations/Submission/Question/BVQuestionSubmissionResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Question/BVQuestionSubmissionResponse.h rename to BVSDK/BVConversations/Submission/Question/BVQuestionSubmissionResponse.h diff --git a/Pod/BVConversations/Submission/Question/BVQuestionSubmissionResponse.m b/BVSDK/BVConversations/Submission/Question/BVQuestionSubmissionResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Question/BVQuestionSubmissionResponse.m rename to BVSDK/BVConversations/Submission/Question/BVQuestionSubmissionResponse.m diff --git a/Pod/BVConversations/Submission/Question/BVSubmittedQuestion.h b/BVSDK/BVConversations/Submission/Question/BVSubmittedQuestion.h similarity index 100% rename from Pod/BVConversations/Submission/Question/BVSubmittedQuestion.h rename to BVSDK/BVConversations/Submission/Question/BVSubmittedQuestion.h diff --git a/Pod/BVConversations/Submission/Question/BVSubmittedQuestion.m b/BVSDK/BVConversations/Submission/Question/BVSubmittedQuestion.m similarity index 100% rename from Pod/BVConversations/Submission/Question/BVSubmittedQuestion.m rename to BVSDK/BVConversations/Submission/Question/BVSubmittedQuestion.m diff --git a/Pod/BVConversations/Submission/Review/BVReviewSubmission.h b/BVSDK/BVConversations/Submission/Review/BVReviewSubmission.h similarity index 97% rename from Pod/BVConversations/Submission/Review/BVReviewSubmission.h rename to BVSDK/BVConversations/Submission/Review/BVReviewSubmission.h index 8e40b108..019bea25 100644 --- a/Pod/BVConversations/Submission/Review/BVReviewSubmission.h +++ b/BVSDK/BVConversations/Submission/Review/BVReviewSubmission.h @@ -80,14 +80,14 @@ typedef void (^ReviewSubmissionCompletion)( - (void)addContextDataValueBool:(nonnull NSString *)contextDataValueName value:(bool)value; - (void)addRatingQuestion:(nonnull NSString *)ratingQuestionName - value:(int)value; + value:(NSInteger)value; - (void)addRatingSlider:(nonnull NSString *)ratingQuestionName value:(nonnull NSString *)value; - (void)addPredefinedTagDimension:(nonnull NSString *)tagQuestionId tagId:(nonnull NSString *)tagId value:(nonnull NSString *)value; - (void)addFreeformTagDimension:(nonnull NSString *)tagQuestionId - tagNumber:(int)tagNumber + tagNumber:(NSInteger)tagNumber value:(nonnull NSString *)value; - (nonnull NSString *)getPasskey; diff --git a/Pod/BVConversations/Submission/Review/BVReviewSubmission.m b/BVSDK/BVConversations/Submission/Review/BVReviewSubmission.m similarity index 92% rename from Pod/BVConversations/Submission/Review/BVReviewSubmission.m rename to BVSDK/BVConversations/Submission/Review/BVReviewSubmission.m index a5b0565b..1adab28c 100644 --- a/Pod/BVConversations/Submission/Review/BVReviewSubmission.m +++ b/BVSDK/BVConversations/Submission/Review/BVReviewSubmission.m @@ -6,6 +6,7 @@ // #import "BVReviewSubmission.h" +#import "BVNetworkingManager.h" #import "BVReviewSubmissionErrorResponse.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" @@ -78,9 +79,9 @@ - (void)addContextDataValueBool:(nonnull NSString *)contextDataValueName /// https://developer.bazaarvoice.com/apis/conversations/tutorials/field_types#rating-question---normal - (void)addRatingQuestion:(nonnull NSString *)ratingQuestionName - value:(int)value { + value:(NSInteger)value { NSString *key = [NSString stringWithFormat:@"rating_%@", ratingQuestionName]; - NSString *valueAsString = [NSString stringWithFormat:@"%i", value]; + NSString *valueAsString = [NSString stringWithFormat:@"%i", (int)value]; self.ratingQuestions[key] = valueAsString; } @@ -102,10 +103,10 @@ - (void)addPredefinedTagDimension:(nonnull NSString *)tagQuestionId /// https://developer.bazaarvoice.com/apis/conversations/tutorials/field_types#tags---tag-dimensions - (void)addFreeformTagDimension:(nonnull NSString *)tagQuestionId - tagNumber:(int)tagNumber + tagNumber:(NSInteger)tagNumber value:(nonnull NSString *)value { NSString *key = - [NSString stringWithFormat:@"tag_%@_%i", tagQuestionId, tagNumber]; + [NSString stringWithFormat:@"tag_%@_%i", tagQuestionId, (int)tagNumber]; self.freeformTags[key] = value; } @@ -217,7 +218,16 @@ - (void)submitReviewWithPhotoUrls:(BVSubmissionAction)action verbose:[NSString stringWithFormat:@"POST: %@\n with BODY: %@", urlString, parameters]]; - NSURLSession *session = [NSURLSession sharedSession]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, @@ -312,6 +322,14 @@ - (void)submitReviewWithPhotoUrls:(BVSubmissionAction)action // start uploading review [postDataTask resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:postDataTask + fromBVObject:self + withURLSession:session]; + } } - (nonnull NSDictionary *) @@ -373,29 +391,29 @@ - (void)submitReviewWithPhotoUrls:(BVSubmissionAction)action parameters[@"userid"] = self.userId; parameters[@"userlocation"] = self.userLocation; - int photoIndex = 0; + NSUInteger photoIndex = 0; for (NSString *url in photoUrls) { - NSString *key = [NSString stringWithFormat:@"photourl_%i", photoIndex]; + NSString *key = [NSString stringWithFormat:@"photourl_%i", (int)photoIndex]; parameters[key] = url; photoIndex += 1; } - int captionIndex = 0; + NSUInteger captionIndex = 0; for (NSString *caption in photoCaptions) { NSString *key = - [NSString stringWithFormat:@"photocaption_%i", captionIndex]; + [NSString stringWithFormat:@"photocaption_%i", (int)captionIndex]; parameters[key] = caption; captionIndex += 1; } - int videoIndex = 1; + NSUInteger videoIndex = 1; for (BVUploadableYouTubeVideo *video in self.videos) { - NSString *key = [NSString stringWithFormat:@"VideoUrl_%i", videoIndex]; + NSString *key = [NSString stringWithFormat:@"VideoUrl_%i", (int)videoIndex]; parameters[key] = video.videoURL; if (video.videoCaption) { NSString *key = - [NSString stringWithFormat:@"VideoCaption_%i", videoIndex]; + [NSString stringWithFormat:@"VideoCaption_%i", (int)videoIndex]; parameters[key] = video.videoCaption; } diff --git a/Pod/BVConversations/Submission/Review/BVReviewSubmissionErrorResponse.h b/BVSDK/BVConversations/Submission/Review/BVReviewSubmissionErrorResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Review/BVReviewSubmissionErrorResponse.h rename to BVSDK/BVConversations/Submission/Review/BVReviewSubmissionErrorResponse.h diff --git a/Pod/BVConversations/Submission/Review/BVReviewSubmissionErrorResponse.m b/BVSDK/BVConversations/Submission/Review/BVReviewSubmissionErrorResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Review/BVReviewSubmissionErrorResponse.m rename to BVSDK/BVConversations/Submission/Review/BVReviewSubmissionErrorResponse.m diff --git a/Pod/BVConversations/Submission/Review/BVReviewSubmissionResponse.h b/BVSDK/BVConversations/Submission/Review/BVReviewSubmissionResponse.h similarity index 100% rename from Pod/BVConversations/Submission/Review/BVReviewSubmissionResponse.h rename to BVSDK/BVConversations/Submission/Review/BVReviewSubmissionResponse.h diff --git a/Pod/BVConversations/Submission/Review/BVReviewSubmissionResponse.m b/BVSDK/BVConversations/Submission/Review/BVReviewSubmissionResponse.m similarity index 100% rename from Pod/BVConversations/Submission/Review/BVReviewSubmissionResponse.m rename to BVSDK/BVConversations/Submission/Review/BVReviewSubmissionResponse.m diff --git a/Pod/BVConversations/Submission/Review/BVSubmittedReview.h b/BVSDK/BVConversations/Submission/Review/BVSubmittedReview.h similarity index 100% rename from Pod/BVConversations/Submission/Review/BVSubmittedReview.h rename to BVSDK/BVConversations/Submission/Review/BVSubmittedReview.h diff --git a/Pod/BVConversations/Submission/Review/BVSubmittedReview.m b/BVSDK/BVConversations/Submission/Review/BVSubmittedReview.m similarity index 100% rename from Pod/BVConversations/Submission/Review/BVSubmittedReview.m rename to BVSDK/BVConversations/Submission/Review/BVSubmittedReview.m diff --git a/Pod/BVConversations/Submission/Video/BVUploadableYouTubeVideo.h b/BVSDK/BVConversations/Submission/Video/BVUploadableYouTubeVideo.h similarity index 100% rename from Pod/BVConversations/Submission/Video/BVUploadableYouTubeVideo.h rename to BVSDK/BVConversations/Submission/Video/BVUploadableYouTubeVideo.h diff --git a/Pod/BVConversations/Submission/Video/BVUploadableYouTubeVideo.m b/BVSDK/BVConversations/Submission/Video/BVUploadableYouTubeVideo.m similarity index 100% rename from Pod/BVConversations/Submission/Video/BVUploadableYouTubeVideo.m rename to BVSDK/BVConversations/Submission/Video/BVUploadableYouTubeVideo.m diff --git a/Pod/BVConversationsStores/Display/Model/BVBulkStoresResponse.h b/BVSDK/BVConversationsStores/Display/Model/BVBulkStoresResponse.h similarity index 100% rename from Pod/BVConversationsStores/Display/Model/BVBulkStoresResponse.h rename to BVSDK/BVConversationsStores/Display/Model/BVBulkStoresResponse.h diff --git a/Pod/BVConversationsStores/Display/Model/BVBulkStoresResponse.m b/BVSDK/BVConversationsStores/Display/Model/BVBulkStoresResponse.m similarity index 100% rename from Pod/BVConversationsStores/Display/Model/BVBulkStoresResponse.m rename to BVSDK/BVConversationsStores/Display/Model/BVBulkStoresResponse.m diff --git a/Pod/BVConversationsStores/Display/Model/BVStoreReviewsResponse.h b/BVSDK/BVConversationsStores/Display/Model/BVStoreReviewsResponse.h similarity index 100% rename from Pod/BVConversationsStores/Display/Model/BVStoreReviewsResponse.h rename to BVSDK/BVConversationsStores/Display/Model/BVStoreReviewsResponse.h diff --git a/Pod/BVConversationsStores/Display/Model/BVStoreReviewsResponse.m b/BVSDK/BVConversationsStores/Display/Model/BVStoreReviewsResponse.m similarity index 100% rename from Pod/BVConversationsStores/Display/Model/BVStoreReviewsResponse.m rename to BVSDK/BVConversationsStores/Display/Model/BVStoreReviewsResponse.m diff --git a/Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.h b/BVSDK/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.h similarity index 100% rename from Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.h rename to BVSDK/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.h diff --git a/Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.m b/BVSDK/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.m similarity index 100% rename from Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.m rename to BVSDK/BVConversationsStores/Display/Model/GenericConversationsResult/BVStore.m diff --git a/Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.h b/BVSDK/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.h similarity index 100% rename from Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.h rename to BVSDK/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.h diff --git a/Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.m b/BVSDK/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.m similarity index 100% rename from Pod/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.m rename to BVSDK/BVConversationsStores/Display/Model/GenericConversationsResult/BVStoreLocation.m diff --git a/Pod/BVConversationsStores/Display/Model/Requests/BVBulkStoreItemsRequest.h b/BVSDK/BVConversationsStores/Display/Requests/BVBulkStoreItemsRequest.h similarity index 81% rename from Pod/BVConversationsStores/Display/Model/Requests/BVBulkStoreItemsRequest.h rename to BVSDK/BVConversationsStores/Display/Requests/BVBulkStoreItemsRequest.h index 0f21d6a5..8000adfc 100644 --- a/Pod/BVConversationsStores/Display/Model/Requests/BVBulkStoreItemsRequest.h +++ b/BVSDK/BVConversationsStores/Display/Requests/BVBulkStoreItemsRequest.h @@ -7,10 +7,9 @@ // #import "BVBulkStoresResponse.h" +#import "BVConversationDisplay.h" #import "BVConversationsRequest.h" -#import "BVSort.h" -#import "BVSortOptionReviews.h" -#import "BVStoreIncludeContentType.h" +#import "BVStoreIncludeTypeValue.h" #import /** @@ -27,17 +26,17 @@ /// Initialize fetching of stores when requesting many store objects at once /// and/or paging -- (nonnull instancetype)init:(int)limit offset:(int)offset; +- (nonnull instancetype)init:(NSUInteger)limit offset:(NSUInteger)offset; /// Initialize to return a specific set of store Ids - (nonnull instancetype)initWithStoreIds:(nonnull NSArray *)storeIds; - (nonnull instancetype)__unavailable init; // Use the designated initializers only. -/// When you apply PDPContentTypeReviews to the content type, you will get -/// review statistics in the BVStore object(s) +/// When you apply reviews to the content type, you will get review statistics +/// in the BVStore object(s) - (nonnull instancetype)includeStatistics: - (BVStoreIncludeContentType)contentType; + (BVStoreIncludeTypeValue)storeIncludeTypeValue; /// Make the asynchronous call from the request object. - (void)load:(nonnull void (^)(BVBulkStoresResponse *__nonnull response))success diff --git a/Pod/BVConversationsStores/Display/Model/Requests/BVBulkStoreItemsRequest.m b/BVSDK/BVConversationsStores/Display/Requests/BVBulkStoreItemsRequest.m similarity index 64% rename from Pod/BVConversationsStores/Display/Model/Requests/BVBulkStoreItemsRequest.m rename to BVSDK/BVConversationsStores/Display/Requests/BVBulkStoreItemsRequest.m index 5c1cf089..221e1154 100644 --- a/Pod/BVConversationsStores/Display/Model/Requests/BVBulkStoreItemsRequest.m +++ b/BVSDK/BVConversationsStores/Display/Requests/BVBulkStoreItemsRequest.m @@ -7,23 +7,25 @@ // #import "BVBulkStoreItemsRequest.h" -#import "BVFilter.h" +#import "BVProductFilterType.h" +#import "BVProductIncludeType.h" +#import "BVRelationalFilterOperator.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" -#import "PDPInclude.h" +#import "BVStoreIncludeType.h" @interface BVBulkStoreItemsRequest () -@property int limit; -@property int offset; -@property(nonnull) NSMutableArray *storeContentTypeStatistics; +@property NSUInteger limit; +@property NSUInteger offset; +@property(nonnull) NSMutableArray *storeIncludeTypes; @property(nonnull) NSArray *filterStoreIds; @end @implementation BVBulkStoreItemsRequest -- (nonnull instancetype)init:(int)limit offset:(int)offset { +- (nonnull instancetype)init:(NSUInteger)limit offset:(NSUInteger)offset { self = [super init]; if (self) { self.limit = limit; @@ -36,7 +38,7 @@ - (nonnull instancetype)init:(int)limit offset:(int)offset { - (nonnull instancetype)initWithStoreIds:(nonnull NSArray *)storeIds { self = [super init]; if (self) { - self.limit = (int)[storeIds count]; + self.limit = [storeIds count]; self.offset = 0; [self initDefaultProps]; _filterStoreIds = storeIds; @@ -46,7 +48,7 @@ - (nonnull instancetype)initWithStoreIds:(nonnull NSArray *)storeIds { - (void)initDefaultProps { _filterStoreIds = [NSArray array]; - self.storeContentTypeStatistics = [NSMutableArray array]; + _storeIncludeTypes = [NSMutableArray array]; } - (void)load:(nonnull void (^)(BVBulkStoresResponse *__nonnull response))success @@ -102,25 +104,30 @@ - (nonnull NSMutableArray *)createParams { [params addObject:[BVStringKeyValuePair pairWithKey:@"Limit" - value:[NSString stringWithFormat:@"%i", self.limit]]]; - [params addObject:[BVStringKeyValuePair - pairWithKey:@"Offset" - value:[NSString - stringWithFormat:@"%i", self.offset]]]; - - if (_storeContentTypeStatistics.count) { - [params - addObject:[BVStringKeyValuePair - pairWithKey:@"Stats" - value:[self statisticsToParams: - self.storeContentTypeStatistics]]]; + value:[NSString + stringWithFormat:@"%i", (int)self.limit]]]; + [params + addObject:[BVStringKeyValuePair + pairWithKey:@"Offset" + value:[NSString + stringWithFormat:@"%i", (int)self.offset]]]; + + if (self.storeIncludeTypes.count) { + [params addObject:[BVStringKeyValuePair + pairWithKey:@"Stats" + value:[self statisticsToParams: + self.storeIncludeTypes]]]; } if ([self.filterStoreIds count] > 0) { BVFilter *filter = [[BVFilter alloc] - initWithString:[BVProductFilterTypeUtil toString:BVProductFilterTypeId] - filterOperator:BVFilterOperatorEqualTo - values:self.filterStoreIds]; + initWithFilterType: + [BVProductFilterType + filterTypeWithRawValue:BVProductFilterValueProductId] + filterOperator:[BVRelationalFilterOperator + filterOperatorWithRawValue: + BVRelationalFilterOperatorValueEqualTo] + values:self.filterStoreIds]; NSString *filterValue = [filter toParameterString]; [params addObject:[BVStringKeyValuePair pairWithKey:@"Filter" value:filterValue]]; @@ -130,17 +137,19 @@ - (nonnull NSMutableArray *)createParams { } - (nonnull instancetype)includeStatistics: - (BVStoreIncludeContentType)contentType { - [self.storeContentTypeStatistics addObject:@(contentType)]; + (BVStoreIncludeTypeValue)storeIncludeTypeValue { + [self.storeIncludeTypes + addObject:[BVStoreIncludeType + includeTypeWithRawValue:storeIncludeTypeValue]]; return self; } - (nonnull NSString *)statisticsToParams: - (nonnull NSArray *)statistics { - NSMutableArray *strings = [NSMutableArray array]; + (nonnull NSArray *)statistics { + NSMutableArray *strings = [NSMutableArray array]; - for (NSNumber *stat in statistics) { - [strings addObject:[PDPContentTypeUtil toString:[stat intValue]]]; + for (BVStoreIncludeType *stat in statistics) { + [strings addObject:[stat toIncludeTypeParameterString]]; } NSArray *sortedArray = [strings diff --git a/Pod/BVConversationsStores/Display/Model/Requests/BVStoreReviewsRequest.h b/BVSDK/BVConversationsStores/Display/Requests/BVStoreReviewsRequest.h similarity index 73% rename from Pod/BVConversationsStores/Display/Model/Requests/BVStoreReviewsRequest.h rename to BVSDK/BVConversationsStores/Display/Requests/BVStoreReviewsRequest.h index a9a9df35..c66826cc 100644 --- a/Pod/BVConversationsStores/Display/Model/Requests/BVStoreReviewsRequest.h +++ b/BVSDK/BVConversationsStores/Display/Requests/BVStoreReviewsRequest.h @@ -6,12 +6,8 @@ // #import "BVBaseReviewsRequest.h" -#import "BVFilterOperator.h" -#import "BVReviewFilterType.h" #import "BVReviewsRequest.h" -#import "BVSort.h" -#import "BVSortOptionReviews.h" -#import "BVStoreIncludeContentType.h" +#import "BVStoreIncludeTypeValue.h" #import "BVStoreReviewsResponse.h" #import @@ -29,12 +25,12 @@ typedef void (^StoreReviewRequestCompletionHandler)( @property(nonnull, readonly) NSString *storeId; - (nonnull instancetype)initWithStoreId:(nonnull NSString *)storeId - limit:(int)limit - offset:(int)offset; + limit:(NSUInteger)limit + offset:(NSUInteger)offset; - (nonnull instancetype)__unavailable init; - (nonnull instancetype)includeStatistics: - (BVStoreIncludeContentType)contentType; + (BVStoreIncludeTypeValue)storeIncludeTypeValue; @end diff --git a/Pod/BVConversationsStores/Display/Model/Requests/BVStoreReviewsRequest.m b/BVSDK/BVConversationsStores/Display/Requests/BVStoreReviewsRequest.m similarity index 58% rename from Pod/BVConversationsStores/Display/Model/Requests/BVStoreReviewsRequest.m rename to BVSDK/BVConversationsStores/Display/Requests/BVStoreReviewsRequest.m index 57a69d75..d928cb98 100644 --- a/Pod/BVConversationsStores/Display/Model/Requests/BVStoreReviewsRequest.m +++ b/BVSDK/BVConversationsStores/Display/Requests/BVStoreReviewsRequest.m @@ -9,38 +9,41 @@ #import "BVCommaUtil.h" #import "BVFilter.h" #import "BVLogger.h" +#import "BVProductIncludeType.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" #import "BVSort.h" -#import "PDPInclude.h" +#import "BVStoreIncludeType.h" @interface BVStoreReviewsRequest () - -@property(nonnull) NSMutableArray *storeContentTypeStatistics; -@property(nonnull) NSMutableArray *reviewSorts; - +@property(nonnull) NSMutableArray *storeIncludeTypes; @end @implementation BVStoreReviewsRequest - (nonnull instancetype)initWithStoreId:(nonnull NSString *)storeId - limit:(int)limit - offset:(int)offset { - return self = [super initWithID:storeId limit:limit offset:offset]; + limit:(NSUInteger)limit + offset:(NSUInteger)offset { + if ((self = [super initWithID:storeId limit:limit offset:offset])) { + _storeIncludeTypes = [NSMutableArray array]; + } + return self; } - (nonnull instancetype)includeStatistics: - (BVStoreIncludeContentType)contentType { - [self.storeContentTypeStatistics addObject:@(contentType)]; + (BVStoreIncludeTypeValue)storeIncludeTypeValue { + [self.storeIncludeTypes + addObject:[BVStoreIncludeType + includeTypeWithRawValue:storeIncludeTypeValue]]; return self; } - (nonnull NSString *)statisticsToParams: - (nonnull NSArray *)statistics { + (nonnull NSArray *)statistics { NSMutableArray *strings = [NSMutableArray array]; - for (NSNumber *stat in statistics) { - [strings addObject:[PDPContentTypeUtil toString:[stat intValue]]]; + for (BVStoreIncludeType *stat in statistics) { + [strings addObject:[stat toIncludeTypeParameterString]]; } NSArray *sortedArray = [strings diff --git a/BVSDK/BVConversationsStores/Display/Sorting & Filtering/BVStoreIncludeTypeValue.h b/BVSDK/BVConversationsStores/Display/Sorting & Filtering/BVStoreIncludeTypeValue.h new file mode 100644 index 00000000..0bfd4fd5 --- /dev/null +++ b/BVSDK/BVConversationsStores/Display/Sorting & Filtering/BVStoreIncludeTypeValue.h @@ -0,0 +1,20 @@ +// +// BVStoreIncludeTypeValue.h +// BVSDK +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#ifndef BVSTOREINCLUDETYPEVALUE_H +#define BVSTOREINCLUDETYPEVALUE_H + +/* + Types of Bazaarvoice content. + */ +typedef NS_ENUM(NSInteger, BVStoreIncludeTypeValue) { + BVStoreIncludeTypeValueReviews +}; + +#endif /* BVSTOREINCLUDETYPEVALUE_H */ diff --git a/BVSDK/BVConversationsStores/Display/Sorting & Filtering/Private/BVStoreIncludeType.h b/BVSDK/BVConversationsStores/Display/Sorting & Filtering/Private/BVStoreIncludeType.h new file mode 100644 index 00000000..f9bd9678 --- /dev/null +++ b/BVSDK/BVConversationsStores/Display/Sorting & Filtering/Private/BVStoreIncludeType.h @@ -0,0 +1,16 @@ +// +// BVStoreIncludeType.h +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVIncludeType.h" +#import "BVStoreIncludeTypeValue.h" + +@interface BVStoreIncludeType : BVIncludeType + +- (nonnull instancetype)initWithStoreIncludeTypeValue: + (BVStoreIncludeTypeValue)storeIncludeTypeValue; + +@end diff --git a/BVSDK/BVConversationsStores/Display/Sorting & Filtering/Private/BVStoreIncludeType.m b/BVSDK/BVConversationsStores/Display/Sorting & Filtering/Private/BVStoreIncludeType.m new file mode 100644 index 00000000..cdfd601b --- /dev/null +++ b/BVSDK/BVConversationsStores/Display/Sorting & Filtering/Private/BVStoreIncludeType.m @@ -0,0 +1,45 @@ +// +// BVStoreIncludeType.m +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#import "BVStoreIncludeType.h" + +@interface BVStoreIncludeType () +@property(nonnull, nonatomic, readwrite) NSString *value; +@end + +@implementation BVStoreIncludeType + ++ (nonnull NSString *)toIncludeTypeParameterStringWithRawValue: + (NSInteger)rawValue { + return [BVStoreIncludeType includeTypeWithRawValue:rawValue].value; +} + ++ (nonnull instancetype)includeTypeWithRawValue:(NSInteger)rawValue { + return [[BVStoreIncludeType alloc] initWithRawValue:rawValue]; +} + +- (nonnull instancetype)initWithRawValue:(NSInteger)rawValue { + if ((self = [super initWithRawValue:rawValue])) { + switch (rawValue) { + case BVStoreIncludeTypeValueReviews: + self.value = @"Reviews"; + break; + } + } + return self; +} + +- (nonnull NSString *)toIncludeTypeParameterString { + return self.value; +} + +- (nonnull instancetype)initWithStoreIncludeTypeValue: + (BVStoreIncludeTypeValue)storeIncludeTypeValue { + return [BVStoreIncludeType includeTypeWithRawValue:storeIncludeTypeValue]; +} + +@end diff --git a/Pod/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.h b/BVSDK/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.h similarity index 100% rename from Pod/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.h rename to BVSDK/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.h diff --git a/Pod/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.m b/BVSDK/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.m similarity index 100% rename from Pod/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.m rename to BVSDK/BVConversationsStores/Submission/Photo/BVUploadableStorePhoto.m diff --git a/Pod/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.h b/BVSDK/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.h similarity index 100% rename from Pod/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.h rename to BVSDK/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.h diff --git a/Pod/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.m b/BVSDK/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.m similarity index 100% rename from Pod/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.m rename to BVSDK/BVConversationsStores/Submission/Review/BVStoreReviewSubmission.m diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswerCollectionViewCell.h b/BVSDK/BVConversationsUI/Views/Answers/BVAnswerCollectionViewCell.h similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswerCollectionViewCell.h rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswerCollectionViewCell.h diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswerCollectionViewCell.m b/BVSDK/BVConversationsUI/Views/Answers/BVAnswerCollectionViewCell.m similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswerCollectionViewCell.m rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswerCollectionViewCell.m diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswerTableViewCell.h b/BVSDK/BVConversationsUI/Views/Answers/BVAnswerTableViewCell.h similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswerTableViewCell.h rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswerTableViewCell.h diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswerTableViewCell.m b/BVSDK/BVConversationsUI/Views/Answers/BVAnswerTableViewCell.m similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswerTableViewCell.m rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswerTableViewCell.m diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswerView.h b/BVSDK/BVConversationsUI/Views/Answers/BVAnswerView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswerView.h rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswerView.h diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswerView.m b/BVSDK/BVConversationsUI/Views/Answers/BVAnswerView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswerView.m rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswerView.m diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswersCollectionView.h b/BVSDK/BVConversationsUI/Views/Answers/BVAnswersCollectionView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswersCollectionView.h rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswersCollectionView.h diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswersCollectionView.m b/BVSDK/BVConversationsUI/Views/Answers/BVAnswersCollectionView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswersCollectionView.m rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswersCollectionView.m diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswersTableView.h b/BVSDK/BVConversationsUI/Views/Answers/BVAnswersTableView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswersTableView.h rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswersTableView.h diff --git a/Pod/BVConversationsUI/Views/Answers/BVAnswersTableView.m b/BVSDK/BVConversationsUI/Views/Answers/BVAnswersTableView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Answers/BVAnswersTableView.m rename to BVSDK/BVConversationsUI/Views/Answers/BVAnswersTableView.m diff --git a/Pod/BVConversationsUI/Views/ProductDisplayPage/BVProductPageViews.h b/BVSDK/BVConversationsUI/Views/ProductDisplayPage/BVProductPageViews.h similarity index 100% rename from Pod/BVConversationsUI/Views/ProductDisplayPage/BVProductPageViews.h rename to BVSDK/BVConversationsUI/Views/ProductDisplayPage/BVProductPageViews.h diff --git a/Pod/BVConversationsUI/Views/ProductDisplayPage/BVProductPageViews.m b/BVSDK/BVConversationsUI/Views/ProductDisplayPage/BVProductPageViews.m similarity index 100% rename from Pod/BVConversationsUI/Views/ProductDisplayPage/BVProductPageViews.m rename to BVSDK/BVConversationsUI/Views/ProductDisplayPage/BVProductPageViews.m diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionCollectionViewCell.h b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionCollectionViewCell.h similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionCollectionViewCell.h rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionCollectionViewCell.h diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionCollectionViewCell.m b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionCollectionViewCell.m similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionCollectionViewCell.m rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionCollectionViewCell.m diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionTableViewCell.h b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionTableViewCell.h similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionTableViewCell.h rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionTableViewCell.h diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionTableViewCell.m b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionTableViewCell.m similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionTableViewCell.m rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionTableViewCell.m diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionView.h b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionView.h rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionView.h diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionView.m b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionView.m rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionView.m diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionsCollectionView.h b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionsCollectionView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionsCollectionView.h rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionsCollectionView.h diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionsCollectionView.m b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionsCollectionView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionsCollectionView.m rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionsCollectionView.m diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionsTableView.h b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionsTableView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionsTableView.h rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionsTableView.h diff --git a/Pod/BVConversationsUI/Views/Questions/BVQuestionsTableView.m b/BVSDK/BVConversationsUI/Views/Questions/BVQuestionsTableView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Questions/BVQuestionsTableView.m rename to BVSDK/BVConversationsUI/Views/Questions/BVQuestionsTableView.m diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewCollectionViewCell.h b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewCollectionViewCell.h similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewCollectionViewCell.h rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewCollectionViewCell.h diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewCollectionViewCell.m b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewCollectionViewCell.m similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewCollectionViewCell.m rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewCollectionViewCell.m diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewTableViewCell.h b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewTableViewCell.h similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewTableViewCell.h rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewTableViewCell.h diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewTableViewCell.m b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewTableViewCell.m similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewTableViewCell.m rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewTableViewCell.m diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewView.h b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewView.h rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewView.h diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewView.m b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewView.m rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewView.m diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewsCollectionView.h b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewsCollectionView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewsCollectionView.h rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewsCollectionView.h diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewsCollectionView.m b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewsCollectionView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewsCollectionView.m rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewsCollectionView.m diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewsTableView.h b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewsTableView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewsTableView.h rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewsTableView.h diff --git a/Pod/BVConversationsUI/Views/Reviews/BVReviewsTableView.m b/BVSDK/BVConversationsUI/Views/Reviews/BVReviewsTableView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/BVReviewsTableView.m rename to BVSDK/BVConversationsUI/Views/Reviews/BVReviewsTableView.m diff --git a/Pod/BVConversationsUI/Views/Reviews/Stores/BVStoreReviewsTableView.h b/BVSDK/BVConversationsUI/Views/Reviews/Stores/BVStoreReviewsTableView.h similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/Stores/BVStoreReviewsTableView.h rename to BVSDK/BVConversationsUI/Views/Reviews/Stores/BVStoreReviewsTableView.h diff --git a/Pod/BVConversationsUI/Views/Reviews/Stores/BVStoreReviewsTableView.m b/BVSDK/BVConversationsUI/Views/Reviews/Stores/BVStoreReviewsTableView.m similarity index 100% rename from Pod/BVConversationsUI/Views/Reviews/Stores/BVStoreReviewsTableView.m rename to BVSDK/BVConversationsUI/Views/Reviews/Stores/BVStoreReviewsTableView.m diff --git a/Pod/BVCurations/BVCurations.h b/BVSDK/BVCurations/BVCurations.h similarity index 100% rename from Pod/BVCurations/BVCurations.h rename to BVSDK/BVCurations/BVCurations.h diff --git a/Pod/BVCurations/BVCurationsAddPostRequest.h b/BVSDK/BVCurations/BVCurationsAddPostRequest.h similarity index 100% rename from Pod/BVCurations/BVCurationsAddPostRequest.h rename to BVSDK/BVCurations/BVCurationsAddPostRequest.h diff --git a/Pod/BVCurations/BVCurationsAddPostRequest.m b/BVSDK/BVCurations/BVCurationsAddPostRequest.m similarity index 100% rename from Pod/BVCurations/BVCurationsAddPostRequest.m rename to BVSDK/BVCurations/BVCurationsAddPostRequest.m diff --git a/Pod/BVCurations/BVCurationsFeedItem.h b/BVSDK/BVCurations/BVCurationsFeedItem.h similarity index 100% rename from Pod/BVCurations/BVCurationsFeedItem.h rename to BVSDK/BVCurations/BVCurationsFeedItem.h diff --git a/Pod/BVCurations/BVCurationsFeedItem.m b/BVSDK/BVCurations/BVCurationsFeedItem.m similarity index 100% rename from Pod/BVCurations/BVCurationsFeedItem.m rename to BVSDK/BVCurations/BVCurationsFeedItem.m diff --git a/Pod/BVCurations/BVCurationsFeedLoader.h b/BVSDK/BVCurations/BVCurationsFeedLoader.h similarity index 66% rename from Pod/BVCurations/BVCurationsFeedLoader.h rename to BVSDK/BVCurations/BVCurationsFeedLoader.h index 2c6cd24b..5dbb95f2 100644 --- a/Pod/BVCurations/BVCurationsFeedLoader.h +++ b/BVSDK/BVCurations/BVCurationsFeedLoader.h @@ -14,8 +14,9 @@ @class BVCurationsFeedRequest; -typedef void (^feedRequestCompletionHandler)(NSArray *); -typedef void (^feedRequestErrorHandler)(NSError *); +typedef void (^feedRequestCompletionHandler)( + NSArray *__nonnull); +typedef void (^feedRequestErrorHandler)(NSError *__nonnull); /// API helper class used for fetching a curations feed with desired parameters /// (BVCurationsFeedRequest) @@ -30,8 +31,9 @@ typedef void (^feedRequestErrorHandler)(NSError *); @param failureHandler Called with a filled out NSError object for any error that does not yeild a valid Curations feed. Called on main thread. */ -- (void)loadFeedWithRequest:(BVCurationsFeedRequest *)feedRequest - completionHandler:(feedRequestCompletionHandler)completionHandler - withFailure:(feedRequestErrorHandler)failureHandler; +- (void)loadFeedWithRequest:(nonnull BVCurationsFeedRequest *)feedRequest + completionHandler: + (nonnull feedRequestCompletionHandler)completionHandler + withFailure:(nonnull feedRequestErrorHandler)failureHandler; @end diff --git a/Pod/BVCurations/BVCurationsFeedLoader.m b/BVSDK/BVCurations/BVCurationsFeedLoader.m similarity index 86% rename from Pod/BVCurations/BVCurationsFeedLoader.m rename to BVSDK/BVCurations/BVCurationsFeedLoader.m index c18a8b27..a7ce6895 100644 --- a/Pod/BVCurations/BVCurationsFeedLoader.m +++ b/BVSDK/BVCurations/BVCurationsFeedLoader.m @@ -8,12 +8,19 @@ #import "BVCurationsFeedLoader.h" #import "BVCurationsFeedItem.h" +#import "BVNetworkingManager.h" #import "BVSDKConfiguration.h" +@interface BVCurationsFeedLoader () +@end + @implementation BVCurationsFeedLoader -- (NSString *)urlRootCurations { +- (instancetype)init { + return ((self = [super init])); +} +- (NSString *)urlRootCurations { return [BVSDKManager sharedManager].configuration.staging ? @"https://stg.api.bazaarvoice.com" : @"https://api.bazaarvoice.com"; @@ -44,7 +51,17 @@ - (void)loadFeedWithRequest:(BVCurationsFeedRequest *)feedRequest [[BVLogger sharedLogger] verbose:[NSString stringWithFormat:@"GET: %@", url]]; - NSURLSessionDataTask *downloadTask = [[NSURLSession sharedSession] + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + + NSURLSessionDataTask *downloadTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { @@ -68,10 +85,10 @@ - (void)loadFeedWithRequest:(BVCurationsFeedRequest *)feedRequest // check response body status code first. Curations API will return // a 200 response on failures, but put the HTTP status in the "code" // value. - int status = 200; + NSInteger status = 200; if ([responseDict objectForKey:@"code"] != nil) { - status = (int)[[responseDict objectForKey:@"code"] integerValue]; + status = [[responseDict objectForKey:@"code"] integerValue]; } if (status < 300) { @@ -166,6 +183,14 @@ - (void)loadFeedWithRequest:(BVCurationsFeedRequest *)feedRequest }]; [downloadTask resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:downloadTask + fromBVObject:self + withURLSession:session]; + } } - (BVCurationsFeedItem *)getFeedItem:(NSDictionary *)update diff --git a/Pod/BVCurations/BVCurationsFeedRequest.h b/BVSDK/BVCurations/BVCurationsFeedRequest.h similarity index 100% rename from Pod/BVCurations/BVCurationsFeedRequest.h rename to BVSDK/BVCurations/BVCurationsFeedRequest.h diff --git a/Pod/BVCurations/BVCurationsFeedRequest.m b/BVSDK/BVCurations/BVCurationsFeedRequest.m similarity index 100% rename from Pod/BVCurations/BVCurationsFeedRequest.m rename to BVSDK/BVCurations/BVCurationsFeedRequest.m diff --git a/Pod/BVCurations/BVCurationsPhotoUploader.h b/BVSDK/BVCurations/BVCurationsPhotoUploader.h similarity index 75% rename from Pod/BVCurations/BVCurationsPhotoUploader.h rename to BVSDK/BVCurations/BVCurationsPhotoUploader.h index 99787c06..e48d45ce 100644 --- a/Pod/BVCurations/BVCurationsPhotoUploader.h +++ b/BVSDK/BVCurations/BVCurationsPhotoUploader.h @@ -11,7 +11,7 @@ #import "BVCurationsAddPostRequest.h" typedef void (^uploadCompletionHandler)(void); -typedef void (^uploadErrorHandler)(NSError *); +typedef void (^uploadErrorHandler)(NSError *__nonnull); /*! API for posting Custom Content to Curations. @@ -30,9 +30,11 @@ typedef void (^uploadErrorHandler)(NSError *); code and error text are returned in an NSError object. This handler is invoked on the main thread. */ -- (void)submitCurationsContentWithParams:(BVCurationsAddPostRequest *)postParams +- (void)submitCurationsContentWithParams: + (nullable BVCurationsAddPostRequest *)postParams completionHandler: - (uploadCompletionHandler)completionHandler - withFailure:(uploadErrorHandler)failureHandler; + (nonnull uploadCompletionHandler)completionHandler + withFailure: + (nonnull uploadErrorHandler)failureHandler; @end diff --git a/Pod/BVCurations/BVCurationsPhotoUploader.m b/BVSDK/BVCurations/BVCurationsPhotoUploader.m similarity index 90% rename from Pod/BVCurations/BVCurationsPhotoUploader.m rename to BVSDK/BVCurations/BVCurationsPhotoUploader.m index 40519d88..04ec47ca 100644 --- a/Pod/BVCurations/BVCurationsPhotoUploader.m +++ b/BVSDK/BVCurations/BVCurationsPhotoUploader.m @@ -8,10 +8,18 @@ #import "BVCurationsPhotoUploader.h" #import "BVCurations.h" +#import "BVNetworkingManager.h" #import "BVSDKConfiguration.h" +@interface BVCurationsPhotoUploader () +@end + @implementation BVCurationsPhotoUploader +- (instancetype)init { + return ((self = [super init])); +} + - (void)submitCurationsContentWithParams:(BVCurationsAddPostRequest *)postParams completionHandler: (uploadCompletionHandler)completionHandler @@ -30,14 +38,23 @@ - (void)submitCurationsContentWithParams:(BVCurationsAddPostRequest *)postParams [NSError errorWithDomain:BVErrDomain code:-1 userInfo:userInfo]; [self errorOnMainThread:err handler:failureHandler]; + return; } NSString *endPoint = [NSString stringWithFormat:@"%@/curations/content/add/?client=%@&passkey=%@", [self urlRootCurations], clientId, passKey]; - NSURLSessionConfiguration *config = - [NSURLSessionConfiguration defaultSessionConfiguration]; - NSURLSession *session = [NSURLSession sessionWithConfiguration:config]; + + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURL *url = [NSURL URLWithString:endPoint]; NSString *boundary = [self generateBoundaryString]; @@ -87,7 +104,7 @@ - (void)submitCurationsContentWithParams:(BVCurationsAddPostRequest *)postParams error:&errorJSON]; if (!errorJSON) { - int status = [self getStatusCodeFromResponse:responseDict]; + NSInteger status = [self getStatusCodeFromResponse:responseDict]; if (status < 299) { [[BVLogger sharedLogger] verbose:[NSString @@ -134,11 +151,20 @@ - (void)submitCurationsContentWithParams:(BVCurationsAddPostRequest *)postParams userInfo:userInfo]; [self errorOnMainThread:err handler:failureHandler]; + return; } }]; [taskUpload resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:taskUpload + fromBVObject:self + withURLSession:session]; + } } - (void)completionOnMainThreadWithHandler: @@ -156,7 +182,7 @@ - (void)errorOnMainThread:(NSError *)error } // Response code may be in either 'status' or 'code' as an integer -- (int)getStatusCodeFromResponse:(NSDictionary *)response { +- (NSInteger)getStatusCodeFromResponse:(NSDictionary *)response { long status = 200; // presume success unless we can dig out the exact failure if ([response objectForKey:@"code"] != nil && @@ -171,7 +197,7 @@ - (int)getStatusCodeFromResponse:(NSDictionary *)response { status = [[response objectForKey:@"status"] integerValue]; } - return (int)status; + return status; } - (NSString *)getErrorStringFromResponse:(NSDictionary *)response { diff --git a/Pod/BVCurationsUI/BVCurationsPostViewController.h b/BVSDK/BVCurationsUI/BVCurationsPostViewController.h similarity index 100% rename from Pod/BVCurationsUI/BVCurationsPostViewController.h rename to BVSDK/BVCurationsUI/BVCurationsPostViewController.h diff --git a/Pod/BVCurationsUI/BVCurationsPostViewController.m b/BVSDK/BVCurationsUI/BVCurationsPostViewController.m similarity index 99% rename from Pod/BVCurationsUI/BVCurationsPostViewController.m rename to BVSDK/BVCurationsUI/BVCurationsPostViewController.m index 260bc3fd..88f6a8b3 100644 --- a/Pod/BVCurationsUI/BVCurationsPostViewController.m +++ b/BVSDK/BVCurationsUI/BVCurationsPostViewController.m @@ -28,7 +28,7 @@ - (nonnull instancetype)initWithPostRequest: logoImage:(nonnull UIImage *)logo bavBarColor:(nonnull UIColor *)navBarColor navBarTintColor:(nonnull UIColor *)navBarTintColor { - if (self = [super init]) { + if ((self = [super init])) { _postRequest = postRequest; _logo = logo; _navBarColor = navBarColor; diff --git a/Pod/BVCurationsUI/BVCurationsUICollectionView.h b/BVSDK/BVCurationsUI/BVCurationsUICollectionView.h similarity index 100% rename from Pod/BVCurationsUI/BVCurationsUICollectionView.h rename to BVSDK/BVCurationsUI/BVCurationsUICollectionView.h diff --git a/Pod/BVCurationsUI/BVCurationsUICollectionView.m b/BVSDK/BVCurationsUI/BVCurationsUICollectionView.m similarity index 99% rename from Pod/BVCurationsUI/BVCurationsUICollectionView.m rename to BVSDK/BVCurationsUI/BVCurationsUICollectionView.m index 591dbf02..605fb918 100644 --- a/Pod/BVCurationsUI/BVCurationsUICollectionView.m +++ b/BVSDK/BVCurationsUI/BVCurationsUICollectionView.m @@ -180,7 +180,7 @@ - (void)loadCurationsFeed:(NSUInteger)limit } else { NSMutableArray *indexPaths = [NSMutableArray new]; NSUInteger inserted = (limit <= items.count) ? limit : items.count; - for (int i = 0; i < inserted; i++) { + for (NSUInteger i = 0; i < inserted; i++) { [indexPaths addObject:[NSIndexPath indexPathForItem:weakSelf.curationsFeedItems diff --git a/Pod/BVCurationsUI/BVCurationsUICollectionViewCell.h b/BVSDK/BVCurationsUI/BVCurationsUICollectionViewCell.h similarity index 100% rename from Pod/BVCurationsUI/BVCurationsUICollectionViewCell.h rename to BVSDK/BVCurationsUI/BVCurationsUICollectionViewCell.h diff --git a/Pod/BVCurationsUI/BVCurationsUICollectionViewCell.m b/BVSDK/BVCurationsUI/BVCurationsUICollectionViewCell.m similarity index 100% rename from Pod/BVCurationsUI/BVCurationsUICollectionViewCell.m rename to BVSDK/BVCurationsUI/BVCurationsUICollectionViewCell.m diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/bv@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/bv@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/bv@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/bv@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/bv@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/bv@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/bv@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/bazaarvoice.imageset/bv@3x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/facebook@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/facebook@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/facebook@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/facebook@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/facebook@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/facebook@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/facebook@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/facebook.imageset/facebook@3x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/google-plus@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/google-plus@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/google-plus@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/google-plus@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/google-plus@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/google-plus@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/google-plus@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/google-plus.imageset/google-plus@3x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/instagram@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/instagram@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/instagram@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/instagram@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/instagram@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/instagram@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/instagram@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/instagram.imageset/instagram@3x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/pinterest@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/pinterest@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/pinterest@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/pinterest@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/pinterest@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/pinterest@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/pinterest@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/pinterest.imageset/pinterest@3x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/placeholder@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/placeholder@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/placeholder@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/placeholder@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/placeholder@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/placeholder@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/placeholder@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/placeholder.imageset/placeholder@3x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/play@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/play@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/play@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/play@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/play@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/play@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/play@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/play.imageset/play@3x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/twitter@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/twitter@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/twitter@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/twitter@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/twitter@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/twitter@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/twitter@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/twitter.imageset/twitter@3x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/Contents.json b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/Contents.json similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/Contents.json rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/Contents.json diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/youtube@2x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/youtube@2x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/youtube@2x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/youtube@2x.png diff --git a/Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/youtube@3x.png b/BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/youtube@3x.png similarity index 100% rename from Pod/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/youtube@3x.png rename to BVSDK/BVCurationsUI/SocialMediaIcons/Assets.xcassets/youtube.imageset/youtube@3x.png diff --git a/Pod/BVExtensionNotifications/BVNotificationViewController.h b/BVSDK/BVExtensionNotifications/BVNotificationViewController.h similarity index 100% rename from Pod/BVExtensionNotifications/BVNotificationViewController.h rename to BVSDK/BVExtensionNotifications/BVNotificationViewController.h diff --git a/Pod/BVExtensionNotifications/BVNotificationViewController.m b/BVSDK/BVExtensionNotifications/BVNotificationViewController.m similarity index 100% rename from Pod/BVExtensionNotifications/BVNotificationViewController.m rename to BVSDK/BVExtensionNotifications/BVNotificationViewController.m diff --git a/Pod/BVExtensionNotifications/BVNotifications.h b/BVSDK/BVExtensionNotifications/BVNotifications.h similarity index 93% rename from Pod/BVExtensionNotifications/BVNotifications.h rename to BVSDK/BVExtensionNotifications/BVNotifications.h index 3a04a6e7..93bc6718 100644 --- a/Pod/BVExtensionNotifications/BVNotifications.h +++ b/BVSDK/BVExtensionNotifications/BVNotifications.h @@ -14,9 +14,6 @@ #import "BVLogger.h" #import "BVSDKConstants.h" -// BVAnalytics -#import "BVAnalyticsManager.h" - // BVNotifications #import "BVNotificationProperties.h" #import "BVNotificationsAnalyticsHelper.h" diff --git a/Pod/BVExtensionNotifications/BVProductReviewNotificationViewController.h b/BVSDK/BVExtensionNotifications/BVProductReviewNotificationViewController.h similarity index 100% rename from Pod/BVExtensionNotifications/BVProductReviewNotificationViewController.h rename to BVSDK/BVExtensionNotifications/BVProductReviewNotificationViewController.h diff --git a/Pod/BVExtensionNotifications/BVProductReviewNotificationViewController.m b/BVSDK/BVExtensionNotifications/BVProductReviewNotificationViewController.m similarity index 100% rename from Pod/BVExtensionNotifications/BVProductReviewNotificationViewController.m rename to BVSDK/BVExtensionNotifications/BVProductReviewNotificationViewController.m diff --git a/Pod/BVExtensionNotifications/BVStoreReviewNotificationViewController.h b/BVSDK/BVExtensionNotifications/BVStoreReviewNotificationViewController.h similarity index 100% rename from Pod/BVExtensionNotifications/BVStoreReviewNotificationViewController.h rename to BVSDK/BVExtensionNotifications/BVStoreReviewNotificationViewController.h diff --git a/Pod/BVExtensionNotifications/BVStoreReviewNotificationViewController.m b/BVSDK/BVExtensionNotifications/BVStoreReviewNotificationViewController.m similarity index 100% rename from Pod/BVExtensionNotifications/BVStoreReviewNotificationViewController.m rename to BVSDK/BVExtensionNotifications/BVStoreReviewNotificationViewController.m diff --git a/Pod/BVNotifications/BVNotificationCenterObject.h b/BVSDK/BVNotifications/BVNotificationCenterObject.h similarity index 100% rename from Pod/BVNotifications/BVNotificationCenterObject.h rename to BVSDK/BVNotifications/BVNotificationCenterObject.h diff --git a/Pod/BVNotifications/BVNotificationConfiguration.h b/BVSDK/BVNotifications/BVNotificationConfiguration.h similarity index 100% rename from Pod/BVNotifications/BVNotificationConfiguration.h rename to BVSDK/BVNotifications/BVNotificationConfiguration.h diff --git a/Pod/BVNotifications/BVNotificationConfiguration.m b/BVSDK/BVNotifications/BVNotificationConfiguration.m similarity index 88% rename from Pod/BVNotifications/BVNotificationConfiguration.m rename to BVSDK/BVNotifications/BVNotificationConfiguration.m index 97b5d771..2d3e08a4 100644 --- a/Pod/BVNotifications/BVNotificationConfiguration.m +++ b/BVSDK/BVNotifications/BVNotificationConfiguration.m @@ -8,6 +8,7 @@ #import "BVNotificationConfiguration.h" #import "BVCommon.h" +#import "BVNetworkingManager.h" @implementation BVNotificationConfiguration @@ -91,7 +92,19 @@ + (void)loadConfiguration:(nonnull NSURL *)url [[BVLogger sharedLogger] verbose:[NSString stringWithFormat:@"GET: %@", url]]; - NSURLSession *session = [NSURLSession sharedSession]; + /// For private classes we ask for the NSURLSession but we don't hand back any + /// objects since it would be useless to the developers as they have no + /// interface to the object graph. + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:nil]; + } + + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + NSURLSessionDataTask *task = [session dataTaskWithRequest:urlRequest completionHandler:^(NSData *__nullable data, diff --git a/Pod/BVNotifications/BVNotificationConstants.h b/BVSDK/BVNotifications/BVNotificationConstants.h similarity index 100% rename from Pod/BVNotifications/BVNotificationConstants.h rename to BVSDK/BVNotifications/BVNotificationConstants.h diff --git a/Pod/BVNotifications/BVNotificationProperties.h b/BVSDK/BVNotifications/BVNotificationProperties.h similarity index 100% rename from Pod/BVNotifications/BVNotificationProperties.h rename to BVSDK/BVNotifications/BVNotificationProperties.h diff --git a/Pod/BVNotifications/BVNotificationProperties.m b/BVSDK/BVNotifications/BVNotificationProperties.m similarity index 100% rename from Pod/BVNotifications/BVNotificationProperties.m rename to BVSDK/BVNotifications/BVNotificationProperties.m diff --git a/Pod/BVNotifications/BVNotificationsAnalyticsHelper.h b/BVSDK/BVNotifications/BVNotificationsAnalyticsHelper.h similarity index 100% rename from Pod/BVNotifications/BVNotificationsAnalyticsHelper.h rename to BVSDK/BVNotifications/BVNotificationsAnalyticsHelper.h diff --git a/Pod/BVNotifications/BVNotificationsAnalyticsHelper.m b/BVSDK/BVNotifications/BVNotificationsAnalyticsHelper.m similarity index 100% rename from Pod/BVNotifications/BVNotificationsAnalyticsHelper.m rename to BVSDK/BVNotifications/BVNotificationsAnalyticsHelper.m diff --git a/Pod/BVNotifications/BVOpenURLMetaData.h b/BVSDK/BVNotifications/BVOpenURLMetaData.h similarity index 100% rename from Pod/BVNotifications/BVOpenURLMetaData.h rename to BVSDK/BVNotifications/BVOpenURLMetaData.h diff --git a/Pod/BVNotifications/BVOpenURLMetaData.m b/BVSDK/BVNotifications/BVOpenURLMetaData.m similarity index 97% rename from Pod/BVNotifications/BVOpenURLMetaData.m rename to BVSDK/BVNotifications/BVOpenURLMetaData.m index ce8ad0ac..c7425819 100644 --- a/Pod/BVNotifications/BVOpenURLMetaData.m +++ b/BVSDK/BVNotifications/BVOpenURLMetaData.m @@ -21,7 +21,7 @@ - (instancetype)initWithURL:(NSURL *)url { return nil; } - if (self = [super init]) { + if ((self = [super init])) { NSArray *queries = [url.query componentsSeparatedByString:@"&"]; for (NSString *query in queries) { NSArray *comps = [query componentsSeparatedByString:@"="]; diff --git a/Pod/BVNotifications/BVProductReviewNotificationCenter.h b/BVSDK/BVNotifications/BVProductReviewNotificationCenter.h similarity index 100% rename from Pod/BVNotifications/BVProductReviewNotificationCenter.h rename to BVSDK/BVNotifications/BVProductReviewNotificationCenter.h diff --git a/Pod/BVNotifications/BVProductReviewNotificationCenter.m b/BVSDK/BVNotifications/BVProductReviewNotificationCenter.m similarity index 100% rename from Pod/BVNotifications/BVProductReviewNotificationCenter.m rename to BVSDK/BVNotifications/BVProductReviewNotificationCenter.m diff --git a/Pod/BVNotifications/BVProductReviewNotificationConfigurationLoader.h b/BVSDK/BVNotifications/BVProductReviewNotificationConfigurationLoader.h similarity index 100% rename from Pod/BVNotifications/BVProductReviewNotificationConfigurationLoader.h rename to BVSDK/BVNotifications/BVProductReviewNotificationConfigurationLoader.h diff --git a/Pod/BVNotifications/BVProductReviewNotificationConfigurationLoader.m b/BVSDK/BVNotifications/BVProductReviewNotificationConfigurationLoader.m similarity index 96% rename from Pod/BVNotifications/BVProductReviewNotificationConfigurationLoader.m rename to BVSDK/BVNotifications/BVProductReviewNotificationConfigurationLoader.m index 33bc6fb4..f55fecc4 100644 --- a/Pod/BVNotifications/BVProductReviewNotificationConfigurationLoader.m +++ b/BVSDK/BVNotifications/BVProductReviewNotificationConfigurationLoader.m @@ -19,7 +19,7 @@ + (void)load { } + (id)sharedManager { - static BVProductReviewNotificationConfigurationLoader *shared; + __strong static BVProductReviewNotificationConfigurationLoader *shared; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shared = [[self alloc] init]; @@ -29,7 +29,7 @@ + (id)sharedManager { } - (id)init { - if (self = [super init]) { + if ((self = [super init])) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivePINAPIKey:) diff --git a/Pod/BVNotifications/BVProductReviewNotificationProperties.h b/BVSDK/BVNotifications/BVProductReviewNotificationProperties.h similarity index 100% rename from Pod/BVNotifications/BVProductReviewNotificationProperties.h rename to BVSDK/BVNotifications/BVProductReviewNotificationProperties.h diff --git a/Pod/BVNotifications/BVProductReviewNotificationProperties.m b/BVSDK/BVNotifications/BVProductReviewNotificationProperties.m similarity index 100% rename from Pod/BVNotifications/BVProductReviewNotificationProperties.m rename to BVSDK/BVNotifications/BVProductReviewNotificationProperties.m diff --git a/Pod/BVNotifications/BVProductReviewRichNotificationCenter.h b/BVSDK/BVNotifications/BVProductReviewRichNotificationCenter.h similarity index 100% rename from Pod/BVNotifications/BVProductReviewRichNotificationCenter.h rename to BVSDK/BVNotifications/BVProductReviewRichNotificationCenter.h diff --git a/Pod/BVNotifications/BVProductReviewRichNotificationCenter.m b/BVSDK/BVNotifications/BVProductReviewRichNotificationCenter.m similarity index 100% rename from Pod/BVNotifications/BVProductReviewRichNotificationCenter.m rename to BVSDK/BVNotifications/BVProductReviewRichNotificationCenter.m diff --git a/Pod/BVNotifications/BVProductReviewSimpleNotificationCenter.h b/BVSDK/BVNotifications/BVProductReviewSimpleNotificationCenter.h similarity index 100% rename from Pod/BVNotifications/BVProductReviewSimpleNotificationCenter.h rename to BVSDK/BVNotifications/BVProductReviewSimpleNotificationCenter.h diff --git a/Pod/BVNotifications/BVProductReviewSimpleNotificationCenter.m b/BVSDK/BVNotifications/BVProductReviewSimpleNotificationCenter.m similarity index 100% rename from Pod/BVNotifications/BVProductReviewSimpleNotificationCenter.m rename to BVSDK/BVNotifications/BVProductReviewSimpleNotificationCenter.m diff --git a/Pod/BVNotifications/BVStoreNotificationConfigurationLoader.h b/BVSDK/BVNotifications/BVStoreNotificationConfigurationLoader.h similarity index 100% rename from Pod/BVNotifications/BVStoreNotificationConfigurationLoader.h rename to BVSDK/BVNotifications/BVStoreNotificationConfigurationLoader.h diff --git a/Pod/BVNotifications/BVStoreNotificationConfigurationLoader.m b/BVSDK/BVNotifications/BVStoreNotificationConfigurationLoader.m similarity index 97% rename from Pod/BVNotifications/BVStoreNotificationConfigurationLoader.m rename to BVSDK/BVNotifications/BVStoreNotificationConfigurationLoader.m index 21618af9..82440db7 100644 --- a/Pod/BVNotifications/BVStoreNotificationConfigurationLoader.m +++ b/BVSDK/BVNotifications/BVStoreNotificationConfigurationLoader.m @@ -22,7 +22,7 @@ + (void)load { } + (id)sharedManager { - static BVStoreNotificationConfigurationLoader *shared; + __strong static BVStoreNotificationConfigurationLoader *shared; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ shared = [[self alloc] init]; @@ -32,7 +32,7 @@ + (id)sharedManager { } - (id)init { - if (self = [super init]) { + if ((self = [super init])) { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveConversationsStoreAPIKey:) diff --git a/Pod/BVNotifications/BVStoreReviewNotificationCenter.h b/BVSDK/BVNotifications/BVStoreReviewNotificationCenter.h similarity index 100% rename from Pod/BVNotifications/BVStoreReviewNotificationCenter.h rename to BVSDK/BVNotifications/BVStoreReviewNotificationCenter.h diff --git a/Pod/BVNotifications/BVStoreReviewNotificationCenter.m b/BVSDK/BVNotifications/BVStoreReviewNotificationCenter.m similarity index 100% rename from Pod/BVNotifications/BVStoreReviewNotificationCenter.m rename to BVSDK/BVNotifications/BVStoreReviewNotificationCenter.m diff --git a/Pod/BVNotifications/BVStoreReviewNotificationProperties.h b/BVSDK/BVNotifications/BVStoreReviewNotificationProperties.h similarity index 100% rename from Pod/BVNotifications/BVStoreReviewNotificationProperties.h rename to BVSDK/BVNotifications/BVStoreReviewNotificationProperties.h diff --git a/Pod/BVNotifications/BVStoreReviewNotificationProperties.m b/BVSDK/BVNotifications/BVStoreReviewNotificationProperties.m similarity index 100% rename from Pod/BVNotifications/BVStoreReviewNotificationProperties.m rename to BVSDK/BVNotifications/BVStoreReviewNotificationProperties.m diff --git a/Pod/BVNotifications/BVStoreReviewRichNotificationCenter.h b/BVSDK/BVNotifications/BVStoreReviewRichNotificationCenter.h similarity index 100% rename from Pod/BVNotifications/BVStoreReviewRichNotificationCenter.h rename to BVSDK/BVNotifications/BVStoreReviewRichNotificationCenter.h diff --git a/Pod/BVNotifications/BVStoreReviewRichNotificationCenter.m b/BVSDK/BVNotifications/BVStoreReviewRichNotificationCenter.m similarity index 100% rename from Pod/BVNotifications/BVStoreReviewRichNotificationCenter.m rename to BVSDK/BVNotifications/BVStoreReviewRichNotificationCenter.m diff --git a/Pod/BVNotifications/BVStoreReviewSimpleNotificationCenter.h b/BVSDK/BVNotifications/BVStoreReviewSimpleNotificationCenter.h similarity index 100% rename from Pod/BVNotifications/BVStoreReviewSimpleNotificationCenter.h rename to BVSDK/BVNotifications/BVStoreReviewSimpleNotificationCenter.h diff --git a/Pod/BVNotifications/BVStoreReviewSimpleNotificationCenter.m b/BVSDK/BVNotifications/BVStoreReviewSimpleNotificationCenter.m similarity index 100% rename from Pod/BVNotifications/BVStoreReviewSimpleNotificationCenter.m rename to BVSDK/BVNotifications/BVStoreReviewSimpleNotificationCenter.m diff --git a/Pod/BVNotifications/mapThumbnail.png b/BVSDK/BVNotifications/mapThumbnail.png similarity index 100% rename from Pod/BVNotifications/mapThumbnail.png rename to BVSDK/BVNotifications/mapThumbnail.png diff --git a/Pod/BVRecommendations/BVProductRecommendationView.h b/BVSDK/BVRecommendations/BVProductRecommendationView.h similarity index 100% rename from Pod/BVRecommendations/BVProductRecommendationView.h rename to BVSDK/BVRecommendations/BVProductRecommendationView.h diff --git a/Pod/BVRecommendations/BVProductRecommendationView.m b/BVSDK/BVRecommendations/BVProductRecommendationView.m similarity index 100% rename from Pod/BVRecommendations/BVProductRecommendationView.m rename to BVSDK/BVRecommendations/BVProductRecommendationView.m diff --git a/Pod/BVRecommendations/BVProductRecommendationsContainer.h b/BVSDK/BVRecommendations/BVProductRecommendationsContainer.h similarity index 100% rename from Pod/BVRecommendations/BVProductRecommendationsContainer.h rename to BVSDK/BVRecommendations/BVProductRecommendationsContainer.h diff --git a/Pod/BVRecommendations/BVProductRecommendationsContainer.m b/BVSDK/BVRecommendations/BVProductRecommendationsContainer.m similarity index 100% rename from Pod/BVRecommendations/BVProductRecommendationsContainer.m rename to BVSDK/BVRecommendations/BVProductRecommendationsContainer.m diff --git a/Pod/BVRecommendations/BVProductReview.h b/BVSDK/BVRecommendations/BVProductReview.h similarity index 100% rename from Pod/BVRecommendations/BVProductReview.h rename to BVSDK/BVRecommendations/BVProductReview.h diff --git a/Pod/BVRecommendations/BVProductReview.m b/BVSDK/BVRecommendations/BVProductReview.m similarity index 100% rename from Pod/BVRecommendations/BVProductReview.m rename to BVSDK/BVRecommendations/BVProductReview.m diff --git a/Pod/BVRecommendations/BVRecommendations.h b/BVSDK/BVRecommendations/BVRecommendations.h similarity index 100% rename from Pod/BVRecommendations/BVRecommendations.h rename to BVSDK/BVRecommendations/BVRecommendations.h diff --git a/Pod/BVRecommendations/BVRecommendationsLoader.h b/BVSDK/BVRecommendations/BVRecommendationsLoader.h similarity index 88% rename from Pod/BVRecommendations/BVRecommendationsLoader.h rename to BVSDK/BVRecommendations/BVRecommendationsLoader.h index 445d487d..c649a999 100644 --- a/Pod/BVRecommendations/BVRecommendationsLoader.h +++ b/BVSDK/BVRecommendations/BVRecommendationsLoader.h @@ -11,13 +11,20 @@ #import "BVRecommendationsRequest.h" #import "BVShopperProfile.h" -/// Loads product recommendations. -@interface BVRecommendationsLoader : NSObject - typedef void (^recommendationsCompletionHandler)( NSArray *__nonnull); typedef void (^recommendationsErrorHandler)(NSError *__nonnull); +/// Loads product recommendations. +@interface BVRecommendationsLoader : NSObject + +/** + Purges the internal cache of the BVRecommendation Engine. + + @availability 3.3.0 and later + */ ++ (void)purgeRecommendationsCache; + /** Load product recommendations based on data fed in from `request`. diff --git a/Pod/BVRecommendations/BVRecommendationsLoader.m b/BVSDK/BVRecommendations/BVRecommendationsLoader.m similarity index 87% rename from Pod/BVRecommendations/BVRecommendationsLoader.m rename to BVSDK/BVRecommendations/BVRecommendationsLoader.m index bbdb7b5b..90357c86 100644 --- a/Pod/BVRecommendations/BVRecommendationsLoader.m +++ b/BVSDK/BVRecommendations/BVRecommendationsLoader.m @@ -7,21 +7,32 @@ #import -#import "BVAnalyticsManager.h" #import "BVCommon.h" +#import "BVNetworkingManager.h" #import "BVRecommendationsLoader.h" #import "BVRecsAnalyticsHelper.h" #import "BVSDKConfiguration.h" #import "BVShopperProfileRequestCache.h" +@interface BVRecommendationsLoader () +@end + @implementation BVRecommendationsLoader ++ (void)purgeRecommendationsCache { + [[BVShopperProfileRequestCache sharedCache] removeAllCachedResponses]; +} + +- (instancetype)init { + return ((self = [super init])); +} + - (void)loadRequest:(BVRecommendationsRequest *)request completionHandler:(recommendationsCompletionHandler)completionHandler errorHandler:(recommendationsErrorHandler)errorHandler { // check if SDK is properly configured // if not, hit the error handler - if ([self isSDKValid] == false) { + if (![self isSDKValid]) { [self errorOnMainThread:[self invalidSDKError] handler:errorHandler]; return; } @@ -82,6 +93,14 @@ - (void)loadRequest:(BVRecommendationsRequest *)request NSCachedURLResponse *cachedResp = [cache cachedResponseForRequest:networkRequest]; + NSURLSession *session = nil; + id sessionDelegate = + [BVSDKManager sharedManager].urlSessionDelegate; + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector(URLSessionForBVObject:)]) { + session = [sessionDelegate URLSessionForBVObject:self]; + } + if (cachedResp) { NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:cachedResp.data @@ -103,7 +122,9 @@ - (void)loadRequest:(BVRecommendationsRequest *)request return; } - NSURLSessionDataTask *task = [[NSURLSession sharedSession] + session = session ?: [BVNetworkingManager sharedManager].bvNetworkingSession; + + NSURLSessionDataTask *task = [session dataTaskWithRequest:networkRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { @@ -167,6 +188,14 @@ - (void)loadRequest:(BVRecommendationsRequest *)request }]; [task resume]; + + if (sessionDelegate && + [sessionDelegate respondsToSelector:@selector + (URLSessionTask:fromBVObject:withURLSession:)]) { + [sessionDelegate URLSessionTask:task + fromBVObject:self + withURLSession:session]; + } } - (void) @@ -200,10 +229,10 @@ - (BOOL)isSDKValid { if (clientId == nil || passKey == nil || [clientId isEqualToString:@""] || [passKey isEqualToString:@""]) { - return false; + return NO; } - return true; + return YES; } - (NSError *)invalidSDKError { diff --git a/Pod/BVRecommendations/BVRecommendationsRequest.h b/BVSDK/BVRecommendations/BVRecommendationsRequest.h similarity index 100% rename from Pod/BVRecommendations/BVRecommendationsRequest.h rename to BVSDK/BVRecommendations/BVRecommendationsRequest.h diff --git a/Pod/BVRecommendations/BVRecommendationsRequest.m b/BVSDK/BVRecommendations/BVRecommendationsRequest.m similarity index 100% rename from Pod/BVRecommendations/BVRecommendationsRequest.m rename to BVSDK/BVRecommendations/BVRecommendationsRequest.m diff --git a/Pod/BVRecommendations/BVRecommendedProduct.h b/BVSDK/BVRecommendations/BVRecommendedProduct.h similarity index 100% rename from Pod/BVRecommendations/BVRecommendedProduct.h rename to BVSDK/BVRecommendations/BVRecommendedProduct.h diff --git a/Pod/BVRecommendations/BVRecommendedProduct.m b/BVSDK/BVRecommendations/BVRecommendedProduct.m similarity index 100% rename from Pod/BVRecommendations/BVRecommendedProduct.m rename to BVSDK/BVRecommendations/BVRecommendedProduct.m diff --git a/Pod/BVRecommendations/BVRecsAnalyticsHelper.h b/BVSDK/BVRecommendations/BVRecsAnalyticsHelper.h similarity index 100% rename from Pod/BVRecommendations/BVRecsAnalyticsHelper.h rename to BVSDK/BVRecommendations/BVRecsAnalyticsHelper.h diff --git a/Pod/BVRecommendations/BVRecsAnalyticsHelper.m b/BVSDK/BVRecommendations/BVRecsAnalyticsHelper.m similarity index 98% rename from Pod/BVRecommendations/BVRecsAnalyticsHelper.m rename to BVSDK/BVRecommendations/BVRecsAnalyticsHelper.m index 85a55b88..d6fcf4ea 100644 --- a/Pod/BVRecommendations/BVRecsAnalyticsHelper.m +++ b/BVSDK/BVRecommendations/BVRecsAnalyticsHelper.m @@ -13,7 +13,7 @@ @implementation BVRecsAnalyticsHelper -static BVRecsAnalyticsHelper *analyticsInstance = nil; +__strong static BVRecsAnalyticsHelper *analyticsInstance = nil; static const NSString *bvProductName = @"Recommendations"; diff --git a/Pod/BVRecommendations/BVShopperProfile.h b/BVSDK/BVRecommendations/BVShopperProfile.h similarity index 100% rename from Pod/BVRecommendations/BVShopperProfile.h rename to BVSDK/BVRecommendations/BVShopperProfile.h diff --git a/Pod/BVRecommendations/BVShopperProfile.m b/BVSDK/BVRecommendations/BVShopperProfile.m similarity index 100% rename from Pod/BVRecommendations/BVShopperProfile.m rename to BVSDK/BVRecommendations/BVShopperProfile.m diff --git a/Pod/BVRecommendations/Private/BVShopperProfileRequestCache.h b/BVSDK/BVRecommendations/Private/BVShopperProfileRequestCache.h similarity index 100% rename from Pod/BVRecommendations/Private/BVShopperProfileRequestCache.h rename to BVSDK/BVRecommendations/Private/BVShopperProfileRequestCache.h diff --git a/Pod/BVRecommendations/BVShopperProfileRequestCache.m b/BVSDK/BVRecommendations/Private/BVShopperProfileRequestCache.m similarity index 100% rename from Pod/BVRecommendations/BVShopperProfileRequestCache.m rename to BVSDK/BVRecommendations/Private/BVShopperProfileRequestCache.m diff --git a/Support Files/BVSDK.h b/BVSDK/Support/BVSDK.h similarity index 83% rename from Support Files/BVSDK.h rename to BVSDK/Support/BVSDK.h index 6f3a9341..e1c1688f 100644 --- a/Support Files/BVSDK.h +++ b/BVSDK/Support/BVSDK.h @@ -17,7 +17,6 @@ FOUNDATION_EXPORT const unsigned char BVSDKVersionString[]; // using statements like #import // Common -#import #import #import @@ -45,10 +44,33 @@ FOUNDATION_EXPORT const unsigned char BVSDKVersionString[]; #import // Conversations +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import + #import #import #import -#import #import #import #import @@ -56,9 +78,7 @@ FOUNDATION_EXPORT const unsigned char BVSDKVersionString[]; #import #import -#import #import -#import #import #import #import @@ -77,7 +97,6 @@ FOUNDATION_EXPORT const unsigned char BVSDKVersionString[]; #import #import -#import #import #import #import @@ -94,7 +113,6 @@ FOUNDATION_EXPORT const unsigned char BVSDKVersionString[]; #import #import -#import #import #import #import @@ -151,4 +169,3 @@ FOUNDATION_EXPORT const unsigned char BVSDKVersionString[]; #import #import #import -#import diff --git a/Support Files/Info.plist b/BVSDK/Support/Info.plist similarity index 96% rename from Support Files/Info.plist rename to BVSDK/Support/Info.plist index 60ab2975..53ec2b4d 100644 --- a/Support Files/Info.plist +++ b/BVSDK/Support/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.9.0 + 7.0.0 CFBundleVersion 1 LSApplicationCategoryType diff --git a/Support Files/integrate-dynamic-framework.sh b/BVSDK/Support/integrate-dynamic-framework.sh similarity index 100% rename from Support Files/integrate-dynamic-framework.sh rename to BVSDK/Support/integrate-dynamic-framework.sh diff --git a/Tests/Tests/BVInternalAnalyticsTest.m b/BVSDKTests/AnalyticsTests/BVInternalAnalyticsTest.m similarity index 96% rename from Tests/Tests/BVInternalAnalyticsTest.m rename to BVSDKTests/AnalyticsTests/BVInternalAnalyticsTest.m index c4452235..7f47bc60 100644 --- a/Tests/Tests/BVInternalAnalyticsTest.m +++ b/BVSDKTests/AnalyticsTests/BVInternalAnalyticsTest.m @@ -30,8 +30,8 @@ @interface BVAnalyticsManager (TestAccessors) @interface BVInternalAnalyticsTests : BVBaseStubTestCase { XCTestExpectation *impressionExpectation; XCTestExpectation *pageviewExpectation; - int numberOfExpectedImpressionAnalyticsEvents; - int numberOfExpectedPageviewAnalyticsEvents; + NSInteger numberOfExpectedImpressionAnalyticsEvents; + NSInteger numberOfExpectedPageviewAnalyticsEvents; } @end @@ -100,7 +100,7 @@ - (void)waitForAnalytics { - (void)analyticsImpressionEventCompleted:(NSNotification *)notification { NSLog(@"analytics impression event fired in tests: %i", - numberOfExpectedImpressionAnalyticsEvents); + (int)numberOfExpectedImpressionAnalyticsEvents); NSError *err = (NSError *)[notification object]; if (err) { @@ -115,7 +115,7 @@ - (void)analyticsImpressionEventCompleted:(NSNotification *)notification { - (void)analyticsPageviewEventCompleted:(NSNotification *)notification { NSLog(@"analytics pageview event fired in tests: %i", - numberOfExpectedPageviewAnalyticsEvents); + (int)numberOfExpectedPageviewAnalyticsEvents); NSError *err = (NSError *)[notification object]; if (err) { @@ -205,8 +205,8 @@ - (void)testBigAnalyticsEvent { BVProductDisplayPageRequest *pdpRequest = [[BVProductDisplayPageRequest alloc] initWithProductId:@"test4"]; - [pdpRequest includeStatistics:PDPContentTypeReviews]; - [pdpRequest includeStatistics:PDPContentTypeQuestions]; + [pdpRequest includeStatistics:BVProductIncludeTypeValueReviews]; + [pdpRequest includeStatistics:BVProductIncludeTypeValueQuestions]; [pdpRequest load:^(BVProductsResponse *__nonnull response) { [[BVAnalyticsManager sharedManager] flushQueue]; } @@ -251,8 +251,8 @@ - (void)testAnalyticsProducts { // Should send one product page view and one or impression for a review BVProductDisplayPageRequest *pdpRequest = [[BVProductDisplayPageRequest alloc] initWithProductId:@"test4"]; - [pdpRequest includeStatistics:PDPContentTypeReviews]; - [pdpRequest includeStatistics:PDPContentTypeQuestions]; + [pdpRequest includeStatistics:BVProductIncludeTypeValueReviews]; + [pdpRequest includeStatistics:BVProductIncludeTypeValueQuestions]; [pdpRequest load:^(BVProductsResponse *__nonnull response) { [[BVAnalyticsManager sharedManager] flushQueue]; } diff --git a/Tests/BVPixelTests.m b/BVSDKTests/AnalyticsTests/BVPixelTests.m similarity index 98% rename from Tests/BVPixelTests.m rename to BVSDKTests/AnalyticsTests/BVPixelTests.m index 9b323ad6..2a920414 100644 --- a/Tests/BVPixelTests.m +++ b/BVSDKTests/AnalyticsTests/BVPixelTests.m @@ -9,6 +9,7 @@ #import "BVAnalyticsManager.h" #import "BVBaseStubTestCase.h" +#import "BVLocaleServiceManager.h" #import "BVSDKConfiguration.h" #import "BVSDKManager.h" @@ -50,8 +51,8 @@ - (BOOL)containsDictionary:(NSDictionary *)otherDictionary { @interface BVPixelTests : BVBaseStubTestCase { XCTestExpectation *impressionExpectation; XCTestExpectation *pageviewExpectation; - int numberOfExpectedImpressionAnalyticsEvents; - int numberOfExpectedPageviewAnalyticsEvents; + NSInteger numberOfExpectedImpressionAnalyticsEvents; + NSInteger numberOfExpectedPageviewAnalyticsEvents; } @end @@ -114,7 +115,7 @@ - (void)waitForAnalytics { - (void)analyticsImpressionEventCompleted:(NSNotification *)notification { NSLog(@"analytics impression event fired in tests: %i", - numberOfExpectedImpressionAnalyticsEvents); + (int)numberOfExpectedImpressionAnalyticsEvents); NSError *err = (NSError *)[notification object]; if (err) { @@ -130,7 +131,7 @@ - (void)analyticsImpressionEventCompleted:(NSNotification *)notification { - (void)analyticsPageviewEventCompleted:(NSNotification *)notification { NSLog(@"analytics pageview event fired in tests: %i", - numberOfExpectedPageviewAnalyticsEvents); + (int)numberOfExpectedPageviewAnalyticsEvents); NSError *err = (NSError *)[notification object]; if (err) { diff --git a/BVSDKTests/BVSDKTests-Bridging-Header.h b/BVSDKTests/BVSDKTests-Bridging-Header.h deleted file mode 100644 index ed2628bc..00000000 --- a/BVSDKTests/BVSDKTests-Bridging-Header.h +++ /dev/null @@ -1,12 +0,0 @@ -// -// BVSDKTests-Bridging-Header.h -// BVSDK -// -// Copyright © 2016 Bazaarvoice. All rights reserved. -// - -#ifndef BVSDKTests_Bridging_Header_h -#define BVSDKTests_Bridging_Header_h - -#import "UIImage+BundleLocator.h" -#endif /* BVSDKTests_Bridging_Header_h */ diff --git a/BVSDKTests/CommonTests/BVBaseStubTestCase.h b/BVSDKTests/CommonTests/BVBaseStubTestCase.h new file mode 100644 index 00000000..7f56b54c --- /dev/null +++ b/BVSDKTests/CommonTests/BVBaseStubTestCase.h @@ -0,0 +1,47 @@ +// +// BVBaseStubTestCase.h +// BVSDK +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#ifndef BVBaseStubTestCase_h +#define BVBaseStubTestCase_h + +#import + +// 3rd Party +#import +#import + +@interface BVBaseStubTestCase : XCTestCase + +@property(nonatomic, assign) NSTimeInterval barrierTimeout; + +// Will stub out calls to bazaarvoice.com, return 200, and a resultFile with +// Content-Type = application/json +- (void)addStubWith200ResponseForJSONFileNamed:(NSString *)resultFile; + +// Use this method to stub calls to bazaarvoice and add any resultFile, headers, +// and HTTP status you want +- (void)addStubWithResultFile:(NSString *)resultFile + statusCode:(NSInteger)httpStatus + withHeaders:(NSDictionary *)httpHeaders; + +// Use these methods to protect against XCode going on to run other tests until +// this test suite has completed all tests. The problem is that even if you are +// waiting on expectations that doesn't mean that Xcode won't start running +// other tests. So, for example, if you're kicking off tests which wait for +// notifications to be delivered and it's possible that other tests will spawn +// notifications that you're waiting for, well, then you can be hosed. So the +// way this works is that whenever you're entering a context you make a +// 'retainBarrier' call and whenever you leave you make a 'releaseBarrier' call. +// Of course, make sure they're balanced else you're error out. +- (BOOL)isBarrierHeld; +- (void)retainBarrier; +- (void)releaseBarrier; +- (void)forceWaitForBarrierWithTimeout:(NSTimeInterval)barrierTimeout; + +@end + +#endif /* BVBaseStubTestCase_h */ diff --git a/BVSDKTests/CommonTests/BVBaseStubTestCase.m b/BVSDKTests/CommonTests/BVBaseStubTestCase.m new file mode 100644 index 00000000..f8d7cbdb --- /dev/null +++ b/BVSDKTests/CommonTests/BVBaseStubTestCase.m @@ -0,0 +1,145 @@ +// +// BVBaseStubTestCase.m +// BVSDK +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#define WaitForAllGroupsToBeEmpty(timeout) \ + do { \ + if (![self waitForGroupToBeEmptyWithTimeout:timeout]) { \ + XCTFail(@"Timed out waiting for groups to empty."); \ + } \ + } while (0) + +#import "BVBaseStubTestCase.h" +#import + +@interface BVBaseStubTestCase () +// This is to synchronize on tests which have side-effects with other tests +// being ran. It's basically a barrier between XCTestCases so that Xcode won't +// enqueue new tests until these tests complete. +@property(nonatomic) dispatch_group_t barrier; +@property(nonatomic, readonly) dispatch_queue_t barrierQueue; +@end + +@implementation BVBaseStubTestCase + +@synthesize barrier = _barrier, barrierQueue = _barrierQueue; + +- (dispatch_queue_t)barrierQueue { + if (NULL == _barrierQueue) { + _barrierQueue = dispatch_queue_create( + "com.bazaarvoice.BVSDKTest.barrierQueue", DISPATCH_QUEUE_SERIAL); + } + return _barrierQueue; +} + +- (dispatch_group_t)barrier { + __block dispatch_group_t tempBarrier = NULL; + dispatch_sync(self.barrierQueue, ^{ + tempBarrier = _barrier; + }); + return tempBarrier; +} + +- (void)setBarrier:(dispatch_group_t)barrier { + dispatch_sync(self.barrierQueue, ^{ + _barrier = barrier; + }); +} + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each + // test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each + // test method in the class. + [super tearDown]; + [OHHTTPStubs removeAllStubs]; +} + +- (void)addStubWith200ResponseForJSONFileNamed:(NSString *)resultFile { + + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + return [request.URL.host containsString:@"bazaarvoice.com"]; + } + withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + // return normal user profile from /users API + return [[OHHTTPStubsResponse + responseWithFileAtPath:OHPathForFile(resultFile, self.class) + statusCode:200 + headers:@{ + @"Content-Type" : + @"application/json;charset=utf-8" + }] responseTime:OHHTTPStubsDownloadSpeedWifi]; + }]; +} + +- (void)addStubWithResultFile:(NSString *)resultFile + statusCode:(NSInteger)httpStatus + withHeaders:(NSDictionary *)httpHeaders { + + [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { + return [request.URL.host containsString:@"bazaarvoice.com"]; + } + withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { + // return normal user profile from /users API + return [[OHHTTPStubsResponse + responseWithFileAtPath:OHPathForFile(resultFile, self.class) + statusCode:(int)httpStatus + headers:httpHeaders] + responseTime:OHHTTPStubsDownloadSpeedWifi]; + }]; +} + +#pragma mark - Synchronization Primitives + +/* + * Thanks to the people at obj.io: https://www.objc.io/issues/15-testing/xctest/ + */ +- (BOOL)isBarrierHeld { + return (NULL != self.barrier); +} + +- (void)retainBarrier { + if (NULL == self.barrier) { + self.barrier = dispatch_group_create(); + } + dispatch_group_enter(self.barrier); +} + +- (void)releaseBarrier { + if (NULL != self.barrier) { + dispatch_group_leave(self.barrier); + } +} + +- (void)forceWaitForBarrierWithTimeout:(NSTimeInterval)barrierTimeout { + (void)[self waitForGroupToBeEmptyWithTimeout:barrierTimeout]; +} + +- (BOOL)waitForGroupToBeEmptyWithTimeout:(NSTimeInterval)timeout { + + if (NULL == self.barrier) { + return YES; + } + + NSDate *const end = [[NSDate date] dateByAddingTimeInterval:timeout]; + int64_t delta = (int64_t)timeout; + dispatch_time_t dispatchTimeout = dispatch_time(DISPATCH_TIME_NOW, delta); + + __block BOOL didComplete = NO; + dispatch_group_notify(self.barrier, dispatch_get_main_queue(), ^{ + didComplete = YES; + }); + + dispatch_group_wait(self.barrier, dispatchTimeout); + + return didComplete && (0. < [end timeIntervalSinceNow]); +} + +@end diff --git a/BVSDKTests/CommonTests/BVLocaleManagerTests.m b/BVSDKTests/CommonTests/BVLocaleManagerTests.m new file mode 100644 index 00000000..f8fa3171 --- /dev/null +++ b/BVSDKTests/CommonTests/BVLocaleManagerTests.m @@ -0,0 +1,229 @@ +// +// BVLocaleManagerTests.m +// BVSDKTests +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import + +#import "BVAnalyticsManager.h" +#import "BVBaseStubTestCase.h" +#import "BVLocaleServiceManager.h" +#import "BVSDKConfiguration.h" +#import "BVSDKManager.h" + +static NSString *configuredLocaleIdentifier; +static NSString *nonEUProductionValue; +static NSString *nonEUStagingValue; +static NSString *euProductionValue; +static NSString *euStagingValue; + +@interface BVLocaleManagerTests : XCTestCase +@end + +@implementation BVLocaleManagerTests + +- (void)setUp { + [super setUp]; + + nonEUProductionValue = @"https://network.bazaarvoice.com/event"; + nonEUStagingValue = @"https://network-stg.bazaarvoice.com/event"; + euProductionValue = @"https://network-eu.bazaarvoice.com/event"; + euStagingValue = @"https://network-eu-stg.bazaarvoice.com/event"; + + configuredLocaleIdentifier = @"en_GB"; + [[BVAnalyticsManager sharedManager] setFlushInterval:0.1]; + + NSDictionary *configDict = @{ + @"clientId" : @"mobileBVPixelTestsiOS", + @"analyticsLocaleIdentifier" : configuredLocaleIdentifier + }; + + [BVSDKManager configureWithConfiguration:configDict + configType:BVConfigurationTypeProd]; + + [[BVSDKManager sharedManager] setLogLevel:BVLogLevelAnalyticsOnly]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + [self testAnalyticsLocaleRoutingEfficacy]; + }]; +} + +#pragma mark - BVLocaleServiceManagerServiceAnalytics + +- (void)testAnalyticsLocaleConfiguration { + NSLocale *greatBritainLocale = + [[NSLocale alloc] initWithLocaleIdentifier:configuredLocaleIdentifier]; + NSLocale *currentLocale = [BVAnalyticsManager sharedManager].analyticsLocale; + + XCTAssertEqualObjects(greatBritainLocale, currentLocale, + @"Configured Locale not equal to Generated Locale"); + + /// Reset, just in case. + configuredLocaleIdentifier = nil; +} + +- (void)testAnalyticsLocaleForEU { + + NSLocale *currentLocale = [BVAnalyticsManager sharedManager].analyticsLocale; + + NSString *stagingResource = [[BVLocaleServiceManager sharedManager] + resourceForService:BVLocaleServiceManagerServiceAnalytics + withLocale:currentLocale + andIsProduction:NO]; + XCTAssertEqualObjects(stagingResource, euStagingValue, + @"Staging resource doesn't match proper resource URL"); + + NSString *productionResource = [[BVLocaleServiceManager sharedManager] + resourceForService:BVLocaleServiceManagerServiceAnalytics + withLocale:currentLocale + andIsProduction:YES]; + XCTAssertEqualObjects( + productionResource, euProductionValue, + @"Production resource doesn't match proper resource URL"); + + /// Reset, just in case. + configuredLocaleIdentifier = nil; +} + +- (void)testAnalyticsLocaleUpdate { + XCTestExpectation *updateExpectation = + [self expectationWithDescription:@"testAnalyticsLocaleUpdate"]; + + NSLocale *currentLocale = [NSLocale autoupdatingCurrentLocale]; + [BVAnalyticsManager sharedManager].analyticsLocale = nil; + + [[NSNotificationCenter defaultCenter] + postNotificationName:NSCurrentLocaleDidChangeNotification + object:nil]; + + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + NSLocale *updatedLocale = + [BVAnalyticsManager sharedManager].analyticsLocale; + XCTAssertEqualObjects(updatedLocale, currentLocale, + @"Updated Locale not equal to Generated Locale"); + [updateExpectation fulfill]; + }); + + [self waitForExpectations:@[ updateExpectation ] timeout:6.0f]; + + /// Reset, just in case. + configuredLocaleIdentifier = nil; +} + +- (void)testAnalyticsLocaleRoutingEfficacy { + + BVLocaleServiceManager *localeServiceManager = + [BVLocaleServiceManager sharedManager]; + XCTAssertNotNil(localeServiceManager, @"localeServiceManager is nil"); + + NSArray *nonEuCountries = @[ + @"en_US", @"en_AU", @"es_CL", @"en_PH", @"fil_PH", @"ru_UA", @"ru_RU", + @"fr_CA", @"en_CA", @"pa_Guru_IN", @"en_IN", @"zh_Hans_SG" + ]; + + NSArray *euCountries = @[ + @"de_AT", // Austria + @"fr_BE", // French (Belgium) + @"de_BE", // German (Belgium) + @"bg_BG", // Bulgaria + @"fr_CH", // Switzerland + @"de_CH", // Switzerland + @"it_CH", // Switzerland + @"rm_CH", // Switzerland + @"gsw_CH", // Switzerland + @"el_CY", // Republic of Cyprus + @"cs_CZ", // Czech Republic + @"de_DE", // Germany + @"da_DK", // Denmark + @"gl_ES", // Spain + @"ca_ES", // Spain + @"es_ES", // Spain + @"eu_ES", // Spain + @"et_EE", // Estonia + @"sv_FI", // Finland + @"fi_FI", // Finland + @"fr_FR", // France + @"cy_GB", // Great Britain / UK + @"gv_GB", // Great Britain / UK + @"en_GB", // Great Britain / UK + @"kw_GB", // Great Britain / UK + @"el_GR", // Greece + @"hr_HR", // Croatia + @"hu_HU", // Hungary + @"en_IE", // Ireland + @"ga_IE", // Ireland + @"is_IS", // Iceland + @"it_IT", // Italy + @"de_LI", // Liechtenstein + @"lt_LT", // Lithuania + @"fr_LU", // Luxembourg + @"de_LU", // Luxembourg + @"lv_LV", // Latvia + @"en_MT", // Malta + @"mt_MT", // Malta + @"nl_NL", // Netherlands + @"nn_NO", // Norway + @"nb_NO", // Norway + @"pl_PL", // Poland + @"pt_PT", // Portugal + @"ro_RO", // Romania + @"sv_SE", // Sweden + @"sl_SI", // Slovenia + @"sk_SK" // Slovakia + ]; + + for (NSString *locale in nonEuCountries) { + NSLocale *nonEULocal = [[NSLocale alloc] initWithLocaleIdentifier:locale]; + XCTAssertNotNil(nonEULocal, @"Non-EU Locale is nil: %@", nonEULocal); + + NSString *nonEUProduction = [localeServiceManager + resourceForService:BVLocaleServiceManagerServiceAnalytics + withLocale:nonEULocal + andIsProduction:YES]; + + XCTAssertEqualObjects(nonEUProduction, nonEUProductionValue, + @"Non-EU production values don't match: %@", locale); + + NSString *nonEUStaging = [localeServiceManager + resourceForService:BVLocaleServiceManagerServiceAnalytics + withLocale:nonEULocal + andIsProduction:NO]; + + XCTAssertEqualObjects(nonEUStaging, nonEUStagingValue, + @"EU staging values don't match: %@", locale); + } + + for (NSString *locale in euCountries) { + NSLocale *euLocale = [[NSLocale alloc] initWithLocaleIdentifier:locale]; + XCTAssertNotNil(euLocale, @"EU Locale is nil: %@", euLocale); + + NSString *euProduction = [localeServiceManager + resourceForService:BVLocaleServiceManagerServiceAnalytics + withLocale:euLocale + andIsProduction:YES]; + + XCTAssertEqualObjects(euProduction, euProductionValue, + @"EU production values don't match: %@", locale); + + NSString *euStaging = [localeServiceManager + resourceForService:BVLocaleServiceManagerServiceAnalytics + withLocale:euLocale + andIsProduction:NO]; + + XCTAssertEqualObjects(euStaging, euStagingValue, + @"EU staging values don't match: %@", locale); + } +} + +@end diff --git a/BVSDKTests/CommonTests/BVNetworkDelegateTestsDelegate.h b/BVSDKTests/CommonTests/BVNetworkDelegateTestsDelegate.h new file mode 100644 index 00000000..066d0e93 --- /dev/null +++ b/BVSDKTests/CommonTests/BVNetworkDelegateTestsDelegate.h @@ -0,0 +1,18 @@ +// +// BVNetworkDelegateTestsDelegate.h +// BVSDKTests +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVURLSessionDelegate.h" +#import + +@class XCTestExpectation; +@interface BVNetworkDelegateTestsDelegate : NSObject + +@property(nonatomic, weak, nullable) XCTestExpectation *urlSessionExpectation; +@property(nonatomic, weak, nullable) + XCTestExpectation *urlSessionTaskExpectation; + +@end diff --git a/BVSDKTests/CommonTests/BVNetworkDelegateTestsDelegate.m b/BVSDKTests/CommonTests/BVNetworkDelegateTestsDelegate.m new file mode 100644 index 00000000..074a5375 --- /dev/null +++ b/BVSDKTests/CommonTests/BVNetworkDelegateTestsDelegate.m @@ -0,0 +1,79 @@ +// +// BVNetworkDelegateTestsDelegate.m +// BVSDKTests +// +// Copyright © 2018 Bazaarvoice. All rights reserved. +// + +#import "BVNetworkDelegateTestsDelegate.h" +#import + +@interface BVNetworkDelegateTestsDelegate () +@property(nonatomic, strong) dispatch_queue_t blockQueue; +@property(nonatomic, assign) BOOL urlSessionHappened; +@property(nonatomic, assign) BOOL urlSessionTaskHappened; +@end + +@implementation BVNetworkDelegateTestsDelegate + +@synthesize blockQueue = _blockQueue, + urlSessionExpectation = _urlSessionExpectation, + urlSessionTaskExpectation = _urlSessionTaskExpectation; + +- (XCTestExpectation *)urlSessionExpectation { + __block XCTestExpectation *tempExpectation = nil; + dispatch_sync(self.blockQueue, ^{ + tempExpectation = _urlSessionExpectation; + }); + return tempExpectation; +} + +- (void)setUrlSessionExpectation:(XCTestExpectation *)urlSessionExpectation { + dispatch_sync(self.blockQueue, ^{ + _urlSessionExpectation = urlSessionExpectation; + }); +} + +- (XCTestExpectation *)urlSessionTaskExpectation { + __block XCTestExpectation *tempExpectation = nil; + dispatch_sync(self.blockQueue, ^{ + tempExpectation = _urlSessionTaskExpectation; + }); + return tempExpectation; +} + +- (void)setUrlSessionTaskExpectation: + (XCTestExpectation *)urlSessionTaskExpectation { + dispatch_sync(self.blockQueue, ^{ + _urlSessionTaskExpectation = urlSessionTaskExpectation; + }); +} + +- (instancetype)init { + if ((self = [super init])) { + _blockQueue = dispatch_queue_create( + "com.bazaarvoice.BVNetworkDelegateTestsDelegate.blockQueue", + DISPATCH_QUEUE_SERIAL); + } + return self; +} + +- (nonnull NSURLSession *)URLSessionForBVObject: + (nullable id)bvObject { + if (self.urlSessionExpectation && !self.urlSessionHappened) { + self.urlSessionHappened = YES; + [self.urlSessionExpectation fulfill]; + } + return [NSURLSession sharedSession]; +} + +- (void)URLSessionTask:(nonnull NSURLSessionTask *)urlSessionTask + fromBVObject:(nonnull id)bvObject + withURLSession:(nonnull NSURLSession *)session { + if (self.urlSessionTaskExpectation && !self.urlSessionTaskHappened) { + self.urlSessionTaskHappened = YES; + [self.urlSessionTaskExpectation fulfill]; + } +} + +@end diff --git a/Tests/Tests/BVUserProfileTests.m b/BVSDKTests/CommonTests/BVUserProfileTests.m similarity index 100% rename from Tests/Tests/BVUserProfileTests.m rename to BVSDKTests/CommonTests/BVUserProfileTests.m diff --git a/Tests/Tests/ConversationsTests/DisplayTests/CommentsDisplayTests.swift b/BVSDKTests/ConversationsTests/DisplayTests/CommentsDisplayTests.swift similarity index 77% rename from Tests/Tests/ConversationsTests/DisplayTests/CommentsDisplayTests.swift rename to BVSDKTests/ConversationsTests/DisplayTests/CommentsDisplayTests.swift index 22938495..91ccbf01 100644 --- a/Tests/Tests/ConversationsTests/DisplayTests/CommentsDisplayTests.swift +++ b/BVSDKTests/ConversationsTests/DisplayTests/CommentsDisplayTests.swift @@ -17,6 +17,7 @@ class CommentsDisplayTests: XCTestCase { "apiKeyConversations": "kuy3zj9pr3n7i0wxajrzj04xo"]; BVSDKManager.configure(withConfiguration: configDict, configType: .staging) BVSDKManager.shared().setLogLevel(BVLogLevel.verbose) + BVSDKManager.shared().urlSessionDelegate = nil; } override func tearDown() { @@ -106,9 +107,9 @@ class CommentsDisplayTests: XCTestCase { let expectation = self.expectation(description: "testCommentDisplayByCommentId") let request = BVCommentsRequest(productId: "1000001", andCommentId: "12024") - request.addInclude(.authors) - request.addInclude(.products) - request.addInclude(.reviews) + .include(.commentAuthors) + .include(.commentProducts) + .include(.commentReviews) request.load({ (response) in @@ -153,9 +154,11 @@ class CommentsDisplayTests: XCTestCase { let limit : UInt16 = 90 let offset : UInt16 = 0 - let request = BVCommentsRequest(productId: "1000001", andReviewId: "192548", limit: limit, offset: offset) - request.addFilter(.contentLocale, filterOperator: .equalTo, value: "en_US") - request.addCommentSort(.commentsSubmissionTime, order: .descending) + let request = + BVCommentsRequest( + productId: "1000001", andReviewId: "192548", limit: limit, offset: offset) + .filter(on: .commentContentLocale, relationalFilterOperatorValue: .equalTo, value: "en_US") + .sort(by: .commentSubmissionTime, monotonicSortOrderValue: .descending) request.load({ (response) in @@ -172,12 +175,47 @@ class CommentsDisplayTests: XCTestCase { self.waitForExpectations(timeout: 10) { (error) in XCTAssertNil(error, "Something went horribly wrong, request took too long.") } + } + + func testCommentFilteringSortingWithNetworkDelegate() { + let mainExpectation = + self.expectation(description: "testCommentDisplayByCommentId") + + let limit : UInt16 = 90 + let offset : UInt16 = 0 + let request = + BVCommentsRequest( + productId: "1000001", andReviewId: "192548", limit: limit, offset: offset) + .filter(on: .commentContentLocale, relationalFilterOperatorValue: .equalTo, value: "en_US") + .sort(by: .commentSubmissionTime, monotonicSortOrderValue: .descending) + let testDelegate = BVNetworkDelegateTestsDelegate() + + XCTAssertNotNil( + testDelegate, "BVNetworkDelegateTestsDelegate is nil") + + testDelegate.urlSessionExpectation = + self.expectation(description: "urlSession expectation") + + testDelegate.urlSessionTaskExpectation = + self.expectation(description: "urlSessionTask expectation") + + BVSDKManager.shared().urlSessionDelegate = testDelegate + + request.load({ (response) in + XCTAssertEqual(response.results.count, 5) + mainExpectation.fulfill() + }) { (error) in + XCTFail("comments by product id display request error: \(error)") + mainExpectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } } - - func testCommentWithErrors() { let expectation = self.expectation(description: "testCommentWithErrors") @@ -201,6 +239,5 @@ class CommentsDisplayTests: XCTestCase { self.waitForExpectations(timeout: 10) { (error) in XCTAssertNil(error, "Something went horribly wrong, request took too long.") } - } } diff --git a/BVSDKTests/ConversationsTests/DisplayTests/ConversationsDisplayTests.swift b/BVSDKTests/ConversationsTests/DisplayTests/ConversationsDisplayTests.swift new file mode 100644 index 00000000..8b895ec0 --- /dev/null +++ b/BVSDKTests/ConversationsTests/DisplayTests/ConversationsDisplayTests.swift @@ -0,0 +1,254 @@ +// +// ConversationsDisplayTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +import XCTest +@testable import BVSDK + +class ConversationsDisplayTests: XCTestCase { + + override func setUp() { + super.setUp() + + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "kuy3zj9pr3n7i0wxajrzj04xo"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + } + + override func tearDown() { + super.tearDown() + } + + func testProductDisplay() { + + let expectation = self.expectation(description: "") + + let request = BVProductDisplayPageRequest(productId: "test1") + .include(.reviews, limit: 10) + .include(.questions, limit: 5) + + // .include(.reviews, limit: 10) + // .include(.questions, limit: 5) + // .includeStatistics(.reviews) + + request.load({ (response) in + + XCTAssertNotNil(response.result) + + let product = response.result! + let brand = product.brand! + XCTAssertEqual(brand.identifier, "cskg0snv1x3chrqlde0zklodb") + XCTAssertEqual(brand.name, "mysh") + XCTAssertEqual(product.productDescription, "Our pinpoint oxford is crafted from only the finest 80\'s two-ply cotton fibers.Single-needle stitching on all seams for a smooth flat appearance. Tailored with our Traditional\n straight collar and button cuffs. Machine wash. Imported.") + XCTAssertEqual(product.brandExternalId, "cskg0snv1x3chrqlde0zklodb") + XCTAssertEqual(product.imageUrl, "http://myshco.com/productImages/shirt.jpg") + XCTAssertEqual(product.name, "Dress Shirt") + XCTAssertEqual(product.categoryId, "testCategory1031") + XCTAssertEqual(product.identifier, "test1") + XCTAssertEqual(product.includedReviews.count, 10) + XCTAssertEqual(product.includedQuestions.count, 5) + expectation.fulfill() + + }) { (error) in + + XCTFail("product display request error: \(error)") + expectation.fulfill() + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + func testReviewDisplay() { + + let expectation = self.expectation(description: "") + + let request = BVReviewsRequest(productId: "test1", limit: 10, offset: 4) + .sort(by: .reviewRating, monotonicSortOrderValue: .ascending) + .filter(on: .hasPhotos, relationalFilterOperatorValue: .equalTo, value: "true") + .filter(on: .hasComments, relationalFilterOperatorValue: .equalTo, value: "false") + + request.load({ (response) in + + XCTAssertEqual(response.results.count, 10) + let review = response.results.first! + XCTAssertEqual(review.rating, 1) + XCTAssertEqual(review.title, "Morbi nibh risus, mattis id placerat a massa nunc.") + XCTAssertEqual(review.reviewText, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed rhoncus scelerisque semper. Morbi in sapien sit amet justo eleifend pellentesque! Cras sollicitudin, quam in ullamcorper faucibus, augue metus blandit justo, vitae ullamcorper tellus quam non purus. Fusce gravida rhoncus placerat. Integer tempus nunc sed elit mollis ut venenatis felis volutpat. Sed a velit et lacus lobortis aliquet? Donec dolor quam, pharetra vitae commodo et, mattis quis nibh? Quisque ultrices neque et lacus volutpat.") + XCTAssertEqual(review.moderationStatus, "APPROVED") + XCTAssertEqual(review.identifier, "191975") + XCTAssertNil(review.product) + XCTAssertEqual(review.isRatingsOnly, false) + XCTAssertEqual(review.isFeatured, false) + XCTAssertEqual(review.productId, "test1") + XCTAssertEqual(review.authorId, "endersgame") + XCTAssertEqual(review.userNickname, "endersgame") + XCTAssertEqual(review.userLocation, "San Fransisco, California") + + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).label, "Pros") + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).identifier, "Pro") + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).values!!, ["Organic Fabric", "Quality"]) + + XCTAssertEqual(review.photos.count, 1) + XCTAssertEqual(review.photos.first?.caption, "Etiam malesuada ultricies urna in scelerisque. Sed viverra blandit nibh non egestas. Sed rhoncus, ipsum in vehicula imperdiet, purus lectus sodales erat, eget ornare lacus lectus ac leo. Suspendisse tristique sollicitudin ultricies. Aliquam erat volutpat.") + XCTAssertEqual(review.photos.first?.identifier, "72586") + XCTAssertNotNil(review.photos.first?.sizes?.thumbnailUrl) + XCTAssertTrue((review.photos.first?.sizes?.normalUrl?.lowercased().contains("jpg?client=apireadonlysandbox"))!) + + XCTAssertEqual(review.contextDataValues.count, 1) + let cdv = review.contextDataValues.first! + XCTAssertEqual(cdv.value, "Female") + XCTAssertEqual(cdv.valueLabel, "Female") + XCTAssertEqual(cdv.dimensionLabel, "Gender") + XCTAssertEqual(cdv.identifier, "Gender") + + XCTAssertEqual(review.badges.first?.badgeType, BVBadgeType.merit) + XCTAssertEqual(review.badges.first?.identifier, "top10Contributor") + XCTAssertEqual(review.badges.first?.contentType, "REVIEW") + + response.results.forEach { (review) in + XCTAssertEqual(review.productId, "test1") + } + + expectation.fulfill() + + }) { (error) in + + XCTFail("product display request error: \(error)") + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + func testQuestionDisplay() { + + let expectation = self.expectation(description: "") + + let request = BVQuestionsAndAnswersRequest(productId: "test1", limit: 10, offset: 0) + .filter(on: .questionHasAnswers, relationalFilterOperatorValue: .equalTo, value: "true") + + request.load({ (response) in + + XCTAssertEqual(response.results.count, 10) + + let question = response.results.first! + XCTAssertEqual(question.questionSummary, "Das ist mein test :)") + XCTAssertEqual(question.questionDetails, "Das ist mein test :)") + XCTAssertEqual(question.userNickname, "123thisisme") + XCTAssertEqual(question.authorId, "eplz083100g") + XCTAssertEqual(question.moderationStatus, "APPROVED") + XCTAssertEqual(question.identifier, "14828") + + XCTAssertEqual(question.answers.count, 1) + + let answer = question.answers.first! + XCTAssertEqual(answer.userNickname, "asdfasdfasdfasdf") + XCTAssertEqual(answer.questionId, "14828") + XCTAssertEqual(answer.authorId, "c6ryqeb2bq0") + XCTAssertEqual(answer.moderationStatus, "APPROVED") + XCTAssertEqual(answer.identifier, "16292") + XCTAssertEqual(answer.answerText, "zxnc,vznxc osaidmf oaismdfo ims adoifmaosidmfoiamsdfimasdf") + + response.results.forEach { (question) in + XCTAssertEqual(question.productId, "test1") + } + + expectation.fulfill() + + }) { (error) in + + XCTFail("product display request error: \(error)") + expectation.fulfill() + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + + func testInlineRatingsDisplayOneProduct() { + + let expectation = self.expectation(description: "") + + let request = + BVBulkRatingsRequest(productIds: ["test3"], statistics: .bulkRatingAll) + .filter(on: .bulkRatingContentLocale, relationalFilterOperatorValue: .equalTo, values: ["en_US"]) + + request.load({ (response) in + XCTAssertEqual(response.results.count, 1) + XCTAssertEqual(response.results[0].productId, "test3") + XCTAssertEqual(response.results[0].reviewStatistics?.totalReviewCount, 29) + XCTAssertNotNil(response.results[0].reviewStatistics?.averageOverallRating) + XCTAssertEqual(response.results[0].reviewStatistics?.overallRatingRange, 5) + XCTAssertEqual(response.results[0].nativeReviewStatistics?.totalReviewCount, 29) + XCTAssertEqual(response.results[0].nativeReviewStatistics?.overallRatingRange, 5) + expectation.fulfill() + }) { (error) in + XCTFail("inline ratings request error: \(error)") + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + func testInlineRatingsDisplayMultipleProducts() { + + let expectation = self.expectation(description: "") + + let request = + BVBulkRatingsRequest(productIds: ["test1", "test2", "test3"], statistics: .bulkRatingAll) + .filter(on: .bulkRatingContentLocale, relationalFilterOperatorValue: .equalTo, values: ["en_US"]) + + request.load({ (response) in + XCTAssertEqual(response.results.count, 3) + expectation.fulfill() + }) { (error) in + XCTFail("inline ratings request error: \(error)") + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in print("request took way too long") } + } + + func testInlineRatingsTooManyProductsError() { + + let expectation = self.expectation(description: "inline ratings display should complete") + + var tooManyProductIds: [String] = [] + + for i in 0 ... 110{ + tooManyProductIds += [String(i)] + } + + + let request = BVBulkRatingsRequest(productIds: tooManyProductIds, statistics: .bulkRatingAll) + + request.load({ (response) in + XCTFail("Should not succeed") + }) { (error) in + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + +} diff --git a/BVSDKTests/ConversationsTests/DisplayTests/InlineRatingsDisplayTests.swift b/BVSDKTests/ConversationsTests/DisplayTests/InlineRatingsDisplayTests.swift new file mode 100644 index 00000000..4883ed53 --- /dev/null +++ b/BVSDKTests/ConversationsTests/DisplayTests/InlineRatingsDisplayTests.swift @@ -0,0 +1,96 @@ +// +// InlineRatingsDisplayTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +import XCTest +@testable import BVSDK + +class InlineRatingsDisplayTests: XCTestCase { + + + override func setUp() { + super.setUp() + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "kuy3zj9pr3n7i0wxajrzj04xo"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + } + + + func testInlineRatingsDisplayMultipleProducts() { + + let expectation = self.expectation(description: "") + + let request = + BVBulkRatingsRequest( + productIds: ["test1", "test2", "test3"], + statistics: .bulkRatingAll) + .filter(on: .bulkRatingContentLocale, + relationalFilterOperatorValue: .equalTo, + values: ["en_US"]) + + request.load({ (response) in + XCTAssertEqual(response.results.count, 3) + expectation.fulfill() + }) { (error) in + XCTFail("inline ratings request error: \(error)") + } + + self.waitForExpectations(timeout: 10) { (error) in print("request took way too long") } + + } + + func testInlineRatingsDisplayOneProduct() { + + let expectation = self.expectation(description: "") + + let request = + BVBulkRatingsRequest(productIds: ["test3"], statistics: .bulkRatingAll) + .filter(on: .bulkRatingContentLocale, relationalFilterOperatorValue: .equalTo, values: ["en_US"]) + + request.load({ (response) in + XCTAssertEqual(response.results.count, 1) + XCTAssertEqual(response.results[0].productId, "test3") + XCTAssertEqual(response.results[0].reviewStatistics?.totalReviewCount, 29) + XCTAssertNotNil(response.results[0].reviewStatistics?.averageOverallRating) + XCTAssertEqual(response.results[0].reviewStatistics?.overallRatingRange, 5) + XCTAssertEqual(response.results[0].nativeReviewStatistics?.totalReviewCount, 29) + XCTAssertEqual(response.results[0].nativeReviewStatistics?.overallRatingRange, 5) + expectation.fulfill() + }) { (error) in + XCTFail("inline ratings request error: \(error)") + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + func testInlineRatingsTooManyProductsError() { + + let expectation = self.expectation(description: "inline ratings display should complete") + + var tooManyProductIds: [String] = [] + + for i in 0 ... 110{ + tooManyProductIds += [String(i)] + } + + let request = BVBulkRatingsRequest(productIds: tooManyProductIds, statistics: .bulkRatingAll) + + request.load({ (response) in + XCTFail("Should not succeed") + }) { (error) in + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + +} diff --git a/BVSDKTests/ConversationsTests/DisplayTests/ProductDisplayTests.swift b/BVSDKTests/ConversationsTests/DisplayTests/ProductDisplayTests.swift new file mode 100644 index 00000000..2d2d5c80 --- /dev/null +++ b/BVSDKTests/ConversationsTests/DisplayTests/ProductDisplayTests.swift @@ -0,0 +1,110 @@ +// +// ProductDisplayTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +import XCTest +@testable import BVSDK + +class ProductDisplayTests: XCTestCase { + + override func setUp() { + super.setUp() + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "kuy3zj9pr3n7i0wxajrzj04xo"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + } + + + func testProductDisplay() { + + let expectation = self.expectation(description: "") + + let request = BVProductDisplayPageRequest(productId: "test1") + .include(.reviews, limit: 10) + .include(.questions, limit: 5) + .includeStatistics(.reviews) + + request.load({ (response) in + + XCTAssertNotNil(response.result) + + let product = response.result! + let brand = product.brand! + XCTAssertEqual(brand.identifier, "cskg0snv1x3chrqlde0zklodb") + XCTAssertEqual(brand.name, "mysh") + XCTAssertEqual(product.productDescription, "Our pinpoint oxford is crafted from only the finest 80\'s two-ply cotton fibers.Single-needle stitching on all seams for a smooth flat appearance. Tailored with our Traditional\n straight collar and button cuffs. Machine wash. Imported.") + XCTAssertEqual(product.brandExternalId, "cskg0snv1x3chrqlde0zklodb") + XCTAssertEqual(product.imageUrl, "http://myshco.com/productImages/shirt.jpg") + XCTAssertEqual(product.name, "Dress Shirt") + XCTAssertEqual(product.categoryId, "testCategory1031") + XCTAssertEqual(product.identifier, "test1") + XCTAssertEqual(product.includedReviews.count, 10) + XCTAssertEqual(product.includedQuestions.count, 5) + expectation.fulfill() + + }) { (error) in + + XCTFail("product display request error: \(error)") + expectation.fulfill() + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + + func testProductDisplayWithFilter() { + + let expectation = self.expectation(description: "") + + let request = BVProductDisplayPageRequest(productId: "test1") + .include(.reviews, limit: 10) + .include(.questions, limit: 5) + // only include reviews where isRatingsOnly is false + .filter(on: .isRatingsOnly, relationalFilterOperatorValue: .equalTo, value: "false") + // only include questions where isFeatured is not equal to true + .filter(on: .questionIsFeatured, relationalFilterOperatorValue: .notEqualTo, value: "true") + .includeStatistics(.reviews) + + request.load({ (response) in + + XCTAssertNotNil(response.result) + + let product = response.result! + + XCTAssertEqual(product.includedReviews.count, 10) + XCTAssertEqual(product.includedQuestions.count, 5) + + // Iterate all the included reviews and verify that all the reviews have isRatingsOnly = false + for review in product.includedReviews { + XCTAssertFalse(review.isRatingsOnly) + } + + // Iterate all the included questions and verify that all the questions have isFeatured = false + for question in product.includedQuestions { + XCTAssertFalse(question.isFeatured) + } + + expectation.fulfill() + + }) { (error) in + + XCTFail("product display request error: \(error)") + expectation.fulfill() + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + +} diff --git a/BVSDKTests/ConversationsTests/DisplayTests/ProfileDisplayTests.swift b/BVSDKTests/ConversationsTests/DisplayTests/ProfileDisplayTests.swift new file mode 100644 index 00000000..c5341593 --- /dev/null +++ b/BVSDKTests/ConversationsTests/DisplayTests/ProfileDisplayTests.swift @@ -0,0 +1,181 @@ +// +// ProfileDisplayTests.swift +// BVSDK +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +import XCTest +@testable import BVSDK + +// Tests conforming to API description at: https://developer.bazaarvoice.com/docs/read/conversations_api/reference/latest/profiles/display +class ProfileDisplayTests: XCTestCase { + + override func setUp() { + super.setUp() + let configDict = ["clientId": "conciergeapidocumentation", + "apiKeyConversations": "caB45h2jBqXFw1OE043qoMBD1gJC8EwFNCjktzgwncXY4"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + BVSDKManager.shared().setLogLevel(.verbose) + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testBasicProfile() { + + let expectation = self.expectation(description: "testBasicProfile") + + let request = BVAuthorRequest(authorId: "data-gen-user-poaouvr127us1ijhpafkfacb9"); + + request.load({ (response) in + // success + + expectation.fulfill() + + let profile = response.results.first! + + XCTAssertEqual(response.limit, 1) + XCTAssertEqual(response.offset, 0) + XCTAssertEqual(response.locale, "en_US") + XCTAssertEqual(response.totalResults, 1) + + // Check profile data from the result + XCTAssertEqual(profile.userNickname, "ferdinand255") + XCTAssertEqual(profile.submissionId, nil) + XCTAssertEqual(profile.badges.count, 2) + XCTAssertEqual(profile.photos.count, 0) + XCTAssertEqual(profile.videos.count, 0) + XCTAssertEqual(profile.contextDataValues.count, 0) + XCTAssertEqual(profile.userLocation, nil) + + XCTAssertEqual(profile.submissionTime, BVModelUtil.convertTimestamp(toDatetime: "2016-01-06T11:52:00.000+00:00")) + + let year : Int = NSCalendar.current.component(Calendar.Component.year, from: profile.lastModeratedTime!) + XCTAssertTrue(year >= 2017) + + // Check the badges + for badge in profile.badges { + + if badge.identifier == "Staff" { + XCTAssertEqual(badge.badgeType, BVBadgeType.affiliation) + } else if badge.identifier == "Expert" { + XCTAssertEqual(badge.badgeType, BVBadgeType.rank) + } + } + + // Stats are nil w/out the Stats flag added + XCTAssertEqual(profile.qaStatistics, nil) + XCTAssertEqual(profile.reviewStatistics, nil) + + // Check the includes + XCTAssertEqual(profile.includedReviews.count, 0) + XCTAssertEqual(profile.includedQuestions.count, 0) + + }) { (error) in + + XCTFail("profile display request error: \(error)") + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + } + + + func testAuthorStatisticsAndIncludes() { + + let expectation = self.expectation(description: "testAuthorStatisticsAndIncludes") + + let request = BVAuthorRequest(authorId: "data-gen-user-poaouvr127us1ijhpafkfacb9") + // stats includes + .includeStatistics(.authorAnswers) + .includeStatistics(.authorQuestions) + .includeStatistics(.authorReviews) + //.includeStatistics(.reviewComments) // This is not supported by API, so will assert. + + // other includes + .include(.authorReviews, limit: UInt(10)) + .include(.authorQuestions, limit: UInt(10)) + .include(.authorAnswers, limit: UInt(10)) + .include(.authorReviewComments, limit: UInt(10)) + + // sorts + .sort(by: .answerSubmissionTime, monotonicSortOrderValue: .descending) + .sort(by: .reviewSubmissionTime, monotonicSortOrderValue: .descending) + .sort(by: .questionSubmissionTime, monotonicSortOrderValue: .descending) + + request.load({ (response) in + // success + + expectation.fulfill() + + let profile = response.results.first! + + // QA Statistics + XCTAssertEqual(profile.qaStatistics?.totalAnswerCount, 33) + XCTAssertEqual(profile.qaStatistics?.totalQuestionCount, 37) + XCTAssertEqual(profile.qaStatistics?.answerHelpfulVoteCount, 0) + XCTAssertEqual(profile.qaStatistics?.helpfulVoteCount, 0) + XCTAssertEqual(profile.qaStatistics?.answerHelpfulVoteCount, 0) + XCTAssertEqual(profile.qaStatistics?.questionHelpfulVoteCount, 0) + XCTAssertEqual(profile.qaStatistics?.answerNotHelpfulVoteCount, 0) + XCTAssertEqual(profile.qaStatistics?.questionNotHelpfulVoteCount, 0) + + // Review Statistics + XCTAssertEqual(profile.reviewStatistics?.totalReviewCount, 23) + XCTAssertEqual(profile.reviewStatistics?.helpfulVoteCount, 66) + XCTAssertEqual(profile.reviewStatistics?.notHelpfulVoteCount, 58) + XCTAssertEqual(profile.reviewStatistics?.notRecommendedCount, 1) + XCTAssertEqual(profile.reviewStatistics?.overallRatingRange, 5) + XCTAssertEqual(profile.reviewStatistics?.ratingDistribution?.fiveStarCount, 7) + XCTAssertEqual(profile.reviewStatistics?.ratingDistribution?.fourStarCount, 16) + XCTAssertEqual(profile.reviewStatistics?.recommendedCount, 16) + XCTAssertEqual(String(format: "%.2f", (profile.reviewStatistics?.averageOverallRating?.floatValue)!), "4.30") + + // Check the includes + XCTAssertEqual(profile.includedReviews.count, 10) + XCTAssertEqual(profile.includedQuestions.count, 10) + XCTAssertEqual(profile.includedAnswers.count, 10) + XCTAssertEqual(profile.includedComments.count, 10) + + }) { (error) in + + XCTFail("profile display request error: \(error)") + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + } + + + func testProfileDisplayFailure() { + let configDict = ["clientId": "conciergeapidocumentation", + "apiKeyConversations": "badkey"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + + let expectation = self.expectation(description: "testDisplayFailure") + + let request = BVAuthorRequest(authorId: "badauthorid") + + request.load({ (response) in + // success + XCTFail("success block should not be called") + expectation.fulfill() + + }) { (error) in + XCTAssertNotNil(error) + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + } + +} diff --git a/BVSDKTests/ConversationsTests/DisplayTests/QuestionDisplayTests.swift b/BVSDKTests/ConversationsTests/DisplayTests/QuestionDisplayTests.swift new file mode 100644 index 00000000..c507b57a --- /dev/null +++ b/BVSDKTests/ConversationsTests/DisplayTests/QuestionDisplayTests.swift @@ -0,0 +1,71 @@ +// +// QuestionDisplayTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +import XCTest +@testable import BVSDK + +class QuestionDisplayTests: XCTestCase { + + + override func setUp() { + super.setUp() + + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "kuy3zj9pr3n7i0wxajrzj04xo"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + } + + + func testQuestionDisplay() { + + let expectation = self.expectation(description: "") + + let request = BVQuestionsAndAnswersRequest(productId: "test1", limit: 10, offset: 0) + .filter(on: .questionHasAnswers, relationalFilterOperatorValue: .equalTo, value: "true") + + request.load({ (response) in + + XCTAssertEqual(response.results.count, 10) + + let question = response.results.first! + XCTAssertEqual(question.questionSummary, "Das ist mein test :)") + XCTAssertEqual(question.questionDetails, "Das ist mein test :)") + XCTAssertEqual(question.userNickname, "123thisisme") + XCTAssertEqual(question.authorId, "eplz083100g") + XCTAssertEqual(question.moderationStatus, "APPROVED") + XCTAssertEqual(question.identifier, "14828") + + XCTAssertEqual(question.answers.count, 1) + + let answer = question.answers.first! + XCTAssertEqual(answer.userNickname, "asdfasdfasdfasdf") + XCTAssertEqual(answer.questionId, "14828") + XCTAssertEqual(answer.authorId, "c6ryqeb2bq0") + XCTAssertEqual(answer.moderationStatus, "APPROVED") + XCTAssertEqual(answer.identifier, "16292") + XCTAssertEqual(answer.brandImageLogoURL, nil) + XCTAssertEqual(answer.answerText, "zxnc,vznxc osaidmf oaismdfo ims adoifmaosidmfoiamsdfimasdf") + + response.results.forEach { (question) in + XCTAssertEqual(question.productId, "test1") + } + + expectation.fulfill() + + }) { (error) in + + XCTFail("product display request error: \(error)") + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + +} diff --git a/BVSDKTests/ConversationsTests/DisplayTests/ReviewDisplayTests.swift b/BVSDKTests/ConversationsTests/DisplayTests/ReviewDisplayTests.swift new file mode 100644 index 00000000..8f575c1b --- /dev/null +++ b/BVSDKTests/ConversationsTests/DisplayTests/ReviewDisplayTests.swift @@ -0,0 +1,258 @@ +// +// ReviewDisplayTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +import XCTest +import OHHTTPStubs +@testable import BVSDK + +class ReviewDisplayTests: XCTestCase { + + override func setUp() { + super.setUp() + + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "kuy3zj9pr3n7i0wxajrzj04xo"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + BVSDKManager.shared().setLogLevel(BVLogLevel.verbose) + } + + override func tearDown() { + super.tearDown() + OHHTTPStubs.removeAllStubs() + + } + + func testReviewDisplay() { + + let expectation = self.expectation(description: "testReviewDisplay") + + let request = BVReviewsRequest(productId: "test1", limit: 10, offset: 4) + .sort(by: .reviewRating, monotonicSortOrderValue: .ascending) + .filter(on: .hasPhotos, relationalFilterOperatorValue: .equalTo, value: "true") + .filter(on: .hasComments, relationalFilterOperatorValue: .equalTo, value: "false") + + request.load({ (response) in + + XCTAssertEqual(response.results.count, 10) + let review = response.results.first! + XCTAssertEqual(review.rating, 1) + XCTAssertEqual(review.title, "Morbi nibh risus, mattis id placerat a massa nunc.") + XCTAssertEqual(review.reviewText, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed rhoncus scelerisque semper. Morbi in sapien sit amet justo eleifend pellentesque! Cras sollicitudin, quam in ullamcorper faucibus, augue metus blandit justo, vitae ullamcorper tellus quam non purus. Fusce gravida rhoncus placerat. Integer tempus nunc sed elit mollis ut venenatis felis volutpat. Sed a velit et lacus lobortis aliquet? Donec dolor quam, pharetra vitae commodo et, mattis quis nibh? Quisque ultrices neque et lacus volutpat.") + XCTAssertEqual(review.moderationStatus, "APPROVED") + XCTAssertEqual(review.identifier, "191975") + XCTAssertNil(review.product) + XCTAssertEqual(review.isRatingsOnly, false) + XCTAssertEqual(review.isSyndicated, false) + XCTAssertEqual(review.isFeatured, false) + XCTAssertEqual(review.productId, "test1") + XCTAssertEqual(review.authorId, "endersgame") + XCTAssertEqual(review.userNickname, "endersgame") + XCTAssertEqual(review.userLocation, "San Fransisco, California") + XCTAssertNil(review.syndicationSource) + + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).label, "Pros") + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).identifier, "Pro") + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).values!!, ["Organic Fabric", "Quality"]) + + XCTAssertEqual(review.photos.count, 1) + XCTAssertEqual(review.photos.first?.caption, "Etiam malesuada ultricies urna in scelerisque. Sed viverra blandit nibh non egestas. Sed rhoncus, ipsum in vehicula imperdiet, purus lectus sodales erat, eget ornare lacus lectus ac leo. Suspendisse tristique sollicitudin ultricies. Aliquam erat volutpat.") + XCTAssertEqual(review.photos.first?.identifier, "72586") + XCTAssertNotNil(review.photos.first?.sizes?.thumbnailUrl) + XCTAssertTrue((review.photos.first?.sizes?.normalUrl?.lowercased().contains("jpg?client=apireadonlysandbox"))!) + + XCTAssertEqual(review.contextDataValues.count, 1) + let cdv = review.contextDataValues.first! + XCTAssertEqual(cdv.value, "Female") + XCTAssertEqual(cdv.valueLabel, "Female") + XCTAssertEqual(cdv.dimensionLabel, "Gender") + XCTAssertEqual(cdv.identifier, "Gender") + + XCTAssertEqual(review.badges.first?.badgeType, BVBadgeType.merit) + XCTAssertEqual(review.badges.first?.identifier, "top10Contributor") + XCTAssertEqual(review.badges.first?.contentType, "REVIEW") + + response.results.forEach { (review) in + XCTAssertEqual(review.productId, "test1") + } + + expectation.fulfill() + + }) { (error) in + + XCTFail("review display request error: \(error)") + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + + func testSyndicationSource(){ + + + stub(condition: isHost("stg.api.bazaarvoice.com")) { _ in + let stubPath = OHPathForFile("testSyndicationSource.json", type(of: self)) + return fixture(filePath: stubPath!, headers: ["Content-Type" as NSObject:"application/json" as AnyObject]) + } + + let expectation = self.expectation(description: "testSyndicationSource") + + + let request = BVReviewsRequest(productId: "test1", limit: 10, offset: 4) + .sort(by: .reviewRating, monotonicSortOrderValue: .descending) + .include(.reviewProducts) + + request.load({ (response) in + + let review = response.results.first + + XCTAssertNotNil(review?.syndicationSource) + XCTAssertEqual(review?.syndicationSource?.name, "bazaarvoice") + XCTAssertNil(review?.syndicationSource?.contentLink) + XCTAssertNotNil(review?.syndicationSource?.logoImageUrl) + + expectation.fulfill() + + }) { (error) in + + XCTFail("review display request error: \(error)") + expectation.fulfill() + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + func testReviewDisplayProductFilteredStats() { + + let expectation = self.expectation(description: "testReviewDisplayProductFilteredStats") + + let request = BVReviewsRequest(productId: "test1", limit: 10, offset: 4) + .sort(by: .reviewRating, monotonicSortOrderValue: .ascending) + .filter(on: .hasPhotos, relationalFilterOperatorValue: .equalTo, value: "true") + .filter(on: .hasComments, relationalFilterOperatorValue: .equalTo, value: "false") + .include(.reviewProducts) + .addCustomDisplayParameter("filteredstats", withValue: "reviews,questions") + + request.load({ (response) in + + XCTAssertEqual(response.results.count, 10) + let review = response.results.first! + XCTAssertEqual(review.rating, 1) + + XCTAssertNotNil(review.product) + XCTAssertNotNil(review.product?.reviewStatistics) + XCTAssertNotNil(review.product?.qaStatistics) + + XCTAssertNotNil(review.product?.reviewStatistics?.contextDataDistribution) + XCTAssertNotNil(review.product?.reviewStatistics?.tagDistribution) + XCTAssertNotNil(review.product?.reviewStatistics?.ratingDistribution) + + let qualityAvg = review.product?.reviewStatistics?.secondaryRatingsAverages?["Quality"] as! NSNumber; + let valueAvg = review.product?.reviewStatistics?.secondaryRatingsAverages?["Value"] as! NSNumber; + + XCTAssertTrue(qualityAvg.intValue > 0) + XCTAssertTrue(valueAvg.intValue > 0) + + XCTAssertEqual(review.identifier, "191975") + + XCTAssertEqual(review.isRatingsOnly, false) + XCTAssertEqual(review.isFeatured, false) + XCTAssertEqual(review.productId, "test1") + XCTAssertEqual(review.authorId, "endersgame") + XCTAssertEqual(review.userNickname, "endersgame") + XCTAssertEqual(review.userLocation, "San Fransisco, California") + + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).label, "Pros") + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).identifier, "Pro") + XCTAssertEqual((review.tagDimensions!["Pro"]! as AnyObject).values!!, ["Organic Fabric", "Quality"]) + + XCTAssertEqual(review.photos.count, 1) + XCTAssertEqual(review.photos.first?.caption, "Etiam malesuada ultricies urna in scelerisque. Sed viverra blandit nibh non egestas. Sed rhoncus, ipsum in vehicula imperdiet, purus lectus sodales erat, eget ornare lacus lectus ac leo. Suspendisse tristique sollicitudin ultricies. Aliquam erat volutpat.") + XCTAssertEqual(review.photos.first?.identifier, "72586") + XCTAssertNotNil(review.photos.first?.sizes?.thumbnailUrl) + XCTAssertTrue((review.photos.first?.sizes?.normalUrl?.lowercased().contains("jpg?client=apireadonlysandbox"))!) + + XCTAssertEqual(review.contextDataValues.count, 1) + let cdv = review.contextDataValues.first! + XCTAssertEqual(cdv.value, "Female") + XCTAssertEqual(cdv.valueLabel, "Female") + XCTAssertEqual(cdv.dimensionLabel, "Gender") + XCTAssertEqual(cdv.identifier, "Gender") + + XCTAssertEqual(review.badges.first?.badgeType, BVBadgeType.merit) + XCTAssertEqual(review.badges.first?.identifier, "top10Contributor") + XCTAssertEqual(review.badges.first?.contentType, "REVIEW") + + response.results.forEach { (review) in + XCTAssertEqual(review.productId, "test1") + } + + expectation.fulfill() + + }) { (error) in + + XCTFail("product display request error: \(error)") + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + func testReviewIncludeComments() { + + let expectation = self.expectation(description: "testReviewIncludeComments") + + let request = BVReviewsRequest(productId: "test1", limit: 10, offset: 0) + .include(.reviewComments) + .filter(on: .id, relationalFilterOperatorValue: .equalTo, value: "192463") // This review is know to have a comment + + request.load({ (response) in + + XCTAssertEqual(response.results.count, 1) // We filtered on a review id, so there should only be one + + let review : BVReview = response.results.first! + + XCTAssertTrue(review.comments.count >= 1) + + let firstComment = review.comments.first! + + // XCTAssertNotNil(firstComment.title) -- title may be nil + XCTAssertNotNil(firstComment.authorId) + XCTAssertNotNil(firstComment.badges) + XCTAssertNotNil(firstComment.submissionTime) + XCTAssertNotNil(firstComment.commentText) + XCTAssertNotNil(firstComment.contentLocale) + XCTAssertNotNil(firstComment.lastModeratedTime) + XCTAssertNotNil(firstComment.lastModificationTime) + + expectation.fulfill() + + }) { (error) in + + XCTFail("review display request error: \(error)") + + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + + +} diff --git a/BVSDKTests/ConversationsTests/DisplayTests/StoresTests/ConversationsStoresDisplayTests.swift b/BVSDKTests/ConversationsTests/DisplayTests/StoresTests/ConversationsStoresDisplayTests.swift new file mode 100644 index 00000000..a078bca9 --- /dev/null +++ b/BVSDKTests/ConversationsTests/DisplayTests/StoresTests/ConversationsStoresDisplayTests.swift @@ -0,0 +1,211 @@ +// +// ConversationsStoresDisplayTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +import XCTest +import OHHTTPStubs +@testable import BVSDK + +class ConversationsStoresDisplayTests: XCTestCase { + + override func setUp() { + super.setUp() + let configDict = ["clientId": "acmestores", + "apiKeyConversationsStores": "mocktestingnokeyrequired"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .prod) + BVSDKManager.shared().setLogLevel(.error) + } + + override func tearDown() { + super.tearDown() + OHHTTPStubs.removeAllStubs() + } + + // This tests fetching reviews from a single store, but store id, given a limit and offset. + func testStoreFeedDisplayWithReviewsAndStats() { + + stub(condition: isHost("api.bazaarvoice.com")) { _ in + // Stub it with our "storeItemWithStatsAndReviews.json" stub file (which is in same bundle as self) + let stubPath = OHPathForFile("storeItemWithStatsAndReviews.json", type(of: self)) + return fixture(filePath: stubPath!, headers: ["Content-Type" as NSObject:"application/json" as AnyObject]) + } + + let expectation = self.expectation(description: "testStoreFeedDisplayWithReviews") + + let request = BVStoreReviewsRequest(storeId: "1", limit: 20, offset: 0) + request.includeStatistics(.reviews) // Include statistics on the store object + .include(.reviewComments) + //request.addInclude(BVReviewIncludeType.products) + .sort(by: .reviewRating, monotonicSortOrderValue: .descending) // sort the reviews by rating, from top rated to lowest rated + + request.load({ (response) in + // Success + // Check the store attributes + let store : BVStore = response.store! + + XCTAssertEqual(store.identifier, "1") + XCTAssertEqual(store.name, "Endurance Cycles Austin") + XCTAssertEqual(store.productDescription, "10901 Stonelake Blvd, Austin, TX 78759") + XCTAssertEqual(store.productPageUrl, "http://www.endurancecycles.com/") + XCTAssertEqual(store.imageUrl, "http://media.bizj.us/view/img/8513272/bazaarvoice-1-exterior*750xx900-506-0-1.jpg") + + // Check that the store has a location + XCTAssertNotNil(store.storeLocation, "storeLocation should not be nil") + + XCTAssertEqual(response.results.count, 12) + + for currReview in response.results { + // make sure we have some review text in each BVReview object + + XCTAssertTrue(currReview.productId != nil, "store productId id should never be nil in a review") + XCTAssertTrue(currReview.authorId != nil, "authorId should never be nil in a review") + } + + XCTAssertNotNil(store.reviewStatistics, "store reviewStatistics should not be nil") + XCTAssertEqual(store.reviewStatistics?.averageOverallRating, 4.75) + XCTAssertEqual(store.reviewStatistics?.totalReviewCount, 12) + + expectation.fulfill() + + }) { (error) in + // Fail + XCTFail("Unexpected error in text") + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + // Testing reading of multiple stores, paged by limit and offsest + func testBulkStoresFetching() { + + let expectation = self.expectation(description: "testBulkStoresFetching") + + stub(condition: isHost("api.bazaarvoice.com")) { _ in + // Stub it with our "storeBulkFeedWithStatistics.json" stub file (which is in same bundle as self) + let stubPath = OHPathForFile("storeBulkFeedWithStatistics.json", type(of: self)) + return fixture(filePath: stubPath!, headers: ["Content-Type" as NSObject:"application/json" as AnyObject]) + } + + let request = BVBulkStoreItemsRequest(20, offset: 0) + request.includeStatistics(.reviews) + request.load({ (response) in + // Success + XCTAssertEqual(Int(response.results.count), Int(truncating: response.totalResults!)) + + for store : BVStore in response.results { + + XCTAssertNotNil(store, "Store in feed should not be nil") + XCTAssertNotNil(store.productDescription, "Store description nil") + XCTAssertNotNil(store.storeLocation, "Store in feed should not be nil") + + XCTAssertNotNil(store.storeLocation?.longitude, "longitude should not be nil") + XCTAssertNotNil(store.storeLocation?.latitude, "latitude should not be nil") + XCTAssertNotNil(store.storeLocation?.city, "city should not be nil") + XCTAssertNotNil(store.storeLocation?.state, "state should not be nil") + XCTAssertNotNil(store.storeLocation?.postalcode, "postalcode should not be nil") + XCTAssertNotNil(store.storeLocation?.phone, "phone should not be nil") + + XCTAssertNotNil(store.reviewStatistics, "reviewStatistics for a store should not be nil") + + } + + expectation.fulfill() + }) { (error) in + // Fail + XCTFail("Unexpected error in text") + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + + // Testing reading of multiple stores, paged + func testFetchStoresByIds() { + + let expectation = self.expectation(description: "testFetchStoresByIds") + + stub(condition: isHost("api.bazaarvoice.com")) { _ in + // Stub it with our "storeFeedOneStore.json" stub file (which is in same bundle as self) + let stubPath = OHPathForFile("storeFetchByIds.json", type(of: self)) + return fixture(filePath: stubPath!, headers: ["Content-Type" as NSObject:"application/json" as AnyObject]) + } + + // Fetch two stores, by ID + let request = BVBulkStoreItemsRequest(storeIds:["1", "3"]) + request.includeStatistics(.reviews) + request.load({ (response) in + // Success + XCTAssertEqual(Int(response.results.count), Int(truncating: response.totalResults!)) + + for store : BVStore in response.results { + + XCTAssertNotNil(store, "Store in feed should not be nil") + XCTAssertNotNil(store.productDescription, "Store description nil") + XCTAssertNotNil(store.storeLocation, "Store in feed should not be nil") + + XCTAssertNotNil(store.storeLocation?.longitude, "longitude should not be nil") + XCTAssertNotNil(store.storeLocation?.latitude, "latitude should not be nil") + XCTAssertNotNil(store.storeLocation?.city, "city should not be nil") + XCTAssertNotNil(store.storeLocation?.state, "state should not be nil") + XCTAssertNotNil(store.storeLocation?.postalcode, "postalcode should not be nil") + XCTAssertNotNil(store.storeLocation?.phone, "phone should not be nil") + + XCTAssertNotNil(store.reviewStatistics, "reviewStatistics for a store should not be nil") + } + + expectation.fulfill() + }) { (error) in + // Fail + XCTFail("Unexpected error in text") + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + + // Test an empty response, e.g. when we provide a store id that does not exist + func testNoStoreFoundResult() { + + let expectation = self.expectation(description: "testNoStoreFoundResult") + + stub(condition: isHost("api.bazaarvoice.com")) { _ in + // Stub it with our "storeFeedOneStore.json" stub file (which is in same bundle as self) + let stubPath = OHPathForFile("storeNoStoresFoundResult.json", type(of: self)) + return fixture(filePath: stubPath!, headers: ["Content-Type" as NSObject:"application/json" as AnyObject]) + } + + // Fetch two stores, by ID + let request = BVBulkStoreItemsRequest(storeIds:["badid"]) + request.includeStatistics(.reviews) + request.load({ (response) in + // Success + XCTAssertEqual(Int(response.results.count), Int(truncating: response.totalResults!)) + + expectation.fulfill() + }) { (error) in + // Fail + XCTFail("Unexpected error in text") + expectation.fulfill() + } + + self.waitForExpectations(timeout: 10) { (error) in + XCTAssertNil(error, "Something went horribly wrong, request took too long.") + } + + } + +} diff --git a/Tests/Tests/ConversationsTests/MiscTests/ConversationsTests.swift b/BVSDKTests/ConversationsTests/MiscTests/ConversationsTests.swift similarity index 100% rename from Tests/Tests/ConversationsTests/MiscTests/ConversationsTests.swift rename to BVSDKTests/ConversationsTests/MiscTests/ConversationsTests.swift diff --git a/BVSDKTests/ConversationsTests/MiscTests/DisplayParametersTests.swift b/BVSDKTests/ConversationsTests/MiscTests/DisplayParametersTests.swift new file mode 100644 index 00000000..796b989a --- /dev/null +++ b/BVSDKTests/ConversationsTests/MiscTests/DisplayParametersTests.swift @@ -0,0 +1,40 @@ +// +// DisplayParametersTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + + +import XCTest +@testable import BVSDK + + +class DisplayParametersTests: XCTestCase { + + func testParamsAreSorted() { + + let request = BVProductDisplayPageRequest(productId: "test1") + + let pdpAnswers:BVInclude = BVInclude(includeType: BVProductIncludeType(pdpIncludeTypeValue: .answers)) + + let pdpQuestions:BVInclude = BVInclude(includeType: BVProductIncludeType(pdpIncludeTypeValue: .questions)) + + let pdpReviews:BVInclude = BVInclude(includeType: BVProductIncludeType(pdpIncludeTypeValue: .reviews)) + + XCTAssertEqual(request.statistics(toParams: [pdpReviews]), "Reviews") + XCTAssertEqual(request.statistics(toParams: [pdpQuestions]), "Questions") + XCTAssertEqual(request.statistics(toParams: [pdpAnswers]), "Answers") + XCTAssertEqual(request.statistics( + toParams: [pdpReviews, pdpAnswers]), "Answers,Reviews") + XCTAssertEqual(request.statistics( + toParams: [pdpQuestions, pdpAnswers]), "Answers,Questions") + XCTAssertEqual(request.statistics( + toParams: [pdpReviews, pdpQuestions, pdpAnswers]), + "Answers,Questions,Reviews") + XCTAssertEqual(request.statistics( + toParams: [pdpAnswers, pdpReviews, pdpQuestions]), + "Answers,Questions,Reviews") + } + +} diff --git a/BVSDKTests/ConversationsTests/MiscTests/FilterTests.swift b/BVSDKTests/ConversationsTests/MiscTests/FilterTests.swift new file mode 100644 index 00000000..680b4f25 --- /dev/null +++ b/BVSDKTests/ConversationsTests/MiscTests/FilterTests.swift @@ -0,0 +1,155 @@ +// +// FilterTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + + +import XCTest +@testable import BVSDK + +class FilterTests: XCTestCase { + + func testOperatorStringify() { + + let productIdFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productId) + + let equalToFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .equalTo) + + let notEqualToFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .notEqualTo) + + let greaterThanFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .greaterThan) + + let greaterThanOrEqualToFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .greaterThanOrEqualTo) + + let lessThanFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .lessThan) + + let lessThanOrEqualToFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .lessThanOrEqualTo) + + XCTAssertEqual("Id:gt:test1", BVFilter(filterType: productIdFilterType, filterOperator: greaterThanFilterOperator, value: "test1").toParameterString()) + XCTAssertEqual("Id:gte:test1", BVFilter(filterType: productIdFilterType, filterOperator: greaterThanOrEqualToFilterOperator, value: "test1").toParameterString()) + XCTAssertEqual("Id:lt:test1", BVFilter(filterType: productIdFilterType, filterOperator: lessThanFilterOperator, value: "test1").toParameterString()) + XCTAssertEqual("Id:lte:test1", BVFilter(filterType: productIdFilterType, filterOperator: lessThanOrEqualToFilterOperator, value: "test1").toParameterString()) + XCTAssertEqual("Id:eq:test1", BVFilter(filterType: productIdFilterType, filterOperator: equalToFilterOperator, value: "test1").toParameterString()) + XCTAssertEqual("Id:neq:test1", BVFilter(filterType: productIdFilterType, filterOperator: notEqualToFilterOperator, value: "test1").toParameterString()) + + } + + func testTypeStringify() { + + let productIdFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productId) + + let productAverageOverallRatingFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productAverageOverallRating) + + let productCategoryAncestorIdFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productCategoryAncestorId) + + let productCategoryIdFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productCategoryId) + + let productIsActiveFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productIsActive) + + let productIsDisabledFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productIsDisabled) + + let productLastAnswerTimeFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productLastAnswerTime) + + let productLastQuestionTimeFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productLastQuestionTime) + + let productLastReviewTimeFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productLastReviewTime) + + let productLastStoryTimeFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productLastStoryTime) + + let productNameFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productName) + + let productRatingsOnlyReviewCountFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productRatingsOnlyReviewCount) + + let productTotalAnswerCountFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productTotalAnswerCount) + + let productTotalQuestionCountFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productTotalQuestionCount) + + let productTotalReviewCountFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productTotalReviewCount) + + let productTotalStoryCountFilterType:BVProductFilterType = BVProductFilterType(productFilterValue: .productTotalStoryCount) + + let equalToFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .equalTo) + + XCTAssertEqual("Id:eq:val", BVFilter(filterType: productIdFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("AverageOverallRating:eq:val", BVFilter(filterType: productAverageOverallRatingFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("CategoryAncestorId:eq:val", BVFilter(filterType: productCategoryAncestorIdFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("CategoryId:eq:val", BVFilter(filterType: productCategoryIdFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("IsActive:eq:val", BVFilter(filterType: productIsActiveFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("IsDisabled:eq:val", BVFilter(filterType: productIsDisabledFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("LastAnswerTime:eq:val", BVFilter(filterType: productLastAnswerTimeFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("LastQuestionTime:eq:val", BVFilter(filterType: productLastQuestionTimeFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("LastReviewTime:eq:val", BVFilter(filterType: productLastReviewTimeFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("LastStoryTime:eq:val", BVFilter(filterType: productLastStoryTimeFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("Name:eq:val", BVFilter(filterType: productNameFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("RatingsOnlyReviewCount:eq:val", BVFilter(filterType: productRatingsOnlyReviewCountFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("TotalAnswerCount:eq:val", BVFilter(filterType: productTotalAnswerCountFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("TotalQuestionCount:eq:val", BVFilter(filterType: productTotalQuestionCountFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("TotalReviewCount:eq:val", BVFilter(filterType: productTotalReviewCountFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + XCTAssertEqual("TotalStoryCount:eq:val", BVFilter(filterType: productTotalStoryCountFilterType, filterOperator: equalToFilterOperator, value: "val").toParameterString()) + + } + + func testFilterSorting() { + + let productIdFilterOperator:BVProductFilterType = BVProductFilterType(productFilterValue: .productId) + + let equalToFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .equalTo) + + XCTAssertEqual("Id:eq:aaa,bbb,ccc", BVFilter(filterType: productIdFilterOperator, filterOperator: equalToFilterOperator, values: ["aaa", "bbb", "ccc"]).toParameterString()) + XCTAssertEqual("Id:eq:aaa,bbb,ccc", BVFilter(filterType: productIdFilterOperator, filterOperator: equalToFilterOperator, values: ["bbb", "aaa", "ccc"]).toParameterString()) + XCTAssertEqual("Id:eq:aaa,bbb,ccc", BVFilter(filterType: productIdFilterOperator, filterOperator: equalToFilterOperator, values: ["ccc", "bbb", "aaa"]).toParameterString()) + + } + + func testValueEscaping() { + + let productIdFilterOperator:BVProductFilterType = BVProductFilterType(productFilterValue: .productId) + + let equalToFilterOperator:BVRelationalFilterOperator = + BVRelationalFilterOperator(relationalFilterValue: .equalTo) + + XCTAssertEqual("Id:eq:a\\,a\\:a%26a", BVFilter(filterType: productIdFilterOperator, filterOperator: equalToFilterOperator, value: "a,a:a&a").toParameterString()) + XCTAssertEqual("Id:eq:a\\,aa,b\\:bb,c%26cc", BVFilter(filterType: productIdFilterOperator, filterOperator: equalToFilterOperator, values: ["a,aa", "b:bb", "c&cc"]).toParameterString()) + XCTAssertEqual("Id:eq:a\\,aa,b\\:bb,c%26cc", BVFilter(string: "Id", filterOperator: equalToFilterOperator, values: ["a,aa", "b:bb", "c&cc"]).toParameterString()) + } + +} + +class SortTests: XCTestCase { + + func testSimpleSort() { + let request = BVProductDisplayPageRequest(productId: "test1") + .include(.reviews, limit: 10) + .sort(by: .reviewRating, monotonicSortOrderValue: .descending) + + let params = request.createParams() + XCTAssertNotNil(getParamValue(params, keyToSearchFor: "Sort_Reviews")) + XCTAssertEqual (getParamValue(params, keyToSearchFor: "Sort_Reviews"), "Rating:desc") + } + + func testComplicatedSort() { + let request = BVProductDisplayPageRequest(productId: "test1") + .include(.reviews, limit: 10) + .sort(by: .reviewRating, monotonicSortOrderValue: .descending) + .sort(by: .reviewSubmissionTime, monotonicSortOrderValue: .descending) + .sort(by: .questionTotalAnswerCount, monotonicSortOrderValue: .descending) + .sort(by: .questionTotalFeedbackCount, monotonicSortOrderValue: .ascending) + + let params = request.createParams() + XCTAssertNotNil(getParamValue(params, keyToSearchFor: "Sort_Reviews")) + XCTAssertEqual (getParamValue(params, keyToSearchFor: "Sort_Reviews"), "Rating:desc,SubmissionTime:desc") + XCTAssertNotNil(getParamValue(params, keyToSearchFor: "Sort_Questions")) + XCTAssertEqual (getParamValue(params, keyToSearchFor: "Sort_Questions"), "TotalAnswerCount:desc,TotalFeedbackCount:asc") + } + +} diff --git a/Tests/Tests/ConversationsTests/MiscTests/ProductIdEscapeTests.swift b/BVSDKTests/ConversationsTests/MiscTests/ProductIdEscapeTests.swift similarity index 100% rename from Tests/Tests/ConversationsTests/MiscTests/ProductIdEscapeTests.swift rename to BVSDKTests/ConversationsTests/MiscTests/ProductIdEscapeTests.swift diff --git a/Tests/Tests/ConversationsTests/ParsingTests/PhotoSizesParsingTests.swift b/BVSDKTests/ConversationsTests/ParsingTests/PhotoSizesParsingTests.swift similarity index 100% rename from Tests/Tests/ConversationsTests/ParsingTests/PhotoSizesParsingTests.swift rename to BVSDKTests/ConversationsTests/ParsingTests/PhotoSizesParsingTests.swift diff --git a/BVSDKTests/ConversationsTests/SubmissionTests/AnswerSubmissionTests.swift b/BVSDKTests/ConversationsTests/SubmissionTests/AnswerSubmissionTests.swift new file mode 100644 index 00000000..d4f0e900 --- /dev/null +++ b/BVSDKTests/ConversationsTests/SubmissionTests/AnswerSubmissionTests.swift @@ -0,0 +1,115 @@ +// +// AnswerSubmissionTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + + +import XCTest +@testable import BVSDK + +class AnswerSubmissionTests: XCTestCase { + + override func setUp() { + super.setUp() + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "2cpdrhohmgmwfz8vqyo48f52g"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + BVSDKManager.shared().setLogLevel(.error) + + } + + func testSubmitAnswerWithPhoto() { + let expectation = self.expectation(description: "testSubmitAnswerWithPhoto") + + let answer = self.fillOutAnswer(.submit) + answer.submit({ (answerSubmission) in + expectation.fulfill() + XCTAssertTrue(answerSubmission.formFields?.keys.count == 0) + }, failure: { (errors) in + expectation.fulfill() + XCTFail() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + func testPreviewAnswerWithPhoto() { + let expectation = self.expectation(description: "testPreviewAnswerWithPhoto") + let answer = self.fillOutAnswer(.preview) + answer.submit({ (answerSubmission) in + expectation.fulfill() + // When run in Preview mode, we get the formFields that can be used for submission. + XCTAssertTrue(answerSubmission.formFields?.keys.count == 22) + }, failure: { (errors) in + expectation.fulfill() + XCTFail() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + func fillOutAnswer(_ action : BVSubmissionAction) -> BVAnswerSubmission { + let answerText = "Answer text Answer text Answer text Answer text Answer text Answer text Answer text Answer text" + let answer = BVAnswerSubmission(questionId: "6104", answerText: answerText) + + let randomId = String(arc4random()) + + answer.campaignId = "BV_REVIEW_DISPLAY" + answer.locale = "en_US" + answer.sendEmailAlertWhenPublished = true + answer.userNickname = "UserNickname" + randomId + answer.userId = "UserId" + randomId + answer.userEmail = "developer@bazaarvoice.com" + answer.agreedToTermsAndConditions = true + answer.action = action + + if let image = PhotoUploadTests.createPNG() { + answer.addPhoto(image, withPhotoCaption: "Very photogenic") + } + + return answer + } + + func testSubmitAnswerFailure() { + let expectation = self.expectation(description: "") + + let answer = BVAnswerSubmission(questionId: "6104", answerText: "") + answer.userId = "craiggil" + answer.action = .preview + + answer.submit({ (questionSubmission) in + XCTFail() + }, failure: { (errors) in + + XCTAssertEqual(errors.count, 1) + for error in errors as [NSError] { + + if error.userInfo[BVFieldErrorName] == nil { + // Would happen if we get internal server error and/or XML is returned + XCTFail("Malformed error response") + break + } + + let fieldName = error.userInfo[BVFieldErrorName] as! String + let errorCode = error.userInfo[BVFieldErrorCode] as! String + let errorMessage = error.userInfo[BVFieldErrorMessage] as! String + + if fieldName == "answertext" { + XCTAssertEqual(errorCode, "ERROR_FORM_REQUIRED") + XCTAssertEqual(errorMessage, "You must enter an answer.") + } + else { + XCTAssertEqual(fieldName, "usernickname") + XCTAssertEqual(errorCode, "ERROR_FORM_REQUIRED") + XCTAssertEqual(errorMessage, "You must enter a nickname.") + } + } + + expectation.fulfill() + }) + waitForExpectations(timeout: 10, handler: nil) + } + +} diff --git a/BVSDKTests/ConversationsTests/SubmissionTests/CommentSubmissionTests.swift b/BVSDKTests/ConversationsTests/SubmissionTests/CommentSubmissionTests.swift new file mode 100644 index 00000000..c2a1d3c7 --- /dev/null +++ b/BVSDKTests/ConversationsTests/SubmissionTests/CommentSubmissionTests.swift @@ -0,0 +1,92 @@ +// +// 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 + + 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/BVSDKTests/ConversationsTests/SubmissionTests/FeedbackSubmissionTests.swift b/BVSDKTests/ConversationsTests/SubmissionTests/FeedbackSubmissionTests.swift new file mode 100644 index 00000000..caf19aa4 --- /dev/null +++ b/BVSDKTests/ConversationsTests/SubmissionTests/FeedbackSubmissionTests.swift @@ -0,0 +1,108 @@ +// +// FeedbackSubmissionTests.swift +// BVSDK +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +import XCTest +@testable import BVSDK + +class FeedbackSubmissionTests: XCTestCase { + + override func setUp() { + super.setUp() + + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "2cpdrhohmgmwfz8vqyo48f52g"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + BVSDKManager.shared().setLogLevel(.error) + + } + + func testSubmitFeedbackHelpfulness() { + + let expectation = self.expectation(description: "testSubmitFeedbackHelpfulness") + + let feedback = BVFeedbackSubmission(contentId: "83725", with: .review, with: .helpfulness) + + let randomId = String(arc4random()) + + feedback.userId = "userId" + randomId + feedback.vote = .positive + + feedback.submit({ (response) in + // success + // verify response object.... + XCTAssertNotNil(response.feedback) + XCTAssertTrue(response.feedback?.inappropriateResponse == nil) + XCTAssertEqual(response.feedback?.helpfulnessResponse.authorId, feedback.userId) + XCTAssertEqual(response.feedback?.helpfulnessResponse.vote, "POSITIVE") + + expectation.fulfill() + + }) { (errors) in + // error + XCTFail("Should not be in failure block") + expectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + func testSubmitFeedbackFlag() { + + let expectation = self.expectation(description: "testSubmitFeedbackFlag") + + let feedback = BVFeedbackSubmission(contentId: "83725", with: .review, with: .inappropriate) + + let randomId = String(arc4random()) + + feedback.userId = "userId" + randomId + feedback.reasonText = "Optional reason text in this field." + + feedback.submit({ (response) in + // success + // verify response object... + XCTAssertNotNil(response.feedback) + XCTAssertTrue(response.feedback?.helpfulnessResponse == nil) + XCTAssertEqual(response.feedback?.inappropriateResponse.authorId, feedback.userId) + XCTAssertEqual(response.feedback?.inappropriateResponse.reasonText, feedback.reasonText) + expectation.fulfill() + + }) { (errors) in + // error + XCTFail("Should not be in failure block") + expectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + + func testSubmissionFailure() { + + let expectation = self.expectation(description: "testSubmitFeedbackFlag") + + let feedback = BVFeedbackSubmission(contentId: "badidshouldmakeerror", with: .review, with: .inappropriate) + + let randomId = String(arc4random()) + + feedback.userId = "userId" + randomId + + feedback.submit({ (response) in + // success + XCTFail("Should not be in success block") + expectation.fulfill() + }) { (errors) in + // error + let errorString : String = errors[0].localizedDescription; + XCTAssertEqual(errorString.contains("ERROR_PARAM_INVALID_PARAMETERS"), true) + expectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + +} diff --git a/BVSDKTests/ConversationsTests/SubmissionTests/PhotoUploadTests.swift b/BVSDKTests/ConversationsTests/SubmissionTests/PhotoUploadTests.swift new file mode 100644 index 00000000..16f43e29 --- /dev/null +++ b/BVSDKTests/ConversationsTests/SubmissionTests/PhotoUploadTests.swift @@ -0,0 +1,149 @@ +// +// ConversationsSubmissionTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + + +import XCTest +@testable import BVSDK + +class PhotoUploadTests: XCTestCase { + + override func setUp() { + super.setUp() + + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "kuy3zj9pr3n7i0wxajrzj04xo"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + BVSDKManager.shared().urlSessionDelegate = nil + } + + + func testUploadablePhotoPNGSuccessWithNetworkDelegate() { + let mainExpectation = self.expectation(description: "") + + if let image = PhotoUploadTests.createPNG() { + let photo = + BVUploadablePhoto(photo: image, photoCaption: "Very photogenic") + // upload photo, make sure it returns a non-empty URL + + let testDelegate = BVNetworkDelegateTestsDelegate() + XCTAssertNotNil( + testDelegate, "BVNetworkDelegateTestsDelegate is nil") + + testDelegate.urlSessionExpectation = + self.expectation(description: "urlSession expectation") + + testDelegate.urlSessionTaskExpectation = + self.expectation(description: "urlSessionTask expectation") + + BVSDKManager.shared().urlSessionDelegate = testDelegate + + photo.upload(for: .review, success: { (photoUrl) in + XCTAssertTrue(photoUrl.count > 0) + mainExpectation.fulfill() + }) { (errors) in + XCTFail() + mainExpectation.fulfill() + } + + } else { + mainExpectation.fulfill() + XCTFail() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + func testUploadablePhotoPNGSuccess() { + let expectation = self.expectation(description: "") + + if let image = PhotoUploadTests.createPNG() { + let photo = + BVUploadablePhoto(photo: image, photoCaption: "Very photogenic") + // upload photo, make sure it returns a non-empty URL + photo.upload(for: .review, success: { (photoUrl) in + XCTAssertTrue(photoUrl.count > 0) + expectation.fulfill() + }) { (errors) in + XCTFail() + expectation.fulfill() + } + + } else { + expectation.fulfill() + XCTFail() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + func testUploadablePhotoJPGTooLargeSuccess() { + let expectation = self.expectation(description: "") + + if let image = PhotoUploadTests.createJPG() { + let photo = + BVUploadablePhoto(photo: image, photoCaption: "Very photogenic") + // upload photo, make sure it returns a non-empty URL + photo.upload(for: .review, success: { (photoUrl) in + XCTAssertTrue(photoUrl.count > 0) + expectation.fulfill() + }) { (errors) in + XCTFail() + expectation.fulfill() + } + + } else { + expectation.fulfill() + XCTFail() + } + + waitForExpectations(timeout: 30, handler: nil) + } + + func testUploadablePhotoFailure() { + let photo = + BVUploadablePhoto( + photo: UIImage(), photoCaption: "Very photogenic") + + // upload photo, make sure it returns a non-empty URL + let expectation = self.expectation(description: "") + + photo.upload(for: .review, success: { (photoUrl) in + XCTFail() + expectation.fulfill() + }) { (errors) in + XCTAssertEqual(errors.count, 1) + let error = errors.first! as NSError + XCTAssertEqual( + error.userInfo[BVFieldErrorCode] as? String, + "ERROR_FORM_IMAGE_PARSE") + XCTAssertEqual( + error.userInfo[BVFieldErrorName] as? String, "photo") + XCTAssertEqual( + error.userInfo[BVFieldErrorMessage] as? String, + "We were unable to parse the image you uploaded. " + + "Please ensure that the image is a valid BMP, PNG, " + + "GIF or JPEG file.") + expectation.fulfill() + } + + waitForExpectations(timeout: 10, handler: nil) + } + + class func createPNG() -> UIImage? { + return UIImage( + named: "ph.png", + in: Bundle(for: PhotoUploadTests.self), + compatibleWith: nil) + } + + class func createJPG() -> UIImage? { + return UIImage( + named: "skelly_android.jpg", + in: Bundle(for: PhotoUploadTests.self), + compatibleWith: nil) + } +} diff --git a/BVSDKTests/ConversationsTests/SubmissionTests/QuestionSubmissionTests.swift b/BVSDKTests/ConversationsTests/SubmissionTests/QuestionSubmissionTests.swift new file mode 100644 index 00000000..5c2515d8 --- /dev/null +++ b/BVSDKTests/ConversationsTests/SubmissionTests/QuestionSubmissionTests.swift @@ -0,0 +1,107 @@ +// +// ConversationsSubmissionTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + + +import XCTest +@testable import BVSDK + +class QuestionSubmissionTests: XCTestCase { + + override func setUp() { + super.setUp() + + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "2cpdrhohmgmwfz8vqyo48f52g"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + } + + func testSubmitQuestionWithPhoto() { + let expectation = self.expectation(description: "") + + let question = self.fillOutQuestion(.submit) + question.submit({ (questionSubmission) in + expectation.fulfill() + XCTAssertTrue(questionSubmission.formFields?.keys.count == 0) + }, failure: { (errors) in + XCTFail() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + func testPreviewQuestionWithPhoto() { + let expectation = self.expectation(description: "") + + let question = self.fillOutQuestion(.preview) + question.submit({ (questionSubmission) in + expectation.fulfill() + // When run in Preview mode, we get the formFields that can be used for submission. + XCTAssertTrue(questionSubmission.formFields?.keys.count == 33) + }, failure: { (errors) in + XCTFail() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + + func fillOutQuestion(_ action : BVSubmissionAction) -> BVQuestionSubmission { + let question = BVQuestionSubmission(productId: "test1") + let randomId = String(arc4random()) + + question.questionSummary = "Question title question title?" + question.questionDetails = "Question body Question body Question body Question body Question body Question body Question body" + question.campaignId = "BV_REVIEW_DISPLAY" + question.locale = "en_US" + question.sendEmailAlertWhenAnswered = true + question.sendEmailAlertWhenPublished = true + question.userNickname = "UserNickname" + randomId + question.userId = "UserId" + randomId + question.userEmail = "developer@bazaarvoice.com" + question.agreedToTermsAndConditions = true + question.action = action + if let image = PhotoUploadTests.createPNG() { + question.addPhoto(image, withPhotoCaption: "Very photogenic") + } + return question + } + + func testSubmitQuestionFailure() { + let expectation = self.expectation(description: "") + + let question = BVQuestionSubmission(productId: "1000001") + question.userNickname = "cgil" + question.userId = "craiggiddl" + question.action = .preview + + question.submit({ (questionSubmission) in + XCTFail() + }, failure: { (errors) in + + XCTAssertEqual(errors.count, 2) + for error in errors as [NSError] { + let fieldName = error.userInfo[BVFieldErrorName] as! String + let errorCode = error.userInfo[BVFieldErrorCode] as! String + let errorMessage = error.userInfo[BVFieldErrorMessage] as! String + + if fieldName == "questionsummary" { + XCTAssertEqual(errorCode, "ERROR_FORM_REQUIRED") + XCTAssertEqual(errorMessage, "You must enter a question.") + } + else { + XCTAssertEqual(fieldName, "usernickname") + XCTAssertEqual(errorCode, "ERROR_FORM_DUPLICATE") + XCTAssertEqual(errorMessage, "Someone has already used that nickname.") + } + } + + expectation.fulfill() + }) + waitForExpectations(timeout: 10, handler: nil) + } + +} diff --git a/BVSDKTests/ConversationsTests/SubmissionTests/ReviewSubmissionTests.swift b/BVSDKTests/ConversationsTests/SubmissionTests/ReviewSubmissionTests.swift new file mode 100644 index 00000000..addab332 --- /dev/null +++ b/BVSDKTests/ConversationsTests/SubmissionTests/ReviewSubmissionTests.swift @@ -0,0 +1,263 @@ +// +// ConversationsSubmissionTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + + +import XCTest +@testable import BVSDK + +class ReviewSubmissionTests: XCTestCase { + + override func setUp() { + super.setUp() + + let configDict = ["clientId": "apitestcustomer", + "apiKeyConversations": "2cpdrhohmgmwfz8vqyo48f52g"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + BVSDKManager.shared().setLogLevel(.error) + BVSDKManager.shared().urlSessionDelegate = nil; + } + + func testSubmitReviewWithPhoto() { + let expectation = + self.expectation(description: "testSubmitReviewWithPhoto") + + let review = self.fillOutReview(.submit) + + review.submit({ (reviewSubmission) in + XCTAssertTrue(reviewSubmission.formFields?.keys.count == 0) + expectation.fulfill() + }, failure: { (errors) in + print(errors.description) + for error in errors { + let nsError = error as NSError + print(nsError.userInfo["BVFieldErrorMessage"] ?? "") + } + XCTFail() + expectation.fulfill() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + func testSubmitReviewWithPhotoAndNetworkDelegate() { + let mainExpectation = + self.expectation(description: "testSubmitReviewWithPhoto") + + let review = self.fillOutReview(.submit) + + let testDelegate = BVNetworkDelegateTestsDelegate() + + XCTAssertNotNil( + testDelegate, "BVNetworkDelegateTestsDelegate is nil") + + testDelegate.urlSessionExpectation = + self.expectation(description: "urlSession expectation") + + testDelegate.urlSessionTaskExpectation = + self.expectation(description: "urlSessionTask expectation") + + BVSDKManager.shared().urlSessionDelegate = testDelegate + + review.submit({ (reviewSubmission) in + XCTAssertTrue(reviewSubmission.formFields?.keys.count == 0) + mainExpectation.fulfill() + }, failure: { (errors) in + print(errors.description) + for error in errors { + let nsError = error as NSError + print(nsError.userInfo["BVFieldErrorMessage"] ?? "") + } + XCTFail() + mainExpectation.fulfill() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + func testPreviewReviewWithPhoto() { + + let expectation = + self.expectation(description: "testPreviewReviewWithPhoto") + + let review = self.fillOutReview(.preview) + + review.submit({ (reviewSubmission) in + // When run in Preview mode, we get the formFields that can be used for submission. + XCTAssertTrue(reviewSubmission.formFields?.keys.count == 50) + expectation.fulfill() + }, failure: { (errors) in + XCTFail() + expectation.fulfill() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + let VIDEO_URL = "https://www.youtube.com/watch?v=oHg5SJYRHA0" + let VIDEO_CAPTION = "Very videogenic" + + func testPreviewReviewWithVideo() { + + let expectation = + self.expectation(description: "testPreviewReviewWithVideo") + + let review = self.fillOutReview(.preview) + review.addVideoURL(VIDEO_URL, withCaption: VIDEO_CAPTION) + review.submit({ (reviewSubmission) in + // When run in Preview mode, we get the formFields that can be used for submission. + + XCTAssertTrue(reviewSubmission.formFields?.keys.count == 50) + expectation.fulfill() + }, failure: { (errors) in + XCTFail() + expectation.fulfill() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + func fillOutReview(_ action : BVSubmissionAction) -> BVReviewSubmission { + let review = BVReviewSubmission( + reviewTitle: "review title", + reviewText: "more than 50 more than 50 more than 50 more than 50 more" + + "than 50", + rating: 4, + productId: "test1") + + let randomId = String(arc4random()) + + review.campaignId = "BV_REVIEW_DISPLAY" + review.locale = "en_US" + review.sendEmailAlertWhenCommented = true + review.sendEmailAlertWhenPublished = true + review.userNickname = "UserNickname" + randomId + review.netPromoterScore = 5 + review.netPromoterComment = "Never!" + review.userId = "UserId" + randomId + review.userEmail = "developer@bazaarvoice.com" + review.agreedToTermsAndConditions = true + review.action = action + + review.addContextDataValueBool("VerifiedPurchaser", value: false) + review.addContextDataValueString("Age", value: "18to24") + review.addContextDataValueString("Gender", value: "Male") + review.addRatingQuestion("Quality", value: 1) + review.addRatingQuestion("Value", value: 3) + review.addRatingQuestion("HowDoes", value: 4) + review.addRatingQuestion("Fit", value: 3) + review.addCustomSubmissionParameter("_foo", withValue: "bar") + + if let image = PhotoUploadTests.createPNG() { + review.addPhoto(image, withPhotoCaption: "Very photogenic") + } + + return review + } + + + func testSubmitReviewFailure() { + let expectation = + self.expectation(description: "testSubmitReviewFailure") + + let review = BVReviewSubmission( + reviewTitle: "", reviewText: "", rating: 123, productId: "1000001") + review.userNickname = "cgil" + review.userId = "craiggiddl" + review.action = .submit + + review.submit({ (reviewSubmission) in + XCTFail() + expectation.fulfill() + }, failure: { (errors) in + errors.forEach { print("Expected Failure Item: \($0)") } + + XCTAssertEqual(errors.count, 5) + expectation.fulfill() + }) + waitForExpectations(timeout: 10, handler: nil) + } + + func testSubmitReviewFailureFormCodeParsing() { + let expectation = + self.expectation(description: "testSubmitReviewFailure") + + let review = BVReviewSubmission( + reviewTitle: "", reviewText: "", rating: 123, productId: "1000001") + review.userNickname = "cgil" + review.userId = "craiggiddl" + review.action = .submit + + review.submit({ (reviewSubmission) in + XCTFail() + expectation.fulfill() + }, failure: { (errors) in + var formRequiredCount = 0 + var formDuplicateCount = 0 + var formTooHighCount = 0 + errors.forEach { error in + let nsError = error as NSError + let errorCodeString = nsError.userInfo[BVFieldErrorCode] as? String + print("Error code string: \(errorCodeString ?? "empty")") + let bvSubmissionErrorCode = nsError.bvSubmissionErrorCode() + switch (bvSubmissionErrorCode) { + case .formRequired: + print("form required enum found") + formRequiredCount = formRequiredCount + 1 + case .formDuplicate: + print("form duplicate enum found") + formDuplicateCount = formDuplicateCount + 1 + case .formTooHigh: + print("form too high enum found") + formTooHighCount = formTooHighCount + 1 + default: + print("unknown enum") + } + } + + XCTAssertEqual(formRequiredCount, 3) + XCTAssertEqual(formDuplicateCount, 1) + XCTAssertEqual(formTooHighCount, 1) + + expectation.fulfill() + }) + waitForExpectations(timeout: 10, handler: nil) + } + + func testSubmitReviewFailureCodeParsing() { + let expectation = + self.expectation(description: "testSubmitReviewFailure") + + let review = BVReviewSubmission( + reviewTitle: "", reviewText: "", rating: 123, productId: "") + review.userNickname = "cgil" + review.userId = "craiggiddl" + review.action = .submit + + review.submit({ (reviewSubmission) in + XCTFail() + expectation.fulfill() + }, failure: { (errors) in + var badRequestCount = 0 + errors.forEach { error in + let nsError = error as NSError + let bvErrorCode = nsError.bvErrorCode() + switch (bvErrorCode) { + case .badRequest: + print("Found bad request") + badRequestCount = badRequestCount + 1 + default: + print("unknown enum found") + } + } + + XCTAssertEqual(badRequestCount, 1) + + expectation.fulfill() + }) + waitForExpectations(timeout: 10, handler: nil) + } +} diff --git a/BVSDKTests/ConversationsTests/SubmissionTests/StoreReviewSubmissionTests.swift b/BVSDKTests/ConversationsTests/SubmissionTests/StoreReviewSubmissionTests.swift new file mode 100644 index 00000000..c6409895 --- /dev/null +++ b/BVSDKTests/ConversationsTests/SubmissionTests/StoreReviewSubmissionTests.swift @@ -0,0 +1,86 @@ +// +// ConversationsSubmissionTests.swift +// Conversations +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + + +import XCTest +@testable import BVSDK + +class StoreReviewSubmissionTests: XCTestCase { + + override func setUp() { + super.setUp() + let configDict = ["clientId": "apiunittests", + "apiKeyConversationsStores": "2cpdrhohmgmwfz8vqyo48f52g"]; + BVSDKManager.configure(withConfiguration: configDict, configType: .staging) + BVSDKManager.shared().setLogLevel(.error) + } + + func testSubmitStoreReviewWithPhoto() { + + let expectation = self.expectation(description: "testSubmitStoreReviewWithPhoto") + + let review = self.fillOutReview() + review.submit({ (reviewSubmission) in + expectation.fulfill() + }, failure: { (errors) in + XCTFail() + expectation.fulfill() + }) + + waitForExpectations(timeout: 10, handler: nil) + } + + func fillOutReview() -> BVStoreReviewSubmission { + let review = BVStoreReviewSubmission(reviewTitle: "The best store ever!", + reviewText: "The store has some of the friendliest staff, and very knowledgable!", + rating: 5, + storeId: "1") + + let randomId = String(arc4random()) + + review.locale = "en_US" + review.sendEmailAlertWhenCommented = true + review.sendEmailAlertWhenPublished = true + review.userNickname = "UserNickname" + randomId + //review.user = "userField" + randomId + review.userId = "userField" + randomId + review.netPromoterScore = 5 + review.userEmail = "developer@bazaarvoice.com" + review.agreedToTermsAndConditions = true + review.action = .preview + + review.addContextDataValueBool("VerifiedPurchaser", value: false) + review.addContextDataValueString("Age", value: "18to24") + review.addContextDataValueString("Gender", value: "Male") + review.addRatingQuestion("Cleanliness", value: 1) + review.addRatingQuestion("HelpfullNess", value: 3) + review.addRatingQuestion("Inventory", value: 4) + + return review + } + + + func testSubmitReviewFailureStore() { + let expectation = self.expectation(description: "testSubmitReviewFailureStore") + + let review = BVStoreReviewSubmission(reviewTitle: "", reviewText: "", rating: 123, storeId: "1000001") + review.userNickname = "cgil" + review.userId = "craiggiddl" + review.action = .submit + + review.submit({ (reviewSubmission) in + //XCTFail() + expectation.fulfill() + }, failure: { (errors) in + errors.forEach { print("Expected Failure Item: \($0)") } + XCTAssertEqual(errors.count, 5) + expectation.fulfill() + }) + waitForExpectations(timeout: 10, handler: nil) + } + +} diff --git a/BVSDKTests/ConversationsTests/SubmissionTests/UASSubmissionTests.swift b/BVSDKTests/ConversationsTests/SubmissionTests/UASSubmissionTests.swift new file mode 100644 index 00000000..08a1a6e6 --- /dev/null +++ b/BVSDKTests/ConversationsTests/SubmissionTests/UASSubmissionTests.swift @@ -0,0 +1,88 @@ +// +// UASSubmissionTests.swift +// BVSDKTests +// +// Copyright © 2017 Bazaarvoice. All rights reserved. +// + +import XCTest +import CommonCrypto +@testable import BVSDK + +class UASSubmissionTests: XCTestCase { + + fileprivate enum TestStyle { + case fast + case slow + } + + fileprivate var testCoverage:TestStyle { + get { + return .fast + } + } + + fileprivate var bvAuthToken:String { + get { + // Paste bv_authtoken below: + return "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + } + } + + override func setUp() { + super.setUp() + let configDict = + ["clientId" : "conversationsapihostedauth", + "apiKeyConversations" : "772wfav4cvd8fpw2vdt64pgc"]; + BVSDKManager.configure( + withConfiguration: configDict, configType: .prod) + BVSDKManager.shared().setLogLevel(.verbose) + } + + override func tearDown() { + super.tearDown() + } + + func testSwapAuthorTokenForUserAuthenticationString() { + let expectation = + self.expectation( + description: "testSwapAuthorTokenForUserAuthenticationString") + + // VALIDATE: Please read and/or check this before submitting a SDK release. + /** + + This concerns regression testing against the authenticateuser.json endpoint: + + In order to properly validate this test: + 1.) A test review needs to be generated using this conversationsapihostedauth user while also configuring for hosted authentication, i.e., https://developer.bazaarvoice.com/conversations-api/tutorials/submission/authentication/bv-mastered, with valid api parameters, e.g., "hostedauthentication_authenticationemail" and "hostedauthentication_callbackurl". One tip would be to pass a uniquely generated email address with the @mailtest.nexus.bazaarvoice.com prefix domain. Then you can acquire the body of the hosted authentication verification email here: https://s3.console.aws.amazon.com/s3/buckets/notifications-data/openmx/?region=us-east-1 + 2.) Find the uniquely generated bitly within the body of the email and copy+paste into your favorite HTTP speaking tool and strip the redirect bv_authtoken parameter out. + 3.) Add that bv_authtoken string to the bvAuthToken computed property of this test. + 4.) Swap the testCoverage computed property to .slow + 5.) Run test(s) and hopefully all succeed. + + */ + switch testCoverage { + case .slow: + let uasSubmission:BVUASSubmission = + BVUASSubmission(bvAuthToken: bvAuthToken) + + uasSubmission.submit({ (response: BVUASSubmissionResponse) in + expectation.fulfill() + }) { (errors:[Error]) in + + errors.forEach { print("Expected Failure Item: \($0)") } + + XCTFail() + expectation.fulfill() + } + break + case .fast: + expectation.fulfill() + break + } + + waitForExpectations(timeout: 10, handler: nil) + } +} + + diff --git a/Tests/Tests/ConversationsTests/SubmissionTests/ph.png b/BVSDKTests/ConversationsTests/SubmissionTests/ph.png similarity index 100% rename from Tests/Tests/ConversationsTests/SubmissionTests/ph.png rename to BVSDKTests/ConversationsTests/SubmissionTests/ph.png diff --git a/Tests/Tests/ConversationsTests/SubmissionTests/skelly_android.jpg b/BVSDKTests/ConversationsTests/SubmissionTests/skelly_android.jpg similarity index 100% rename from Tests/Tests/ConversationsTests/SubmissionTests/skelly_android.jpg rename to BVSDKTests/ConversationsTests/SubmissionTests/skelly_android.jpg diff --git a/Tests/Tests/BVCurationsTests.m b/BVSDKTests/CurationsTests/BVCurationsTests.m similarity index 87% rename from Tests/Tests/BVCurationsTests.m rename to BVSDKTests/CurationsTests/BVCurationsTests.m index c46ad517..c4cab922 100644 --- a/Tests/Tests/BVCurationsTests.m +++ b/BVSDKTests/CurationsTests/BVCurationsTests.m @@ -6,11 +6,11 @@ // #import "BVBaseStubTestCase.h" +#import "BVNetworkDelegateTestsDelegate.h" #import #import @interface BVCurationsTests : BVBaseStubTestCase - @end @implementation BVCurationsTests @@ -27,6 +27,7 @@ - (void)setUp { [BVSDKManager configureWithConfiguration:configDict configType:BVConfigurationTypeStaging]; [[BVSDKManager sharedManager] setLogLevel:BVLogLevelError]; + [BVSDKManager sharedManager].urlSessionDelegate = nil; } - (void)tearDown { @@ -100,6 +101,77 @@ - (void)testFetchCurations { [self waitForExpectations]; } +// Test normal parse result from a feed but using network delegate +- (void)testFetchCurationsWithNetworkDelegate { + [self addStubWith200ResponseForJSONFileNamed:@"curationsFeedTest1.json"]; + + __weak XCTestExpectation *mainExpectation = [self + expectationWithDescription:@"testFetchCurationsWithNetworkDelegate"]; + + BVCurationsFeedRequest *feedRequest = [[BVCurationsFeedRequest alloc] + initWithGroups:@[ @"livebv", @"test2", @"test3" ]]; + + // media={'video':{'width':480,'height':360}} + feedRequest.media = @{ @"media" : @{@"width" : @"480", @"height" : @"360"} }; + + BVCurationsFeedLoader *urlRequest = [[BVCurationsFeedLoader alloc] init]; + + /// Setup the networking test delegate + BVNetworkDelegateTestsDelegate *testDelegate = + [[BVNetworkDelegateTestsDelegate alloc] init]; + XCTAssertNotNil(testDelegate, @"BVNetworkDelegateTestsDelegate is nil."); + + testDelegate.urlSessionExpectation = + [self expectationWithDescription: + @"testFetchCurationsWithNetworkDelegate urlSessionExpectation"]; + + testDelegate.urlSessionTaskExpectation = + [self expectationWithDescription: + @"testFetchCurationsWithNetworkDelegate urlSessionExpectation"]; + + [BVSDKManager sharedManager].urlSessionDelegate = testDelegate; + + [urlRequest loadFeedWithRequest:feedRequest + completionHandler:^(NSArray *feedItems) { + // success! + + XCTAssertNotNil( + feedItems, + @"ERROR: feeItems should not be nil in curations api response."); + + bool hasPhotos = NO; + bool hasVideos = NO; + for (BVCurationsFeedItem *feedItem in feedItems) { + if (feedItem.photos.count > 0) { + hasPhotos = YES; + } + if (feedItem.videos.count > 0) { + hasVideos = YES; + } + } + + XCTAssertTrue(hasPhotos, + @"Test feed did not have photos, but should have."); + XCTAssertTrue(hasVideos, + @"Test feed did not have videos, but should have."); + + [mainExpectation fulfill]; + + } + withFailure:^(NSError *error) { + // failure : ( + + NSString *errorString = + [NSString stringWithFormat:@"ERROR: Curations API feed failure: %@", + error.localizedDescription]; + XCTAssert(errorString == nil, @"%@", errorString); + + [mainExpectation fulfill]; + }]; + + [self waitForExpectations]; +} + // Test fetching curations with user's geolocation - (void)testFetchCurationsWithLocation { [self addStubWith200ResponseForJSONFileNamed:@"curationsFeedTest1.json"]; @@ -121,7 +193,7 @@ - (void)testFetchCurationsWithLocation { feedItems, @"ERROR: feeItems should not be nil in curations api response."); - int locationCount = 0; + NSInteger locationCount = 0; for (BVCurationsFeedItem *feedItem in feedItems) { if (feedItem.coordinates != nil && feedItem.coordinates.latitude != nil && @@ -717,6 +789,36 @@ - (void)testPostNilRequestObject { [self waitForExpectations]; } +- (void)testPostNilRequestObjectWithNetworkDelegate { + __weak XCTestExpectation *expectation = + [self expectationWithDescription: + @"testPostNilRequestObjectWithNetworkDelegate"]; + + // Hit the API - which should never make an API call and just return the + // error + // handler + BVCurationsPhotoUploader *uploadAPI = [[BVCurationsPhotoUploader alloc] init]; + + [uploadAPI submitCurationsContentWithParams:nil + completionHandler:^(void) { + // completion + NSLog(@"success"); + XCTAssertTrue( + NO, @"Success block called in test which should have failed."); + [expectation fulfill]; + } + withFailure:^(NSError *error) { + // error + NSLog(@"ERROR: %@", error.localizedDescription); + XCTAssertNotNil(error, @"Got a nil NSError object"); + XCTAssertEqual(error.code, -1, @"Expected error code -1"); + + [expectation fulfill]; + }]; + + [self waitForExpectations]; +} + - (void)waitForExpectations { [self waitForExpectationsWithTimeout:30.0 handler:^(NSError *error) { diff --git a/Tests/Tests/resources/conversations/conversationsAuthorWithIncludes.json b/BVSDKTests/MockData/conversations/conversationsAuthorWithIncludes.json similarity index 100% rename from Tests/Tests/resources/conversations/conversationsAuthorWithIncludes.json rename to BVSDKTests/MockData/conversations/conversationsAuthorWithIncludes.json diff --git a/Tests/Tests/resources/conversations/conversationsGenericPostResponse.json b/BVSDKTests/MockData/conversations/conversationsGenericPostResponse.json similarity index 100% rename from Tests/Tests/resources/conversations/conversationsGenericPostResponse.json rename to BVSDKTests/MockData/conversations/conversationsGenericPostResponse.json diff --git a/Tests/Tests/resources/conversations/conversationsQuestionsIncludeAnswers.json b/BVSDKTests/MockData/conversations/conversationsQuestionsIncludeAnswers.json similarity index 100% rename from Tests/Tests/resources/conversations/conversationsQuestionsIncludeAnswers.json rename to BVSDKTests/MockData/conversations/conversationsQuestionsIncludeAnswers.json diff --git a/Tests/Tests/resources/conversations/conversationsReviewsEnduranceCycles.json b/BVSDKTests/MockData/conversations/conversationsReviewsEnduranceCycles.json similarity index 100% rename from Tests/Tests/resources/conversations/conversationsReviewsEnduranceCycles.json rename to BVSDKTests/MockData/conversations/conversationsReviewsEnduranceCycles.json diff --git a/Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_FilterLocation.json b/BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_FilterLocation.json similarity index 100% rename from Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_FilterLocation.json rename to BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_FilterLocation.json diff --git a/Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortHighestRated.json b/BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortHighestRated.json similarity index 100% rename from Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortHighestRated.json rename to BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortHighestRated.json diff --git a/Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortLowestRated.json b/BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortLowestRated.json similarity index 100% rename from Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortLowestRated.json rename to BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortLowestRated.json diff --git a/Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortMostHelpful.json b/BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortMostHelpful.json similarity index 100% rename from Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortMostHelpful.json rename to BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortMostHelpful.json diff --git a/Tests/Tests/resources/conversations/stores/storeBulkFeedWithStatistics.json b/BVSDKTests/MockData/conversations/stores/storeBulkFeedWithStatistics.json similarity index 100% rename from Tests/Tests/resources/conversations/stores/storeBulkFeedWithStatistics.json rename to BVSDKTests/MockData/conversations/stores/storeBulkFeedWithStatistics.json diff --git a/Tests/Tests/resources/conversations/stores/storeFetchByIds.json b/BVSDKTests/MockData/conversations/stores/storeFetchByIds.json similarity index 100% rename from Tests/Tests/resources/conversations/stores/storeFetchByIds.json rename to BVSDKTests/MockData/conversations/stores/storeFetchByIds.json diff --git a/Tests/Tests/resources/conversations/stores/storeItemWithStatsAndReviews.json b/BVSDKTests/MockData/conversations/stores/storeItemWithStatsAndReviews.json similarity index 100% rename from Tests/Tests/resources/conversations/stores/storeItemWithStatsAndReviews.json rename to BVSDKTests/MockData/conversations/stores/storeItemWithStatsAndReviews.json diff --git a/Tests/Tests/resources/conversations/stores/storeNoStoresFoundResult.json b/BVSDKTests/MockData/conversations/stores/storeNoStoresFoundResult.json similarity index 100% rename from Tests/Tests/resources/conversations/stores/storeNoStoresFoundResult.json rename to BVSDKTests/MockData/conversations/stores/storeNoStoresFoundResult.json diff --git a/Tests/Tests/resources/notification_config/testNotificationConfig.json b/BVSDKTests/MockData/conversations/testNotificationConfig.json similarity index 100% rename from Tests/Tests/resources/notification_config/testNotificationConfig.json rename to BVSDKTests/MockData/conversations/testNotificationConfig.json diff --git a/Tests/Tests/resources/notification_config/testNotificationProductConfig.json b/BVSDKTests/MockData/conversations/testNotificationProductConfig.json similarity index 100% rename from Tests/Tests/resources/notification_config/testNotificationProductConfig.json rename to BVSDKTests/MockData/conversations/testNotificationProductConfig.json diff --git a/Tests/Tests/resources/conversations/testShowCategory.json b/BVSDKTests/MockData/conversations/testShowCategory.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowCategory.json rename to BVSDKTests/MockData/conversations/testShowCategory.json diff --git a/Tests/Tests/resources/conversations/testShowCategorySparse.json b/BVSDKTests/MockData/conversations/testShowCategorySparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowCategorySparse.json rename to BVSDKTests/MockData/conversations/testShowCategorySparse.json diff --git a/Tests/Tests/resources/conversations/testShowCommentStory.json b/BVSDKTests/MockData/conversations/testShowCommentStory.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowCommentStory.json rename to BVSDKTests/MockData/conversations/testShowCommentStory.json diff --git a/Tests/Tests/resources/conversations/testShowCommentStorySparse.json b/BVSDKTests/MockData/conversations/testShowCommentStorySparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowCommentStorySparse.json rename to BVSDKTests/MockData/conversations/testShowCommentStorySparse.json diff --git a/Tests/Tests/resources/conversations/testShowComments.json b/BVSDKTests/MockData/conversations/testShowComments.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowComments.json rename to BVSDKTests/MockData/conversations/testShowComments.json diff --git a/Tests/Tests/resources/conversations/testShowCommentsSparse.json b/BVSDKTests/MockData/conversations/testShowCommentsSparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowCommentsSparse.json rename to BVSDKTests/MockData/conversations/testShowCommentsSparse.json diff --git a/Tests/Tests/resources/conversations/testShowProducts.json b/BVSDKTests/MockData/conversations/testShowProducts.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowProducts.json rename to BVSDKTests/MockData/conversations/testShowProducts.json diff --git a/Tests/Tests/resources/conversations/testShowProductsSparse.json b/BVSDKTests/MockData/conversations/testShowProductsSparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowProductsSparse.json rename to BVSDKTests/MockData/conversations/testShowProductsSparse.json diff --git a/Tests/Tests/resources/conversations/testShowProfile.json b/BVSDKTests/MockData/conversations/testShowProfile.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowProfile.json rename to BVSDKTests/MockData/conversations/testShowProfile.json diff --git a/Tests/Tests/resources/conversations/testShowProfileSparse.json b/BVSDKTests/MockData/conversations/testShowProfileSparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowProfileSparse.json rename to BVSDKTests/MockData/conversations/testShowProfileSparse.json diff --git a/Tests/Tests/resources/conversations/testShowQuestion.json b/BVSDKTests/MockData/conversations/testShowQuestion.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowQuestion.json rename to BVSDKTests/MockData/conversations/testShowQuestion.json diff --git a/Tests/Tests/resources/conversations/testShowQuestionSparse.json b/BVSDKTests/MockData/conversations/testShowQuestionSparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowQuestionSparse.json rename to BVSDKTests/MockData/conversations/testShowQuestionSparse.json diff --git a/Tests/Tests/resources/conversations/testShowQuestions.json b/BVSDKTests/MockData/conversations/testShowQuestions.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowQuestions.json rename to BVSDKTests/MockData/conversations/testShowQuestions.json diff --git a/Tests/Tests/resources/conversations/testShowQuestionsSparse.json b/BVSDKTests/MockData/conversations/testShowQuestionsSparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowQuestionsSparse.json rename to BVSDKTests/MockData/conversations/testShowQuestionsSparse.json diff --git a/Tests/Tests/resources/conversations/testShowReview.json b/BVSDKTests/MockData/conversations/testShowReview.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowReview.json rename to BVSDKTests/MockData/conversations/testShowReview.json diff --git a/Tests/Tests/resources/conversations/testShowReviewIncludesSearch.json b/BVSDKTests/MockData/conversations/testShowReviewIncludesSearch.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowReviewIncludesSearch.json rename to BVSDKTests/MockData/conversations/testShowReviewIncludesSearch.json diff --git a/Tests/Tests/resources/conversations/testShowReviewSparse.json b/BVSDKTests/MockData/conversations/testShowReviewSparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowReviewSparse.json rename to BVSDKTests/MockData/conversations/testShowReviewSparse.json diff --git a/Tests/Tests/resources/conversations/testShowStatistics.json b/BVSDKTests/MockData/conversations/testShowStatistics.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowStatistics.json rename to BVSDKTests/MockData/conversations/testShowStatistics.json diff --git a/Tests/Tests/resources/conversations/testShowStory.json b/BVSDKTests/MockData/conversations/testShowStory.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowStory.json rename to BVSDKTests/MockData/conversations/testShowStory.json diff --git a/Tests/Tests/resources/conversations/testShowStorySparse.json b/BVSDKTests/MockData/conversations/testShowStorySparse.json similarity index 100% rename from Tests/Tests/resources/conversations/testShowStorySparse.json rename to BVSDKTests/MockData/conversations/testShowStorySparse.json diff --git a/Tests/Tests/resources/conversations/testSyndicationSource.json b/BVSDKTests/MockData/conversations/testSyndicationSource.json similarity index 100% rename from Tests/Tests/resources/conversations/testSyndicationSource.json rename to BVSDKTests/MockData/conversations/testSyndicationSource.json diff --git a/Tests/Tests/resources/curations/curations500Error.json b/BVSDKTests/MockData/curations/curations500Error.json similarity index 100% rename from Tests/Tests/resources/curations/curations500Error.json rename to BVSDKTests/MockData/curations/curations500Error.json diff --git a/BVSDKTests/MockData/curations/curationsFeedTest1.json b/BVSDKTests/MockData/curations/curationsFeedTest1.json new file mode 100644 index 00000000..7d76add1 --- /dev/null +++ b/BVSDKTests/MockData/curations/curationsFeedTest1.json @@ -0,0 +1 @@ +{"status":"ok","code":200,"results":20,"tasks":[],"updates":[{"html":null,"data":{"rating":null,"classification":"photo","text":"New gong mallet on left. Two views of old mallet- note the screws! #nofoolin #livebv","id":103424,"praises":2,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/bvrecruits","username":"bvrecruits","alias":"Inside Bazaarvoice","token":"bvrecruits","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fbvrecruits&checksum=2dd1f709","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459619804,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDtQE91QRkP/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12519635_555843914588709_1008786948_n.jpg?ig_cache_key=MTIxOTcwMTc4NDEwMTA2NzAyMw%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12519635_555843914588709_1008786948_n.jpg?ig_cache_key=MTIxOTcwMTc4NDEwMTA2NzAyMw%3D%3D.2","image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12519635_555843914588709_1008786948_n.jpg%3Fig_cache_key%3DMTIxOTcwMTc4NDEwMTA2NzAyMw%253D%253D.2&checksum=3c0b20ef","id":66535,"local_url":"https://scissors.feedmagnet.com/content/photo/66535/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDtQE91QRkP/","product_id":null,"language":"en","token":"1219701784101067023_1234474165","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"So this happened a few weeks ago at the @bazaarvoice Gong. The mallet broke on Gene at the start of the meeting. #livebv","id":103423,"praises":2,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/bvrecruits","username":"bvrecruits","alias":"Inside Bazaarvoice","token":"bvrecruits","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fbvrecruits&checksum=2dd1f709","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459619547,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDtPlf0QRjE/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12940139_442901665913217_51825942_n.jpg?ig_cache_key=MTIxOTY5OTYyMTU2ODI1NjE5Ng%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12940139_442901665913217_51825942_n.jpg?ig_cache_key=MTIxOTY5OTYyMTU2ODI1NjE5Ng%3D%3D.2","image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12940139_442901665913217_51825942_n.jpg%3Fig_cache_key%3DMTIxOTY5OTYyMTU2ODI1NjE5Ng%253D%253D.2&checksum=aaabe636","id":66534,"local_url":"https://scissors.feedmagnet.com/content/photo/66534/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDtPlf0QRjE/","product_id":null,"language":"en","token":"1219699621568256196_1234474165","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photoblurb","text":"Yeah, its amazing what a shift in role can do to your #productivity. #livebv pic.twitter.com/dqHAZn5017","id":103410,"praises":0,"explicit_permission_status":"uninitiated","author":{"profile":"http://twitter.com/assaultbylogic/","username":"@assaultbylogic","alias":"John Steinmetz","token":"@assaultbylogic","avatar":"https://fake-curations.feedmagnet.com/?url=http%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F506144768041033728%2F7D3zvDtj_normal.jpeg&checksum=4bd41aba","channel":"twitter"},"links":[{"domain":"twitter.com","display_url":"pic.twitter.com/dqHAZn5017","url":"http://twitter.com/assaultbylogic/status/716071765982613504/photo/1","short_url":"http://pic.twitter.com/dqHAZn5017","favicon":"https://scissors.feedmagnet.com/content/remote_image/?url=http%3A//twitter.com/favicon.ico&checksum=4838572917018106911","id":33482}],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"twitter","tags":[],"timestamp":1459559787,"photos":[{"origin":"twitter","permalink":null,"token":"716071765458292736","role":"photo","display_url":"pic.twitter.com/dqHAZn5017","url":"http://pbs.twimg.com/media/Ce__eg3UEAAlBag.jpg","image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=http%3A%2F%2Fpbs.twimg.com%2Fmedia%2FCe__eg3UEAAlBag.jpg&checksum=8605d5f2","id":66525,"local_url":"https://scissors.feedmagnet.com/content/photo/66525/"}],"teaser":null,"groups":["livebv"],"permalink":"http://www.twitter.com/assaultbylogic/status/716071765982613504/","product_id":null,"language":"en","token":"716071765982613504","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"That's a wrap on a b:allin April Fools Day. My coworkers really know how to stick it to the (wo)man #livebv","id":103408,"praises":28,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/clairemcahill","username":"clairemcahill","alias":"Claire Cahill","token":"clairemcahill","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fclairemcahill&checksum=32dc65fa","channel":"instagram"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459557103,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDrYe9jEw2z/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12328073_213867595646978_19140177_n.jpg?ig_cache_key=MTIxOTE3NTgwNDkyMTEyMjIyNw%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12328073_213867595646978_19140177_n.jpg?ig_cache_key=MTIxOTE3NTgwNDkyMTEyMjIyNw%3D%3D.2","image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12328073_213867595646978_19140177_n.jpg%3Fig_cache_key%3DMTIxOTE3NTgwNDkyMTEyMjIyNw%253D%253D.2&checksum=ecf4149e","id":66523,"local_url":"https://scissors.feedmagnet.com/content/photo/66523/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDrYe9jEw2z/","product_id":null,"language":"en","token":"1219175804921122227_16766218","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Beautiful double rainbow on this rainy Friday morning. #livebv","id":103403,"praises":3,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/stacibobaci","username":"stacibobaci","alias":"Staci Preece","token":"stacibobaci","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fstacibobaci&checksum=7855bc11","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459549608,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDrKMDCBybc/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12479053_488536168018588_322360363_n.jpg?ig_cache_key=MTIxOTExMjkzMjQ4NzkzMTYxMg%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12479053_488536168018588_322360363_n.jpg?ig_cache_key=MTIxOTExMjkzMjQ4NzkzMTYxMg%3D%3D.2","image_service_url":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12479053_488536168018588_322360363_n.jpg%3Fig_cache_key%3DMTIxOTExMjkzMjQ4NzkzMTYxMg%253D%253D.2&checksum=9e50bb74","id":66518,"local_url":"https://scissors.feedmagnet.com/content/photo/66518/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDrKMDCBybc/","product_id":null,"language":"en","token":"1219112932487931612_1390765456","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Henri bringing the fun to Fridays at BV. #livebv","id":103402,"praises":1,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/stacibobaci","username":"stacibobaci","alias":"Staci Preece","token":"stacibobaci","avatar":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fstacibobaci&checksum=7855bc11","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459549451,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDrJ46Mhya8/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12917827_1062280490512054_1654040317_n.jpg?ig_cache_key=MTIxOTExMTYxNzMzMDM1Nzk0OA%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12917827_1062280490512054_1654040317_n.jpg?ig_cache_key=MTIxOTExMTYxNzMzMDM1Nzk0OA%3D%3D.2","image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12917827_1062280490512054_1654040317_n.jpg%3Fig_cache_key%3DMTIxOTExMTYxNzMzMDM1Nzk0OA%253D%253D.2&checksum=69592689","id":66517,"local_url":"https://scissors.feedmagnet.com/content/photo/66517/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDrJ46Mhya8/","product_id":null,"language":"en","token":"1219111617330357948_1390765456","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"My backfill has been hired. #livebv","id":103397,"praises":74,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/twopotatopups","username":"twopotatopups","alias":"Ruby + Linus","token":"twopotatopups","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Ftwopotatopups&checksum=239d0f11","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459541749,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDq7MyIQxCL/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12930892_613559405463765_506131907_n.jpg?ig_cache_key=MTIxOTA0NzAxMjM2MDcyMDUyMw%3D%3D.2.c","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12930892_613559405463765_506131907_n.jpg?ig_cache_key=MTIxOTA0NzAxMjM2MDcyMDUyMw%3D%3D.2.c","image_service_url":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12930892_613559405463765_506131907_n.jpg%3Fig_cache_key%3DMTIxOTA0NzAxMjM2MDcyMDUyMw%253D%253D.2.c&checksum=9b72ba82","id":66515,"local_url":"https://scissors.feedmagnet.com/content/photo/66515/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDq7MyIQxCL/","product_id":null,"language":"en","token":"1219047012360720523_1645724291","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Linus visits work. #lastdayatwork #livebv","id":103396,"praises":83,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/twopotatopups","username":"twopotatopups","alias":"Ruby + Linus","token":"twopotatopups","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Ftwopotatopups&checksum=239d0f11","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459541662,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDq7CDPQxBx/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12519387_1079512398758810_635637173_n.jpg?ig_cache_key=MTIxOTA0NjI3NDgxNzUyNzkyMQ%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12519387_1079512398758810_635637173_n.jpg?ig_cache_key=MTIxOTA0NjI3NDgxNzUyNzkyMQ%3D%3D.2","image_service_url":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12519387_1079512398758810_635637173_n.jpg%3Fig_cache_key%3DMTIxOTA0NjI3NDgxNzUyNzkyMQ%253D%253D.2&checksum=ac356c9e","id":66514,"local_url":"https://scissors.feedmagnet.com/content/photo/66514/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDq7CDPQxBx/","product_id":null,"language":"af","token":"1219046274817527921_1645724291","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Dustin, you've been replaced. No hard feelings? #BVNYC #livebv #nyc #Manhattan","id":103387,"praises":14,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/jaxattaxatx","username":"jaxattaxatx","alias":"Jaxxy","token":"jaxattaxatx","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fjaxattaxatx&checksum=451dec52","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459533201,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDqq5Sog4AH/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12950190_208734626169828_1535706099_n.jpg?ig_cache_key=MTIxODk3NTMwNDEyNzg0MDI2Mw%3D%3D.2.c","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12950190_208734626169828_1535706099_n.jpg?ig_cache_key=MTIxODk3NTMwNDEyNzg0MDI2Mw%3D%3D.2.c","image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12950190_208734626169828_1535706099_n.jpg%3Fig_cache_key%3DMTIxODk3NTMwNDEyNzg0MDI2Mw%253D%253D.2.c&checksum=3e3489a4","id":66508,"local_url":"https://scissors.feedmagnet.com/content/photo/66508/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDqq5Sog4AH/","product_id":null,"language":"nl","token":"1218975304127840263_265450223","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Here comes trouble to #BVNYC . Love when BV alumnus come back for a visit. #nyc #Manhattan #livebv","id":103386,"praises":14,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/jaxattaxatx","username":"jaxattaxatx","alias":"Jaxxy","token":"jaxattaxatx","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fjaxattaxatx&checksum=451dec52","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459532990,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDqqfevA4PD/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12905089_958430594253946_646200997_n.jpg?ig_cache_key=MTIxODk3MzUzMDQxNTM5OTg3NQ%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12905089_958430594253946_646200997_n.jpg?ig_cache_key=MTIxODk3MzUzMDQxNTM5OTg3NQ%3D%3D.2","image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12905089_958430594253946_646200997_n.jpg%3Fig_cache_key%3DMTIxODk3MzUzMDQxNTM5OTg3NQ%253D%253D.2&checksum=39d874f5","id":66507,"local_url":"https://scissors.feedmagnet.com/content/photo/66507/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDqqfevA4PD/","product_id":null,"language":"en","token":"1218973530415399875_265450223","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"@chrisolfers taking a photo of a happy rainbow at work. #livebv","id":103377,"praises":15,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/cmsadler_","username":"cmsadler_","alias":"Cynthia","token":"cmsadler_","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fcmsadler_&checksum=306c7e75","channel":"instagram"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459522393,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDqWR6_Ji72/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12501835_1709110972698464_701329195_n.jpg?ig_cache_key=MTIxODg4NDYzNzc0Nzk4MjA3MA%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12501835_1709110972698464_701329195_n.jpg?ig_cache_key=MTIxODg4NDYzNzc0Nzk4MjA3MA%3D%3D.2","image_service_url":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12501835_1709110972698464_701329195_n.jpg%3Fig_cache_key%3DMTIxODg4NDYzNzc0Nzk4MjA3MA%253D%253D.2&checksum=7170d007","id":66500,"local_url":"https://scissors.feedmagnet.com/content/photo/66500/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDqWR6_Ji72/","product_id":null,"language":"en","token":"1218884637747982070_40392442","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Double rainbow kind of day. #livebv","id":103378,"praises":8,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/cjohannessen","username":"cjohannessen","alias":"cjohannessen","token":"cjohannessen","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fcjohannessen&checksum=84ad41c2","channel":"instagram"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459522369,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDqWPDeuPp-/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12912344_217373781956431_1019039446_n.jpg?ig_cache_key=MTIxODg4NDQ0MDcwOTIwMDUxMA%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12912344_217373781956431_1019039446_n.jpg?ig_cache_key=MTIxODg4NDQ0MDcwOTIwMDUxMA%3D%3D.2","image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12912344_217373781956431_1019039446_n.jpg%3Fig_cache_key%3DMTIxODg4NDQ0MDcwOTIwMDUxMA%253D%253D.2&checksum=c0102506","id":66501,"local_url":"https://scissors.feedmagnet.com/content/photo/66501/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDqWPDeuPp-/","product_id":null,"language":"en","token":"1218884440709200510_23503645","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photoblurb","text":"View from my office. Happy Friday. #livebv @ Bazaarvoice instagram.com/p/BDqV9xRgTNU/","id":103375,"praises":0,"explicit_permission_status":"uninitiated","author":{"profile":"http://twitter.com/kingill/","username":"@kingill","alias":"Kin Gill","token":"@kingill","avatar":"https://fake-curations.feedmagnet.com/?url=http%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F542029430671937536%2FoEM79Cey_normal.jpeg&checksum=0fb0e735","channel":"twitter"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[],"channel":"twitter","tags":[],"timestamp":1459522231,"photos":[{"origin":"instagram","permalink":"http://instagr.am/p/BDqV9xRgTNU","token":"BDqV9xRgTNU","role":"photo","display_url":"instagram.com/p/BDqV9xRgTNU/","url":"http://instagr.am/p/BDqV9xRgTNU/media?size=l","image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=http%3A%2F%2Finstagr.am%2Fp%2FBDqV9xRgTNU%2Fmedia%3Fsize%3Dl&checksum=6277dfed","id":66498,"local_url":"https://scissors.feedmagnet.com/content/photo/66498/"}],"teaser":null,"groups":["livebv"],"permalink":"http://www.twitter.com/kingill/status/715914242470203393/","product_id":null,"language":"en","token":"715914242470203393","place":"Austin, TX","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"View from my office. Happy Friday. #livebv","id":103376,"praises":11,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/kingill","username":"kingill","alias":"Kin Gill","token":"kingill","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fkingill&checksum=d3f84981","channel":"instagram"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459522228,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDqV9xRgTNU/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/11250185_1056695637702197_442919075_n.jpg?ig_cache_key=MTIxODg4MzI1MjkyODk4Mzg5Mg%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/11250185_1056695637702197_442919075_n.jpg?ig_cache_key=MTIxODg4MzI1MjkyODk4Mzg5Mg%3D%3D.2","image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F11250185_1056695637702197_442919075_n.jpg%3Fig_cache_key%3DMTIxODg4MzI1MjkyODk4Mzg5Mg%253D%253D.2&checksum=1e5d010a","id":66499,"local_url":"https://scissors.feedmagnet.com/content/photo/66499/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDqV9xRgTNU/","product_id":null,"language":"en","token":"1218883252928983892_2840075","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Office #rainbow #atx #livebv","id":103373,"praises":13,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/xavier.official","username":"xavier.official","alias":"XAVIER","token":"xavier.official","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fxavier.official&checksum=ba8fbff7","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459522187,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDqV4udC_XR/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12940812_1598583990464499_1666468693_n.jpg?ig_cache_key=MTIxODg4MjkwNjMwNDAxNzg3Mw%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12940812_1598583990464499_1666468693_n.jpg?ig_cache_key=MTIxODg4MjkwNjMwNDAxNzg3Mw%3D%3D.2","image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12940812_1598583990464499_1666468693_n.jpg%3Fig_cache_key%3DMTIxODg4MjkwNjMwNDAxNzg3Mw%253D%253D.2&checksum=e162392b","id":66497,"local_url":"https://scissors.feedmagnet.com/content/photo/66497/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDqV4udC_XR/","product_id":null,"language":"en","token":"1218882906304017873_217705764","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Lunchtime in Munich ☀️ #lovemunich #lunchtime #thursdays #forawalk #bvde #livebv","id":103342,"praises":9,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/myannalein","username":"myannalein","alias":"myannalein","token":"myannalein","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fmyannalein&checksum=bbb981ec","channel":"instagram"},"links":[],"coordinates":{"latitude":48.126206574,"longitude":11.575143459},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459442232,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDn9YqsHPd2/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12132702_253880111617449_1832587668_n.jpg?ig_cache_key=MTIxODIxMjIwMDE2ODg3OTk5MA%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12132702_253880111617449_1832587668_n.jpg?ig_cache_key=MTIxODIxMjIwMDE2ODg3OTk5MA%3D%3D.2","image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12132702_253880111617449_1832587668_n.jpg%3Fig_cache_key%3DMTIxODIxMjIwMDE2ODg3OTk5MA%253D%253D.2&checksum=d9ee66da","id":66479,"local_url":"https://scissors.feedmagnet.com/content/photo/66479/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDn9YqsHPd2/","product_id":null,"language":"de","token":"1218212200168879990_1251176852","place":"Isar","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photoblurb","text":"Whole Foods is finally open #LiveBv #breakfast #lunch #ilovetacos… instagram.com/p/BDn4jmWAb15e...","id":103335,"praises":0,"explicit_permission_status":"uninitiated","author":{"profile":"http://twitter.com/ManishPatel_/","username":"@ManishPatel_","alias":"Manish Patel","token":"@ManishPatel_","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=http%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F2161276097%2Fmini_me_normal.jpg&checksum=b05be054","channel":"twitter"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[],"channel":"twitter","tags":[],"timestamp":1459439821,"photos":[{"origin":"instagram","permalink":"http://instagr.am/p/BDn4jmWAb15eInl5pfQIT4dijMLliE0S6MOF9U0","token":"BDn4jmWAb15eInl5pfQIT4dijMLliE0S6MOF9U0","role":"photo","display_url":"instagram.com/p/BDn4jmWAb15e...","url":"http://instagr.am/p/BDn4jmWAb15eInl5pfQIT4dijMLliE0S6MOF9U0/media?size=l","image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=http%3A%2F%2Finstagr.am%2Fp%2FBDn4jmWAb15eInl5pfQIT4dijMLliE0S6MOF9U0%2Fmedia%3Fsize%3Dl&checksum=8ee94d01","id":66475,"local_url":"https://scissors.feedmagnet.com/content/photo/66475/"}],"teaser":null,"groups":["livebv"],"permalink":"http://www.twitter.com/ManishPatel_/status/715568591349923840/","product_id":null,"language":"en","token":"715568591349923840","place":"Austin, TX","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Whole Foods Market Express opened in our office today. Cold section, hot bar, salad bar, pastries, and coffee. #livebv","id":103331,"praises":18,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/lacefaced","username":"lacefaced","alias":"Lacey Corm","token":"lacefaced","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Flacefaced&checksum=0a5fa748","channel":"instagram"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459433978,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDntpIykpef/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12599404_1692324691023079_1535909013_n.jpg?ig_cache_key=MTIxODE0Mjk2MzI1Njk1NjgzMQ%3D%3D.2.c","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12599404_1692324691023079_1535909013_n.jpg?ig_cache_key=MTIxODE0Mjk2MzI1Njk1NjgzMQ%3D%3D.2.c","image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12599404_1692324691023079_1535909013_n.jpg%3Fig_cache_key%3DMTIxODE0Mjk2MzI1Njk1NjgzMQ%253D%253D.2.c&checksum=91a0508d","id":66472,"local_url":"https://scissors.feedmagnet.com/content/photo/66472/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDntpIykpef/","product_id":null,"language":"en","token":"1218142963256956831_1751188","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"Whole foods is about to break this bread inside of BV! #livebv","id":103328,"praises":2,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/coffeetowhiskey","username":"coffeetowhiskey","alias":"coffeetowhiskey","token":"coffeetowhiskey","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fcoffeetowhiskey&checksum=dadc7e52","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459432337,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDnqgtNkU5-/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/11917983_1713608175560975_1734452237_n.jpg?ig_cache_key=MTIxODEyOTE4OTc0OTczOTEzNA%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/11917983_1713608175560975_1734452237_n.jpg?ig_cache_key=MTIxODEyOTE4OTc0OTczOTEzNA%3D%3D.2","image_service_url":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F11917983_1713608175560975_1734452237_n.jpg%3Fig_cache_key%3DMTIxODEyOTE4OTc0OTczOTEzNA%253D%253D.2&checksum=96bbeb7f","id":66470,"local_url":"https://scissors.feedmagnet.com/content/photo/66470/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDnqgtNkU5-/","product_id":null,"language":"en","token":"1218129189749739134_44621106","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"photo","text":"@wholefoods Cafe now open at @bazaarvoice. This will be dangerous. #tacoseveryday #livebv","id":103324,"praises":3,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/robbach","username":"robbach","alias":"Rob Wernersbach","token":"robbach","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Frobbach&checksum=b9295def","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[],"channel":"instagram","tags":[],"timestamp":1459427040,"photos":[{"origin":"instagram","permalink":"https://www.instagram.com/p/BDngaOFPg7j/","token":"https://scontent.cdninstagram.com/t51.2885-15/e35/12940101_654864084654700_1779712380_n.jpg?ig_cache_key=MTIxODA4NDc2MzU0MjA5NzYzNQ%3D%3D.2","role":"photo","display_url":null,"url":"https://scontent.cdninstagram.com/t51.2885-15/e35/12940101_654864084654700_1779712380_n.jpg?ig_cache_key=MTIxODA4NDc2MzU0MjA5NzYzNQ%3D%3D.2","image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe35%2F12940101_654864084654700_1779712380_n.jpg%3Fig_cache_key%3DMTIxODA4NDc2MzU0MjA5NzYzNQ%253D%253D.2&checksum=ff0ebd47","id":66468,"local_url":"https://scissors.feedmagnet.com/content/photo/66468/"}],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDngaOFPg7j/","product_id":null,"language":"en","token":"1218084763542097635_15568970","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"#liveBV @ellenlobb @pmattozzi","id":103005,"praises":22,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/camilleeerenee","username":"camilleeerenee","alias":"Camille Hodgins","token":"camilleeerenee","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fcamilleeerenee&checksum=6bf6580c","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/t51.2885-15/e15/10919224_1599803243674549_1988039306_n.jpg?ig_cache_key=MTIxMzI0MTk1NjAxOTY0ODUwMw%3D%3D.2","token":"1213241956019648503_4282476","display_url":null,"image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe15%2F10919224_1599803243674549_1988039306_n.jpg%3Fig_cache_key%3DMTIxMzI0MTk1NjAxOTY0ODUwMw%253D%253D.2&checksum=f1e5e218","id":6604,"remote_url":"https://scontent.cdninstagram.com/t50.2886-16/12901704_1068823146489816_1409121301_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1458849733,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BDWTSGHJrf3/","product_id":null,"language":"hu","token":"1213241956019648503_4282476","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"videoblurb","text":"Break-FAST #livebv @ Bazaarvoice instagram.com/p/BCK8oehlKU9/","id":101904,"praises":0,"explicit_permission_status":"uninitiated","author":{"profile":"http://twitter.com/PMattozzi/","username":"@PMattozzi","alias":"Pasquale Mattozzi","token":"@PMattozzi","avatar":"https://fake-curations.feedmagnet.com/?url=http%3A%2F%2Fpbs.twimg.com%2Fprofile_images%2F605857185206136832%2FtHee-ifW_normal.jpg&checksum=460efc47","channel":"twitter"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[{"origin":"instagram","permalink":"https://t.co/wPimpk4iLm","code":"\n \n ","image_url":"https://scontent.cdninstagram.com/t51.2885-15/e15/1517003_460132144197991_42830467_n.jpg?ig_cache_key=MTE5MjAzMTcxNTczODI5NzY2MQ%3D%3D.2","token":"8ce825ac867ead63664bd7f24bd2ce02d3baca17","display_url":"instagram.com/p/BCK8oehlKU9/","image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe15%2F1517003_460132144197991_42830467_n.jpg%3Fig_cache_key%3DMTE5MjAzMTcxNTczODI5NzY2MQ%253D%253D.2&checksum=e793a352","id":6458,"remote_url":"http://scontent.cdninstagram.com/t50.2886-16/12501032_1705615433055375_1369744601_n.mp4","video_type":"video/mp4"}],"channel":"twitter","tags":[],"timestamp":1456321276,"photos":[],"teaser":null,"groups":["livebv"],"permalink":"http://www.twitter.com/PMattozzi/status/702488467431882754/","product_id":null,"language":"nl","token":"702488467431882754","place":"Austin, TX","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Break-FAST #livebv","id":101905,"praises":32,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/pmattozzi","username":"pmattozzi","alias":"Pasquale Mattozzi","token":"pmattozzi","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fpmattozzi&checksum=c2cb92d3","channel":"instagram"},"links":[],"coordinates":{"latitude":30.39994,"longitude":-97.73694},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/t51.2885-15/e15/1517003_460132144197991_42830467_n.jpg?ig_cache_key=MTE5MjAzMTcxNTczODI5NzY2MQ%3D%3D.2","token":"1192031715738297661_1716320","display_url":null,"image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe15%2F1517003_460132144197991_42830467_n.jpg%3Fig_cache_key%3DMTE5MjAzMTcxNTczODI5NzY2MQ%253D%253D.2&checksum=e793a352","id":6459,"remote_url":"https://scontent.cdninstagram.com/t50.2886-16/12768644_1082453641785863_370585242_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1456321275,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BCK8oehlKU9/","product_id":null,"language":"id","token":"1192031715738297661_1716320","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Presidents' Day with the squad. #livebv","id":101412,"praises":29,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/clairemcahill","username":"clairemcahill","alias":"Claire Cahill","token":"clairemcahill","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fclairemcahill&checksum=32dc65fa","channel":"instagram"},"links":[],"coordinates":{"latitude":30.247849115,"longitude":-97.712987264},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/t51.2885-15/e15/12716878_1707324952845242_2085616899_n.jpg?ig_cache_key=MTE4NTcyNjIwNjk0OTEzMzkxMA%3D%3D.2","token":"1185726206949133910_16766218","display_url":null,"image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe15%2F12716878_1707324952845242_2085616899_n.jpg%3Fig_cache_key%3DMTE4NTcyNjIwNjk0OTEzMzkxMA%253D%253D.2&checksum=33c5c8b7","id":6395,"remote_url":"https://scontent.cdninstagram.com/t50.2886-16/12750556_1714209535468703_832177444_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1455569600,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BB0i7P2Ew5W/","product_id":null,"language":"en","token":"1185726206949133910_16766218","place":"Kreigs Softball Fields","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Lives were taken, but the paella was worth it. @lcortes9 @melslau #paellanight #coworkers #livebv","id":101072,"praises":22,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/mumtamittal","username":"mumtamittal","alias":"Mumta Mittal","token":"mumtamittal","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fmumtamittal&checksum=bf2de91b","channel":"instagram"},"links":[],"coordinates":{"latitude":30.401781538,"longitude":-97.726534903},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/t51.2885-15/e15/12545430_889430307840839_2054003541_n.jpg","token":"1179437136863569623_183672568","display_url":null,"image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe15%2F12545430_889430307840839_2054003541_n.jpg&checksum=0fc7eb7d","id":6361,"remote_url":"https://scontent.cdninstagram.com/t50.2886-16/12661734_1685267548397462_1681161633_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1454819884,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BBeM9O5qCbX/","product_id":null,"language":"en","token":"1179437136863569623_183672568","place":"The Domain","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Thunderclap, Thunderclap, Thunderclap @weldesouky_ getting Mila to do some dancehall moves #Dog #Dance #LiveBV #BostonTerrier","id":100722,"praises":24,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/solhi82","username":"solhi82","alias":"Khoshal Wial","token":"solhi82","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fsolhi82&checksum=5392ff19","channel":"instagram"},"links":[],"coordinates":{"latitude":51.4918785,"longitude":-0.22221},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/t51.2885-15/e15/12383274_986937161376325_518739794_n.jpg","token":"1173292083978082651_10809773","display_url":null,"image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Ft51.2885-15%2Fe15%2F12383274_986937161376325_518739794_n.jpg&checksum=0b189924","id":6338,"remote_url":"https://scontent.cdninstagram.com/t50.2886-16/12629857_1515644998740233_337300898_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1454087337,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BBIXu8bSplb/","product_id":null,"language":"en","token":"1173292083978082651_10809773","place":"Bazaarvoice London","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Happy belated holidays from Bazaarvoice #livebv","id":100523,"praises":30,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/emilyc27","username":"emilyc27","alias":"Emily Cahill","token":"emilyc27","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Femilyc27&checksum=26d81442","channel":"instagram"},"links":[],"coordinates":{"latitude":30.2672,"longitude":-97.7639},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xap1/t51.2885-15/e15/12501963_1669203430020797_562617241_n.jpg","token":"1169811852732733007_4092884","display_url":null,"image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xap1%2Ft51.2885-15%2Fe15%2F12501963_1669203430020797_562617241_n.jpg&checksum=7b4cc92c","id":6323,"remote_url":"https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/12632292_558107417701686_1925849904_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453672461,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA8Aa6gLRZP/","product_id":null,"language":"en","token":"1169811852732733007_4092884","place":"Austin, Texas","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Only and greatest capture of the night. #livebv #holidayparty","id":100511,"praises":34,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/mumtamittal","username":"mumtamittal","alias":"Mumta Mittal","token":"mumtamittal","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fmumtamittal&checksum=bf2de91b","channel":"instagram"},"links":[],"coordinates":{"latitude":30.400894176,"longitude":-97.737876229},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xpa1/t51.2885-15/e15/12523726_1051341644903643_409440336_n.jpg","token":"1169604886613468176_183672568","display_url":null,"image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xpa1%2Ft51.2885-15%2Fe15%2F12523726_1051341644903643_409440336_n.jpg&checksum=d33beb73","id":6321,"remote_url":"https://scontent.cdninstagram.com/hphotos-xpa1/t50.2886-16/12604273_172551556445453_725183008_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453647789,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA7RXKSKCQQ/","product_id":null,"language":"en","token":"1169604886613468176_183672568","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Ok fine, better quality #livebv #ballas @treylamastres @k_weaver @krayhill @sanzen7 @stephanieazy @stevene8seven @jmikulewicz","id":100506,"praises":29,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/clairemcahill","username":"clairemcahill","alias":"Claire Cahill","token":"clairemcahill","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fclairemcahill&checksum=32dc65fa","channel":"instagram"},"links":[],"coordinates":{"latitude":30.400894176,"longitude":-97.737876229},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xtf1/t51.2885-15/e15/12568710_465218383684592_57593569_n.jpg","token":"1169374026348564136_16766218","display_url":null,"image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xtf1%2Ft51.2885-15%2Fe15%2F12568710_465218383684592_57593569_n.jpg&checksum=f4d15661","id":6320,"remote_url":"https://scontent.cdninstagram.com/hphotos-xap1/t50.2886-16/12625159_555715164604795_1780131248_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453620268,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA6c3s6Ew6o/","product_id":null,"language":"no","token":"1169374026348564136_16766218","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"#livebv #intern #uwaterloo #canada","id":100501,"praises":9,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/jason.dippel","username":"jason.dippel","alias":"Jason Dippel","token":"jason.dippel","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fjason.dippel&checksum=2c547f82","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xpa1/t51.2885-15/e15/12519461_1012827692110413_1831775215_n.jpg","token":"1169338689096703510_1938603766","display_url":null,"image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xpa1%2Ft51.2885-15%2Fe15%2F12519461_1012827692110413_1831775215_n.jpg&checksum=5530121a","id":6319,"remote_url":"https://scontent.cdninstagram.com/hphotos-xpt1/t50.2886-16/12604349_1709226285956211_2064409690_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453616056,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA6U1ehut4W/","product_id":null,"language":null,"token":"1169338689096703510_1938603766","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Last one, promise. #livebv","id":100500,"praises":13,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/lacefaced","username":"lacefaced","alias":"Lacey Corm","token":"lacefaced","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Flacefaced&checksum=0a5fa748","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xpf1/t51.2885-15/e15/12501780_1683252328609815_1245965548_n.jpg","token":"1169328866554188816_1751188","display_url":null,"image_service_url":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xpf1%2Ft51.2885-15%2Fe15%2F12501780_1683252328609815_1245965548_n.jpg&checksum=942d52e1","id":6318,"remote_url":"https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/12604017_239086309756246_194559971_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453614885,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA6SmikkpQQ/","product_id":null,"language":"fr","token":"1169328866554188816_1751188","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"With my awesome teammates #livebv","id":100496,"praises":13,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/cmsadler_","username":"cmsadler_","alias":"Cynthia","token":"cmsadler_","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fcmsadler_&checksum=306c7e75","channel":"instagram"},"links":[],"coordinates":{"latitude":30.400894176,"longitude":-97.737876229},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xpa1/t51.2885-15/e15/12547595_921361411279792_966057559_n.jpg","token":"1169304254127943619_40392442","display_url":null,"image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xpa1%2Ft51.2885-15%2Fe15%2F12547595_921361411279792_966057559_n.jpg&checksum=0443f7f7","id":6315,"remote_url":"https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/12563847_1662394087346193_834707399_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453611951,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA6NAYdpi_D/","product_id":null,"language":"en","token":"1169304254127943619_40392442","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Work party with my sweets #livebv","id":100497,"praises":11,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/cmsadler_","username":"cmsadler_","alias":"Cynthia","token":"cmsadler_","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fcmsadler_&checksum=306c7e75","channel":"instagram"},"links":[],"coordinates":{"latitude":30.400894176,"longitude":-97.737876229},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xtp1/t51.2885-15/e15/12556098_1559662294352625_316603596_n.jpg","token":"1169303597308325806_40392442","display_url":null,"image_service_url":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xtp1%2Ft51.2885-15%2Fe15%2F12556098_1559662294352625_316603596_n.jpg&checksum=65c4ba79","id":6316,"remote_url":"https://scontent.cdninstagram.com/hphotos-xpa1/t50.2886-16/12629874_1658164057791658_1525017928_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453611872,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA6M20wJi-u/","product_id":null,"language":"en","token":"1169303597308325806_40392442","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Holiday party with my SQUAD. #ballas #forbayley #livebv @jmikulewicz @treylamastres @k_weaver @krayhill @raquel_pescado @sanzen7 @stevene8seven @stephanieazy","id":100494,"praises":14,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/clairemcahill","username":"clairemcahill","alias":"Claire Cahill","token":"clairemcahill","avatar":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fclairemcahill&checksum=32dc65fa","channel":"instagram"},"links":[],"coordinates":{"latitude":30.400894176,"longitude":-97.737876229},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xft1/t51.2885-15/e15/12568280_1698776257072816_300869961_n.jpg","token":"1169291604206489501_16766218","display_url":null,"image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xft1%2Ft51.2885-15%2Fe15%2F12568280_1698776257072816_300869961_n.jpg&checksum=a62bd075","id":6314,"remote_url":"https://scontent.cdninstagram.com/hphotos-xpf1/t50.2886-16/12625308_803923709737011_376215250_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453610443,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA6KITTkw-d/","product_id":null,"language":"en","token":"1169291604206489501_16766218","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Playing at work #livebv","id":100493,"praises":21,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/lacefaced","username":"lacefaced","alias":"Lacey Corm","token":"lacefaced","avatar":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Flacefaced&checksum=0a5fa748","channel":"instagram"},"links":[],"coordinates":{"latitude":30.400894176,"longitude":-97.737876229},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xpa1/t51.2885-15/e15/12501969_212619399083087_534102308_n.jpg","token":"1169283760706983873_1751188","display_url":null,"image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xpa1%2Ft51.2885-15%2Fe15%2F12501969_212619399083087_534102308_n.jpg&checksum=cd9f4ba6","id":6313,"remote_url":"https://scontent.cdninstagram.com/hphotos-xpa1/t50.2886-16/12629949_536304726535267_1027687559_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453609508,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA6IWKekpfB/","product_id":null,"language":"en","token":"1169283760706983873_1751188","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Mad Men holiday party #livebv","id":100489,"praises":17,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/lacefaced","username":"lacefaced","alias":"Lacey Corm","token":"lacefaced","avatar":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Flacefaced&checksum=0a5fa748","channel":"instagram"},"links":[],"coordinates":{"latitude":30.400894176,"longitude":-97.737876229},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xfp1/t51.2885-15/e15/12407722_1666246673593052_1366502760_n.jpg","token":"1169253115318408286_1751188","display_url":null,"image_service_url":"https://fake-curations.feedmagnet.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xfp1%2Ft51.2885-15%2Fe15%2F12407722_1666246673593052_1366502760_n.jpg&checksum=e75de509","id":6312,"remote_url":"https://scontent.cdninstagram.com/hphotos-xaf1/t50.2886-16/12624733_1026734357379269_1236858963_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453605854,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA6BYNvEpRe/","product_id":null,"language":"en","token":"1169253115318408286_1751188","place":"Bazaarvoice","reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Mad Men: BV #livebv","id":100485,"praises":15,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/kellyballard","username":"kellyballard","alias":"kellyballard","token":"kellyballard","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fkellyballard&checksum=b00ee386","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xfa1/t51.2885-15/e15/12552299_581427582009536_1448295394_n.jpg","token":"1169227418328330655_23191755","display_url":null,"image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xfa1%2Ft51.2885-15%2Fe15%2F12552299_581427582009536_1448295394_n.jpg&checksum=104da5f5","id":6311,"remote_url":"https://scontent.cdninstagram.com/hphotos-xfa1/t50.2886-16/12629874_525395434307147_128451139_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1453602791,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BA57iRjG3Gf/","product_id":null,"language":"de","token":"1169227418328330655_23191755","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"#dsbrewing #livebv","id":100171,"praises":2,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/gregorydgarcia","username":"gregorydgarcia","alias":"Greg Garcia","token":"gregorydgarcia","avatar":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fgregorydgarcia&checksum=a3f50bb0","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xpa1/t51.2885-15/e15/12545518_936916606393298_1560909505_n.jpg","token":"1163236159841701855_1903119162","display_url":null,"image_service_url":"https://curations-imaging-b.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xpa1%2Ft51.2885-15%2Fe15%2F12545518_936916606393298_1560909505_n.jpg&checksum=7b103d9d","id":6286,"remote_url":"https://scontent.cdninstagram.com/hphotos-xtf1/t50.2886-16/12552107_654742188000126_1777347979_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1452888577,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BAkpR_RQQ_f/","product_id":null,"language":null,"token":"1163236159841701855_1903119162","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"Winning entry to the beer competition. Preview #livebv","id":100159,"praises":3,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/gregorydgarcia","username":"gregorydgarcia","alias":"Greg Garcia","token":"gregorydgarcia","avatar":"https://curations-imaging-c.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fgregorydgarcia&checksum=a3f50bb0","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xfp1/t51.2885-15/e15/12531116_1116001908418790_640733119_n.jpg","token":"1163106183268076922_1903119162","display_url":null,"image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xfp1%2Ft51.2885-15%2Fe15%2F12531116_1116001908418790_640733119_n.jpg&checksum=ace55cc4","id":6285,"remote_url":"https://scontent.cdninstagram.com/hphotos-xta1/t50.2886-16/12572695_956267994460082_1562479518_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1452873083,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/BAkLulJwQ16/","product_id":null,"language":"en","token":"1163106183268076922_1903119162","place":null,"reply_to":null,"sourceClient":"scissors"}},{"html":null,"data":{"rating":null,"classification":"video","text":"\"Hover\" board! #livebv #hoverboard","id":99272,"praises":22,"explicit_permission_status":"uninitiated","author":{"profile":"http://instagram.com/xavier.music","username":"xavier.music","alias":"XAVIER","token":"xavier.music","avatar":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Favatars.io%2Finstagram%2Fxavier.music&checksum=613dffa7","channel":"instagram"},"links":[],"coordinates":{"latitude":null,"longitude":null},"featured_groups":[],"videos":[{"origin":"instagram","permalink":null,"code":"\n \n ","image_url":"https://scontent.cdninstagram.com/hphotos-xap1/t51.2885-15/e15/1209667_957507650993463_1712942369_n.jpg","token":"1150751871444186379_217705764","display_url":null,"image_service_url":"https://curations-imaging.nexus.bazaarvoice.com/?url=https%3A%2F%2Fscontent.cdninstagram.com%2Fhphotos-xap1%2Ft51.2885-15%2Fe15%2F1209667_957507650993463_1712942369_n.jpg&checksum=beee7ec7","id":6228,"remote_url":"https://scontent.cdninstagram.com/hphotos-xaf1/t50.2886-16/12456703_512865545552634_328597401_n.mp4","video_type":null}],"channel":"instagram","tags":[],"timestamp":1451400334,"photos":[],"teaser":"","groups":["livebv","wat-test-2"],"permalink":"https://www.instagram.com/p/_4SrrqC_UL/","product_id":null,"language":"da","token":"1150751871444186379_217705764","place":null,"reply_to":null,"sourceClient":"scissors"}}],"options":{"client":"scissors","limit":20,"groups":"livebv","has_photo":"true"}} diff --git a/Tests/Tests/resources/curations/post_ErrorParsingBody.json b/BVSDKTests/MockData/curations/post_ErrorParsingBody.json similarity index 100% rename from Tests/Tests/resources/curations/post_ErrorParsingBody.json rename to BVSDKTests/MockData/curations/post_ErrorParsingBody.json diff --git a/Tests/Tests/resources/curations/post_MissingRequiredKey.json b/BVSDKTests/MockData/curations/post_MissingRequiredKey.json similarity index 100% rename from Tests/Tests/resources/curations/post_MissingRequiredKey.json rename to BVSDKTests/MockData/curations/post_MissingRequiredKey.json diff --git a/Tests/Tests/resources/curations/post_successfulCreation.json b/BVSDKTests/MockData/curations/post_successfulCreation.json similarity index 100% rename from Tests/Tests/resources/curations/post_successfulCreation.json rename to BVSDKTests/MockData/curations/post_successfulCreation.json diff --git a/Tests/Tests/resources/curations/puppy.jpg b/BVSDKTests/MockData/curations/puppy.jpg similarity index 100% rename from Tests/Tests/resources/curations/puppy.jpg rename to BVSDKTests/MockData/curations/puppy.jpg diff --git a/Tests/Tests/resources/curations/test_pattern.jpg b/BVSDKTests/MockData/curations/test_pattern.jpg similarity index 100% rename from Tests/Tests/resources/curations/test_pattern.jpg rename to BVSDKTests/MockData/curations/test_pattern.jpg diff --git a/Tests/Tests/resources/emptyJSON.json b/BVSDKTests/MockData/emptyJSON.json similarity index 100% rename from Tests/Tests/resources/emptyJSON.json rename to BVSDKTests/MockData/emptyJSON.json diff --git a/Tests/Tests/resources/malformedJSON.json b/BVSDKTests/MockData/malformedJSON.json similarity index 100% rename from Tests/Tests/resources/malformedJSON.json rename to BVSDKTests/MockData/malformedJSON.json diff --git a/Tests/Tests/resources/pin/productsToReviewResult.json b/BVSDKTests/MockData/productsToReviewResult.json similarity index 100% rename from Tests/Tests/resources/pin/productsToReviewResult.json rename to BVSDKTests/MockData/productsToReviewResult.json diff --git a/Tests/Tests/resources/recommendations/recommendationsByCategoryId.json b/BVSDKTests/MockData/recommendations/recommendationsByCategoryId.json similarity index 100% rename from Tests/Tests/resources/recommendations/recommendationsByCategoryId.json rename to BVSDKTests/MockData/recommendations/recommendationsByCategoryId.json diff --git a/Tests/Tests/resources/recommendations/recommendationsByProductId.json b/BVSDKTests/MockData/recommendations/recommendationsByProductId.json similarity index 100% rename from Tests/Tests/resources/recommendations/recommendationsByProductId.json rename to BVSDKTests/MockData/recommendations/recommendationsByProductId.json diff --git a/Tests/Tests/resources/recommendations/recommendationsNullJSON.json b/BVSDKTests/MockData/recommendations/recommendationsNullJSON.json similarity index 100% rename from Tests/Tests/resources/recommendations/recommendationsNullJSON.json rename to BVSDKTests/MockData/recommendations/recommendationsNullJSON.json diff --git a/Tests/Tests/resources/recommendations/recommendationsResult.json b/BVSDKTests/MockData/recommendations/recommendationsResult.json similarity index 100% rename from Tests/Tests/resources/recommendations/recommendationsResult.json rename to BVSDKTests/MockData/recommendations/recommendationsResult.json diff --git a/Tests/Tests/resources/recommendations/userProfile1.json b/BVSDKTests/MockData/recommendations/userProfile1.json similarity index 100% rename from Tests/Tests/resources/recommendations/userProfile1.json rename to BVSDKTests/MockData/recommendations/userProfile1.json diff --git a/Tests/Tests/BVNotificationConfigTests.h b/BVSDKTests/NotificationTests/BVNotificationConfigTests.h similarity index 100% rename from Tests/Tests/BVNotificationConfigTests.h rename to BVSDKTests/NotificationTests/BVNotificationConfigTests.h diff --git a/Tests/Tests/BVNotificationConfigTests.m b/BVSDKTests/NotificationTests/BVNotificationConfigTests.m similarity index 100% rename from Tests/Tests/BVNotificationConfigTests.m rename to BVSDKTests/NotificationTests/BVNotificationConfigTests.m diff --git a/Tests/Tests/BVStoreNotificationConfigurationLoader+Private.h b/BVSDKTests/NotificationTests/BVStoreNotificationConfigurationLoader+Private.h similarity index 100% rename from Tests/Tests/BVStoreNotificationConfigurationLoader+Private.h rename to BVSDKTests/NotificationTests/BVStoreNotificationConfigurationLoader+Private.h diff --git a/Tests/Tests/BVRecommendationsTests.m b/BVSDKTests/RecommendationsTests/BVRecommendationsTests.m similarity index 81% rename from Tests/Tests/BVRecommendationsTests.m rename to BVSDKTests/RecommendationsTests/BVRecommendationsTests.m index 598a06bf..7ca51a7c 100644 --- a/Tests/Tests/BVRecommendationsTests.m +++ b/BVSDKTests/RecommendationsTests/BVRecommendationsTests.m @@ -10,9 +10,9 @@ #import #import "BVBaseStubTestCase.h" +#import "BVNetworkDelegateTestsDelegate.h" -@interface BVRecommendationsTests : BVBaseStubTestCase { -} +@interface BVRecommendationsTests : BVBaseStubTestCase @end @implementation BVRecommendationsTests @@ -27,6 +27,7 @@ - (void)setUp { [BVSDKManager configureWithConfiguration:configDict configType:BVConfigurationTypeStaging]; [[BVSDKManager sharedManager] setLogLevel:BVLogLevelError]; + [BVSDKManager sharedManager].urlSessionDelegate = nil; } - (void)tearDown { @@ -66,6 +67,43 @@ - (void)testFetchProductRecommendations { [self waitForExpectations]; } +// Same as the above test but we'll test using our networking delegate. +- (void)testFetchProductRecommendationsWithNetworkingDelegate { + __weak XCTestExpectation *expectation = + [self expectationWithDescription: + @"testFetchProductRecommendationsWithNetworkingDelegate"]; + + [self addStubWith200ResponseForJSONFileNamed:@"recommendationsResult.json"]; + + BVRecommendationsRequest *request = + [[BVRecommendationsRequest alloc] initWithLimit:10]; + BVRecommendationsLoader *loader = [[BVRecommendationsLoader alloc] init]; + + /// Setup the networking test delegate + BVNetworkDelegateTestsDelegate *testDelegate = + [[BVNetworkDelegateTestsDelegate alloc] init]; + XCTAssertNotNil(testDelegate, @"BVNetworkDelegateTestsDelegate is nil."); + + testDelegate.urlSessionExpectation = + [self expectationWithDescription:@"urlSessionExpectation"]; + + [BVSDKManager sharedManager].urlSessionDelegate = testDelegate; + + [loader loadRequest:request + completionHandler:^( + NSArray *__nonnull recommendations) { + XCTAssertTrue([recommendations count] > 0, + @"Recommendation result should not be size 0"); + [expectation fulfill]; + } + errorHandler:^(NSError *__nonnull error) { + XCTFail(@"Error handler should not have been called"); + [expectation fulfill]; + }]; + + [self waitForExpectations]; +} + - (void)testRecommendationsByProductId { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"testRecommendationsByProductId"]; diff --git a/BVSDKTests/Support/BVSDKTests-Bridging-Header.h b/BVSDKTests/Support/BVSDKTests-Bridging-Header.h new file mode 100644 index 00000000..85eb8fc7 --- /dev/null +++ b/BVSDKTests/Support/BVSDKTests-Bridging-Header.h @@ -0,0 +1,21 @@ +// +// BVSDKTests-Bridging-Header.h +// BVSDK +// +// Copyright © 2016 Bazaarvoice. All rights reserved. +// + +#ifndef BVSDKTests_Bridging_Header_h +#define BVSDKTests_Bridging_Header_h + +#import "BVBaseProductRequest_Private.h" +#import "BVBaseReviewsRequest_Private.h" +#import "BVCommaUtil.h" +#import "BVCommentsRequest_Private.h" +#import "BVNetworkDelegateTestsDelegate.h" +#import "BVProductFilterType.h" +#import "BVProductIncludeType.h" +#import "BVRelationalFilterOperator.h" +#import "UIImage+BundleLocator.h" + +#endif /* BVSDKTests_Bridging_Header_h */ diff --git a/BVSDKTests/Info.plist b/BVSDKTests/Support/Info.plist similarity index 100% rename from BVSDKTests/Info.plist rename to BVSDKTests/Support/Info.plist diff --git a/Tests/Tests/Tests-Info.plist b/BVSDKTests/Support/Tests-Info.plist similarity index 100% rename from Tests/Tests/Tests-Info.plist rename to BVSDKTests/Support/Tests-Info.plist diff --git a/Tests/Tests/Tests-Prefix.pch b/BVSDKTests/Support/Tests-Prefix.pch similarity index 100% rename from Tests/Tests/Tests-Prefix.pch rename to BVSDKTests/Support/Tests-Prefix.pch diff --git a/Tests/Tests/en.lproj/InfoPlist.strings b/BVSDKTests/Support/en.lproj/InfoPlist.strings similarity index 100% rename from Tests/Tests/en.lproj/InfoPlist.strings rename to BVSDKTests/Support/en.lproj/InfoPlist.strings diff --git a/BVSDKTests/UIImage+BundleLocator.m b/BVSDKTests/UIImage+BundleLocator.m deleted file mode 100644 index 17420e21..00000000 --- a/BVSDKTests/UIImage+BundleLocator.m +++ /dev/null @@ -1,21 +0,0 @@ -// -// UIImage+Tests.m -// BVSDK -// -// Copyright © 2016 Bazaarvoice. All rights reserved. -// - -#import "UIImage+BundleLocator.h" - -@implementation UIImage (BundleLocator) -+ (UIImage *)bundledImageNamed:(NSString *)imageName { - - return [UIImage imageNamed:imageName - inBundle:[NSBundle - bundleForClass:[BundleLocator class]] - compatibleWithTraitCollection:nil]; -} -@end - -@implementation BundleLocator -@end diff --git a/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/project.pbxproj b/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/project.pbxproj index d9f23e84..7f42aebd 100644 --- a/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/project.pbxproj +++ b/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/project.pbxproj @@ -133,7 +133,6 @@ 87A3192B1CF4938E000D3D0F /* conversationsQuestionsIncludeAnswers.json in Resources */ = {isa = PBXBuildFile; fileRef = 87A3192A1CF4938E000D3D0F /* conversationsQuestionsIncludeAnswers.json */; }; 87A3192F1CF493AF000D3D0F /* recommendationsResult.json in Resources */ = {isa = PBXBuildFile; fileRef = 87A3192E1CF493AF000D3D0F /* recommendationsResult.json */; }; 87A319311CF493D5000D3D0F /* userProfile1.json in Resources */ = {isa = PBXBuildFile; fileRef = 87A319301CF493D5000D3D0F /* userProfile1.json */; }; - 87A319331CF493E3000D3D0F /* curationsEnduranceCycles.json in Resources */ = {isa = PBXBuildFile; fileRef = 87A319321CF493E3000D3D0F /* curationsEnduranceCycles.json */; }; 87A319351CF4942F000D3D0F /* conversationsReviewsEnduranceCycles.json in Resources */ = {isa = PBXBuildFile; fileRef = 87A319341CF4942F000D3D0F /* conversationsReviewsEnduranceCycles.json */; }; 87B97E961DEF218C005B8C85 /* CartViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B97E941DEF218C005B8C85 /* CartViewController.swift */; }; 87B97E971DEF218C005B8C85 /* CartViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 87B97E951DEF218C005B8C85 /* CartViewController.xib */; }; @@ -305,9 +304,9 @@ 73F020F21CED4E8D00FC7D7D /* QuestionAnswerTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuestionAnswerTableViewCell.swift; sourceTree = ""; }; 73F020F31CED4E8D00FC7D7D /* QuestionAnswerTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = QuestionAnswerTableViewCell.xib; sourceTree = ""; }; 85EE127F748BF21B76427E51 /* Pods-BVSDKDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BVSDKDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BVSDKDemo/Pods-BVSDKDemo.debug.xcconfig"; sourceTree = ""; }; - 870F130E1E436FB800D46BE6 /* conversationsAuthorWithIncludes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsAuthorWithIncludes.json; path = ../../../Tests/Tests/resources/conversations/conversationsAuthorWithIncludes.json; sourceTree = ""; }; + 870F130E1E436FB800D46BE6 /* conversationsAuthorWithIncludes.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsAuthorWithIncludes.json; path = ../../../BVSDKTests/MockData/conversations/conversationsAuthorWithIncludes.json; sourceTree = ""; }; 871AD3F21D063A57006583E5 /* ProfileUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileUtils.swift; sourceTree = ""; }; - 871AD4041D074E2F006583E5 /* post_successfulCreation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = post_successfulCreation.json; path = ../../../Tests/Tests/resources/curations/post_successfulCreation.json; sourceTree = ""; }; + 871AD4041D074E2F006583E5 /* post_successfulCreation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = post_successfulCreation.json; path = ../../../BVSDKTests/MockData/curations/post_successfulCreation.json; sourceTree = ""; }; 871AD4091D088B9A006583E5 /* submitPhotoWithReview.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = submitPhotoWithReview.json; sourceTree = ""; }; 871AD40F1D08A5B8006583E5 /* ProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; 871AD4101D08A5B8006583E5 /* ProfileViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ProfileViewController.xib; sourceTree = ""; }; @@ -322,10 +321,10 @@ 8763B3181D1DC65B00C71F54 /* enduranceCyclesSanFrancisco.gpx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = enduranceCyclesSanFrancisco.gpx; path = gpx/enduranceCyclesSanFrancisco.gpx; sourceTree = ""; }; 876FF1B51D21DDFF000ED410 /* FacebookLoginViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = FacebookLoginViewController.xib; path = BVSDKDemo/FacebookLoginViewController.xib; sourceTree = ""; }; 876FF1B71D22C0D7000ED410 /* NotificationPermissionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NotificationPermissionViewController.swift; path = BVSDKDemo/NotificationPermissionViewController.swift; sourceTree = ""; }; - 8772167A1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_FilterLocation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles_FilterLocation.json; path = ../../../Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_FilterLocation.json; sourceTree = ""; }; - 8772167B1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortHighestRated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles_SortHighestRated.json; path = ../../../Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortHighestRated.json; sourceTree = ""; }; - 8772167C1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortLowestRated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles_SortLowestRated.json; path = ../../../Tests/Tests/resources/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortLowestRated.json; sourceTree = ""; }; - 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 = ""; }; + 8772167A1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_FilterLocation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles_FilterLocation.json; path = ../../../BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_FilterLocation.json; sourceTree = ""; }; + 8772167B1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortHighestRated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles_SortHighestRated.json; path = ../../../BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortHighestRated.json; sourceTree = ""; }; + 8772167C1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortLowestRated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles_SortLowestRated.json; path = ../../../BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortLowestRated.json; sourceTree = ""; }; + 8772167D1D3FDAD4007E5C6C /* conversationsReviewsEnduranceCycles_SortMostHelpful.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles_SortMostHelpful.json; path = ../../../BVSDKTests/MockData/conversations/sortingReviews/conversationsReviewsEnduranceCycles_SortMostHelpful.json; 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 = ""; }; @@ -337,13 +336,13 @@ 878309941CEDF4070097FC48 /* AskAQuestionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AskAQuestionViewController.swift; path = Conversations/AskAQuestionViewController.swift; sourceTree = ""; }; 878309951CEDF4070097FC48 /* AskAQuestionViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = AskAQuestionViewController.xib; path = Conversations/AskAQuestionViewController.xib; sourceTree = ""; }; 879144991CF8BAAE00976220 /* revolution_cycles.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = revolution_cycles.png; sourceTree = ""; }; - 8792AB971D91E6140055B519 /* storeBulkFeedWithStatistics.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = storeBulkFeedWithStatistics.json; path = ../../../Tests/Tests/resources/conversations/stores/storeBulkFeedWithStatistics.json; sourceTree = ""; }; - 87A02B551E018AC30002701B /* testNotificationProductConfig.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = testNotificationProductConfig.json; path = ../../../Tests/Tests/resources/notification_config/testNotificationProductConfig.json; sourceTree = ""; }; - 87A3192A1CF4938E000D3D0F /* conversationsQuestionsIncludeAnswers.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsQuestionsIncludeAnswers.json; path = ../../../Tests/Tests/resources/conversations/conversationsQuestionsIncludeAnswers.json; sourceTree = ""; }; - 87A3192E1CF493AF000D3D0F /* recommendationsResult.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = recommendationsResult.json; path = ../../../Tests/Tests/resources/recommendations/recommendationsResult.json; sourceTree = ""; }; - 87A319301CF493D5000D3D0F /* userProfile1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = userProfile1.json; path = ../../../Tests/Tests/resources/recommendations/userProfile1.json; sourceTree = ""; }; - 87A319321CF493E3000D3D0F /* curationsEnduranceCycles.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = curationsEnduranceCycles.json; path = ../../../Tests/Tests/resources/curations/curationsEnduranceCycles.json; sourceTree = ""; }; - 87A319341CF4942F000D3D0F /* conversationsReviewsEnduranceCycles.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles.json; path = ../../../Tests/Tests/resources/conversations/conversationsReviewsEnduranceCycles.json; sourceTree = ""; }; + 8792AB971D91E6140055B519 /* storeBulkFeedWithStatistics.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = storeBulkFeedWithStatistics.json; path = ../../../BVSDKTests/MockData/conversations/stores/storeBulkFeedWithStatistics.json; sourceTree = ""; }; + 87A02B551E018AC30002701B /* testNotificationProductConfig.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = testNotificationProductConfig.json; path = ../../../BVSDKTests/MockData/conversations/testNotificationProductConfig.json; sourceTree = ""; }; + 87A3192A1CF4938E000D3D0F /* conversationsQuestionsIncludeAnswers.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsQuestionsIncludeAnswers.json; path = ../../../BVSDKTests/MockData/conversations/conversationsQuestionsIncludeAnswers.json; sourceTree = ""; }; + 87A3192E1CF493AF000D3D0F /* recommendationsResult.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = recommendationsResult.json; path = ../../../BVSDKTests/MockData/recommendations/recommendationsResult.json; sourceTree = ""; }; + 87A319301CF493D5000D3D0F /* userProfile1.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = userProfile1.json; path = ../../../BVSDKTests/MockData/recommendations/userProfile1.json; sourceTree = ""; }; + 87A319321CF493E3000D3D0F /* curationsEnduranceCycles.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = curationsEnduranceCycles.json; path = ../../../BVSDKTests/MockData/curations/curationsEnduranceCycles.json; sourceTree = ""; }; + 87A319341CF4942F000D3D0F /* conversationsReviewsEnduranceCycles.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = conversationsReviewsEnduranceCycles.json; path = ../../../BVSDKTests/MockData/conversations/conversationsReviewsEnduranceCycles.json; sourceTree = ""; }; 87B97E941DEF218C005B8C85 /* CartViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CartViewController.swift; path = Cart/CartViewController.swift; sourceTree = ""; }; 87B97E951DEF218C005B8C85 /* CartViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = CartViewController.xib; path = Cart/CartViewController.xib; sourceTree = ""; }; 87B97EB81DEF8CE4005B8C85 /* UIBarButtonItem+Badge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem+Badge.swift"; sourceTree = ""; }; @@ -366,7 +365,7 @@ 87CF9A691DEE3D0A0069D2B9 /* CartProductTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = CartProductTableViewCell.xib; path = Cart/CartProductTableViewCell.xib; sourceTree = ""; }; 87F24D821DAE8147002231D6 /* enduranceCyclesChicago.gpx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = enduranceCyclesChicago.gpx; path = gpx/enduranceCyclesChicago.gpx; sourceTree = ""; }; 87F24D831DAE8147002231D6 /* enduranceCyclesNY.gpx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = enduranceCyclesNY.gpx; path = gpx/enduranceCyclesNY.gpx; sourceTree = ""; }; - 87F5B9351DC38F37004D2297 /* testNotificationConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = testNotificationConfig.json; path = ../../../Tests/Tests/resources/notification_config/testNotificationConfig.json; sourceTree = ""; }; + 87F5B9351DC38F37004D2297 /* testNotificationConfig.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = testNotificationConfig.json; path = ../../../BVSDKTests/MockData/conversations/testNotificationConfig.json; sourceTree = ""; }; BC927E763E958B0ADE062DDB /* Pods-Curations Custom Post Extension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Curations Custom Post Extension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Curations Custom Post Extension/Pods-Curations Custom Post Extension.debug.xcconfig"; sourceTree = ""; }; BF76865CF343C4C4833D6B9B /* Pods_Curations_Custom_Post_Extension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Curations_Custom_Post_Extension.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -646,19 +645,11 @@ 736D4CD41CDED0C40095EE6B /* View Controllers */ = { isa = PBXGroup; children = ( - 736D4CD51CDED0C40095EE6B /* Curations Feed */, 736D4CDE1CDED0C40095EE6B /* Lightbox Demo */, ); path = "View Controllers"; sourceTree = ""; }; - 736D4CD51CDED0C40095EE6B /* Curations Feed */ = { - isa = PBXGroup; - children = ( - ); - path = "Curations Feed"; - sourceTree = ""; - }; 736D4CDE1CDED0C40095EE6B /* Lightbox Demo */ = { isa = PBXGroup; children = ( @@ -916,7 +907,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0810; - LastUpgradeCheck = 0800; + LastUpgradeCheck = 0910; ORGANIZATIONNAME = Bazaarvoice; TargetAttributes = { 73199AE91CDD84E4006CC59D = { @@ -1047,7 +1038,6 @@ 736D4D071CDED0C40095EE6B /* CurationsFeedPageViewController.xib in Resources */, 736D4CFD1CDED0C40095EE6B /* CurationsFeedItemDetailCell.xib in Resources */, 87F24D841DAE8147002231D6 /* enduranceCyclesChicago.gpx in Resources */, - 87A319331CF493E3000D3D0F /* curationsEnduranceCycles.json in Resources */, 15A09C2D1ED5D08400FEC447 /* searchIcon@2x.png in Resources */, 737FA0B81CF8AD2700864B1B /* submitReview.json in Resources */, ); @@ -1378,14 +1368,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -1425,14 +1421,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -1517,7 +1519,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.bazaarvoice.BVSDKDemoApp; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "BVSDKDemo/BVSDKDemo-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 3.0; VERSIONING_SYSTEM = "apple-generic"; }; diff --git a/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/xcshareddata/xcschemes/BVSDKDemo.xcscheme b/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/xcshareddata/xcschemes/BVSDKDemo.xcscheme index 8ce22a6b..2cb15e2b 100644 --- a/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/xcshareddata/xcschemes/BVSDKDemo.xcscheme +++ b/Examples/BVSDKDemo/BVSDKDemo.xcodeproj/xcshareddata/xcschemes/BVSDKDemo.xcscheme @@ -1,6 +1,6 @@ { + + private var contextView: ContextView! + private var normalAnimationRect: UIView! + private var springAnimationRect: UIView! + + let topSpace = CGFloat(40) + + public override init(nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - private var contextView: ContextView! - private var normalAnimationRect: UIView! - private var springAnimationRect: UIView! - - let topSpace = CGFloat(40) + cellSpec = .nibFile(nibName: "BVSDKDemoActionCell", bundle: Bundle(for: BVSDKDemoActionCell.self), height: { _ in 60 }) + settings.animation.scale = nil + settings.animation.present.duration = 0.5 + settings.animation.present.options = UIViewAnimationOptions.curveEaseOut.union(.allowUserInteraction) + settings.animation.present.springVelocity = 0.0 + settings.animation.present.damping = 0.7 + settings.statusBar.style = .default - public override init(nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - - cellSpec = .nibFile(nibName: "BVSDKDemoActionCell", bundle: Bundle(for: BVSDKDemoActionCell.self), height: { _ in 60 }) - settings.animation.scale = nil - settings.animation.present.duration = 0.5 - settings.animation.present.options = UIViewAnimationOptions.curveEaseOut.union(.allowUserInteraction) - settings.animation.present.springVelocity = 0.0 - settings.animation.present.damping = 0.7 - settings.statusBar.style = .default - - onConfigureCellForAction = { cell, action, indexPath in - cell.actionTitleLabel.text = action.data - cell.actionTitleLabel.textColor = UIColor.white - cell.alpha = action.enabled ? 1.0 : 0.5 - } - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + onConfigureCellForAction = { cell, action, indexPath in + cell.actionTitleLabel.text = action.data + cell.actionTitleLabel.textColor = UIColor.white + cell.alpha = action.enabled ? 1.0 : 0.5 } + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func viewDidLoad() { + super.viewDidLoad() + contextView = ContextView(frame: CGRect(x: 0, y: -topSpace, width: collectionView.bounds.width, height: contentHeight + topSpace + 20)) + contextView.autoresizingMask = UIViewAutoresizing.flexibleWidth.union(.flexibleBottomMargin) + collectionView.clipsToBounds = false + collectionView.addSubview(contextView) + collectionView.sendSubview(toBack: contextView) - open override func viewDidLoad() { - super.viewDidLoad() - contextView = ContextView(frame: CGRect(x: 0, y: -topSpace, width: collectionView.bounds.width, height: contentHeight + topSpace + 20)) - contextView.autoresizingMask = UIViewAutoresizing.flexibleWidth.union(.flexibleBottomMargin) - collectionView.clipsToBounds = false - collectionView.addSubview(contextView) - collectionView.sendSubview(toBack: contextView) - - - normalAnimationRect = UIView(frame: CGRect(x: 0, y: view.bounds.height/2, width: 30, height: 30)) - normalAnimationRect.isHidden = true - view.addSubview(normalAnimationRect) - - springAnimationRect = UIView(frame: CGRect(x: 40, y: view.bounds.height/2, width: 30, height: 30)) - springAnimationRect.isHidden = true - view.addSubview(springAnimationRect) - - backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.65) - } - override open func onWillPresentView() { - super.onWillPresentView() - - collectionView.frame.origin.y = contentHeight + (topSpace - contextView.topSpace) - - startAnimation() - let initSpace = CGFloat(45.0) - let initTime = 0.1 - let animationDuration = settings.animation.present.duration - 0.1 - - let options = UIViewAnimationOptions.curveEaseOut.union(.allowUserInteraction) - UIView.animate(withDuration: initTime, delay: settings.animation.present.delay, options: options, animations: { [weak self] in - guard let me = self else { - return - } - - var frame = me.springAnimationRect.frame - frame.origin.y = frame.origin.y - initSpace - me.springAnimationRect.frame = frame - }, completion: { [weak self] finished in - guard let me = self , finished else { - self?.finishAnimation() - return - } - - UIView.animate(withDuration: animationDuration - initTime, delay: 0, options: options, animations: { [weak self] in - guard let me = self else { - return - } - - var frame = me.springAnimationRect.frame - frame.origin.y -= (me.contentHeight - initSpace) - me.springAnimationRect.frame = frame - }, completion: { (finish) -> Void in - me.finishAnimation() - }) - }) - - - UIView.animate(withDuration: animationDuration - initTime, delay: settings.animation.present.delay + initTime, options: options, animations: { [weak self] in - guard let me = self else { - return - } - - var frame = me.normalAnimationRect.frame - frame.origin.y -= me.contentHeight - me.normalAnimationRect.frame = frame - }, completion:nil) - } + normalAnimationRect = UIView(frame: CGRect(x: 0, y: view.bounds.height/2, width: 30, height: 30)) + normalAnimationRect.isHidden = true + view.addSubview(normalAnimationRect) + springAnimationRect = UIView(frame: CGRect(x: 40, y: view.bounds.height/2, width: 30, height: 30)) + springAnimationRect.isHidden = true + view.addSubview(springAnimationRect) - override open func dismissView(_ presentedView: UIView, presentingView: UIView, animationDuration: Double, completion: ((_ completed: Bool) -> Void)?) { - finishAnimation() - finishAnimation() - - let animationSettings = settings.animation.dismiss - UIView.animate(withDuration: animationDuration, - delay: animationSettings.delay, - usingSpringWithDamping: animationSettings.damping, - initialSpringVelocity: animationSettings.springVelocity, - options: animationSettings.options, - animations: { [weak self] in - self?.backgroundView.alpha = 0.0 - }, - completion:nil) - - gravityBehavior.action = { [weak self] in - if let me = self { - let progress = min(1.0, me.collectionView.frame.origin.y / (me.contentHeight + (me.topSpace - me.contextView.topSpace))) - let pixels = min(20, progress * 150.0) - me.contextView.diff = -pixels - me.contextView.setNeedsDisplay() - - if (self?.collectionView.frame.origin.y)! > (self?.view.bounds.size.height)! { - self?.animator.removeAllBehaviors() - completion?(true) - } - } - } - animator.addBehavior(gravityBehavior) - } + backgroundView.backgroundColor = UIColor.black.withAlphaComponent(0.65) + } + + override open func onWillPresentView() { + super.onWillPresentView() + + collectionView.frame.origin.y = contentHeight + (topSpace - contextView.topSpace) - //MARK : Private Helpers + startAnimation() + let initSpace = CGFloat(45.0) + let initTime = 0.1 + let animationDuration = settings.animation.present.duration - 0.1 - private var diff = CGFloat(0) - private var displayLink: CADisplayLink! - private var animationCount = 0 + let options = UIViewAnimationOptions.curveEaseOut.union(.allowUserInteraction) + UIView.animate(withDuration: initTime, delay: settings.animation.present.delay, options: options, animations: { [weak self] in + guard let me = self else { + return + } + + var frame = me.springAnimationRect.frame + frame.origin.y = frame.origin.y - initSpace + me.springAnimationRect.frame = frame + }, completion: { [weak self] finished in + guard let me = self , finished else { + self?.finishAnimation() + return + } + + UIView.animate(withDuration: animationDuration - initTime, delay: 0, options: options, animations: { [weak self] in + guard let me = self else { + return + } + + var frame = me.springAnimationRect.frame + frame.origin.y -= (me.contentHeight - initSpace) + me.springAnimationRect.frame = frame + }, completion: { (finish) -> Void in + me.finishAnimation() + }) + }) - private lazy var animator: UIDynamicAnimator = { [unowned self] in - let animator = UIDynamicAnimator(referenceView: self.view) - return animator - }() - private lazy var gravityBehavior: UIGravityBehavior = { [unowned self] in - let gravityBehavior = UIGravityBehavior(items: [self.collectionView]) - gravityBehavior.magnitude = 2.0 - return gravityBehavior - }() + UIView.animate(withDuration: animationDuration - initTime, delay: settings.animation.present.delay + initTime, options: options, animations: { [weak self] in + guard let me = self else { + return + } + + var frame = me.normalAnimationRect.frame + frame.origin.y -= me.contentHeight + me.normalAnimationRect.frame = frame + }, completion:nil) + } + + + override open func dismissView(_ presentedView: UIView, presentingView: UIView, animationDuration: Double, completion: ((_ completed: Bool) -> Void)?) { + finishAnimation() + finishAnimation() + let animationSettings = settings.animation.dismiss + UIView.animate(withDuration: animationDuration, + delay: animationSettings.delay, + usingSpringWithDamping: animationSettings.damping, + initialSpringVelocity: animationSettings.springVelocity, + options: animationSettings.options, + animations: { [weak self] in + self?.backgroundView.alpha = 0.0 + }, + completion:nil) - @objc private func update(_ displayLink: CADisplayLink) { - - let normalRectLayer = normalAnimationRect.layer.presentation() - let springRectLayer = springAnimationRect.layer.presentation() + gravityBehavior.action = { [weak self] in + if let me = self { + let progress = min(1.0, me.collectionView.frame.origin.y / (me.contentHeight + (me.topSpace - me.contextView.topSpace))) + let pixels = min(20, progress * 150.0) + me.contextView.diff = -pixels + me.contextView.setNeedsDisplay() - let normalRectFrame = (normalRectLayer!.value(forKey: "frame")! as AnyObject).cgRectValue - let springRectFrame = (springRectLayer!.value(forKey: "frame")! as AnyObject).cgRectValue - contextView.diff = (normalRectFrame?.origin.y)! - (springRectFrame?.origin.y)! - contextView.setNeedsDisplay() + if (self?.collectionView.frame.origin.y)! > (self?.view.bounds.size.height)! { + self?.animator.removeAllBehaviors() + completion?(true) + } + } } + animator.addBehavior(gravityBehavior) + } + + //MARK : Private Helpers + + private var diff = CGFloat(0) + private var displayLink: CADisplayLink! + private var animationCount = 0 + + private lazy var animator: UIDynamicAnimator = { [unowned self] in + let animator = UIDynamicAnimator(referenceView: self.view) + return animator + }() + + private lazy var gravityBehavior: UIGravityBehavior = { [unowned self] in + let gravityBehavior = UIGravityBehavior(items: [self.collectionView]) + gravityBehavior.magnitude = 2.0 + return gravityBehavior + }() + + + @objc private func update(_ displayLink: CADisplayLink) { - private func startAnimation() { - if displayLink == nil { - self.displayLink = CADisplayLink(target: self, selector: #selector(BVSDKDemoActionController.update(_:))) - self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode) - } - animationCount += 1 + let normalRectLayer = normalAnimationRect.layer.presentation() + let springRectLayer = springAnimationRect.layer.presentation() + + let normalRectFrame = (normalRectLayer!.value(forKey: "frame")! as AnyObject).cgRectValue + let springRectFrame = (springRectLayer!.value(forKey: "frame")! as AnyObject).cgRectValue + contextView.diff = (normalRectFrame?.origin.y)! - (springRectFrame?.origin.y)! + contextView.setNeedsDisplay() + } + + private func startAnimation() { + if displayLink == nil { + self.displayLink = CADisplayLink(target: self, selector: #selector(BVSDKDemoActionController.update(_:))) + self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode) + } + animationCount += 1 + } + + private func finishAnimation() { + animationCount -= 1 + if animationCount == 0 { + displayLink.invalidate() + displayLink = nil } + } + + + private class ContextView: UIView { + let topSpace = CGFloat(25) + var diff = CGFloat(0) - private func finishAnimation() { - animationCount -= 1 - if animationCount == 0 { - displayLink.invalidate() - displayLink = nil - } + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = UIColor.clear } + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } - private class ContextView: UIView { - let topSpace = CGFloat(25) - var diff = CGFloat(0) - - override init(frame: CGRect) { - super.init(frame: frame) - backgroundColor = UIColor.clear - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func draw(_ rect: CGRect) { - let path = UIBezierPath() - - path.move(to: CGPoint(x: 0, y: frame.height)) - path.addLine(to: CGPoint(x: frame.width, y: frame.height)) - path.addLine(to: CGPoint(x: frame.width, y: topSpace)) - path.addQuadCurve(to: CGPoint(x: 0, y: topSpace), controlPoint: CGPoint(x: frame.width/2, y: topSpace - diff)) - path.close() - - if let context = UIGraphicsGetCurrentContext(){ - context.addPath(path.cgPath) - UIColor.bazaarvoiceNavy().set() - context.fillPath() - } - } + override func draw(_ rect: CGRect) { + let path = UIBezierPath() + + path.move(to: CGPoint(x: 0, y: frame.height)) + path.addLine(to: CGPoint(x: frame.width, y: frame.height)) + path.addLine(to: CGPoint(x: frame.width, y: topSpace)) + path.addQuadCurve(to: CGPoint(x: 0, y: topSpace), controlPoint: CGPoint(x: frame.width/2, y: topSpace - diff)) + path.close() + + if let context = UIGraphicsGetCurrentContext(){ + context.addPath(path.cgPath) + UIColor.bazaarvoiceNavy().set() + context.fillPath() + } } - + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/CallToActionCell.swift b/Examples/BVSDKDemo/BVSDKDemo/CallToActionCell.swift index 30636598..34407739 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/CallToActionCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/CallToActionCell.swift @@ -9,33 +9,33 @@ import UIKit import FontAwesomeKit class CallToActionCell: UITableViewCell { - - - @IBOutlet weak var button : UIButton! - @IBOutlet weak var rightIcon : UIImageView! - @IBOutlet weak var leftIcon : UIImageView! - + + + @IBOutlet weak var button : UIButton! + @IBOutlet weak var rightIcon : UIImageView! + @IBOutlet weak var leftIcon : UIImageView! + + + func setCustomLeftIcon(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!)) { + leftIcon.image = getIconImage(icon) + } + + func setCustomRightIcon(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!)) { + rightIcon.image = getIconImage(icon) + } + + func getIconImage(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!)) -> UIImage { - func setCustomLeftIcon(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!)) { - leftIcon.image = getIconImage(icon) - } + let size = CGFloat(20) - func setCustomRightIcon(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!)) { - rightIcon.image = getIconImage(icon) - } + let newIcon = icon(size) + newIcon?.addAttribute( + NSForegroundColorAttributeName, + value: UIColor.lightGray.withAlphaComponent(0.5) + ) - func getIconImage(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!)) -> UIImage { - - let size = CGFloat(20) - - let newIcon = icon(size) - newIcon?.addAttribute( - NSForegroundColorAttributeName, - value: UIColor.lightGray.withAlphaComponent(0.5) - ) - - return newIcon!.image(with: CGSize(width: size, height: size)) - - } + return newIcon!.image(with: CGSize(width: size, height: size)) + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Cart/CartManager.swift b/Examples/BVSDKDemo/BVSDKDemo/Cart/CartManager.swift index dcd06c22..0632adbc 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Cart/CartManager.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Cart/CartManager.swift @@ -9,44 +9,44 @@ import UIKit import BVSDK class CartManager { - - private var productsArray: [BVProduct] = [] - public var didPurchase = false - - private init() {} // Ensure only used as a singleton! - - static let sharedInstance : CartManager = { - let instance = CartManager() - return instance - }() - - func addProduct(product: BVProduct) { - productsArray.append(product) - } - - func removeProduct(product: BVProduct) -> Bool { - if let index = productsArray.index(of: product) { - productsArray.remove(at: index) - return true - } - - return false - } - - func productAtIndex(_ index: Int) -> BVProduct { - return productsArray[index] - } - - func numberOfItemsInCart() -> Int { - return productsArray.count - } - - func clearCart() { - productsArray.removeAll(keepingCapacity: false) - } - - func isProductInCart(product: BVProduct) -> Bool { - return productsArray.contains(product) + + private var productsArray: [BVProduct] = [] + public var didPurchase = false + + private init() {} // Ensure only used as a singleton! + + static let sharedInstance : CartManager = { + let instance = CartManager() + return instance + }() + + func addProduct(product: BVProduct) { + productsArray.append(product) + } + + func removeProduct(product: BVProduct) -> Bool { + if let index = productsArray.index(of: product) { + productsArray.remove(at: index) + return true } + return false + } + + func productAtIndex(_ index: Int) -> BVProduct { + return productsArray[index] + } + + func numberOfItemsInCart() -> Int { + return productsArray.count + } + + func clearCart() { + productsArray.removeAll(keepingCapacity: false) + } + + func isProductInCart(product: BVProduct) -> Bool { + return productsArray.contains(product) + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Cart/CartProductTableViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/Cart/CartProductTableViewCell.swift index a1950a26..794398ac 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Cart/CartProductTableViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Cart/CartProductTableViewCell.swift @@ -9,30 +9,30 @@ import UIKit import BVSDK class CartProductTableViewCell: UITableViewCell { - - @IBOutlet weak var productImageView: UIImageView! - @IBOutlet weak var productTitle: UILabel! - @IBOutlet weak var productPrice: UILabel! - - var product : BVProduct? { - - didSet { - if let _ = product { - productTitle.text = product?.name - productImageView.sd_setImage(with: NSURL(string: (product?.imageUrl!)!) as URL!) - productPrice.text = "$0.00" - } - } - } + + @IBOutlet weak var productImageView: UIImageView! + @IBOutlet weak var productTitle: UILabel! + @IBOutlet weak var productPrice: UILabel! + + var product : BVProduct? { - override func awakeFromNib() { - super.awakeFromNib() - } - - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - // Configure the view for the selected state + didSet { + if let _ = product { + productTitle.text = product?.name + productImageView.sd_setImage(with: NSURL(string: (product?.imageUrl!)!) as URL!) + productPrice.text = "$0.00" + } } + } + + override func awakeFromNib() { + super.awakeFromNib() + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + // Configure the view for the selected state + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Cart/CartViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Cart/CartViewController.swift index 50260e33..0182e30e 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Cart/CartViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Cart/CartViewController.swift @@ -10,173 +10,173 @@ import UIKit import BVSDK class CartViewController: UIViewController, UITableViewDataSource { - - @IBOutlet weak var tableView: UITableView! - @IBOutlet weak var emptyView: UIView! - @IBOutlet weak var checkoutButton: UIButton! - - /// NumberFormatter to get the price of a product formatted right - lazy var productPriceFormatter: NumberFormatter = { - let productPriceFormatter = NumberFormatter() - productPriceFormatter.numberStyle = .currency - productPriceFormatter.locale = NSLocale.current - return productPriceFormatter - }() - - override func viewDidLoad() { - super.viewDidLoad() - title = "Cart" - - self.styleCheckoutButtonButton() - - tableView.estimatedRowHeight = 100 - tableView.rowHeight = UITableViewAutomaticDimension - tableView.tableFooterView = UIView(frame: CGRect.zero) - - let nibCartProductCell = UINib(nibName: "CartProductTableViewCell", bundle: nil) - tableView.register(nibCartProductCell, forCellReuseIdentifier: "CartProductTableViewCell") - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - checkEmptyStateOfCart() - tableView.reloadData() - } - - private func styleCheckoutButtonButton(){ - self.checkoutButton.layer.cornerRadius = 4 - self.checkoutButton.layer.backgroundColor = UIColor.bazaarvoiceNavy().cgColor - self.checkoutButton.setTitleColor(UIColor.white, for: .normal) - } - - @IBAction func clearAllButtonPressed() { - let alertView = UIAlertController(title: "Clear all?", message: "Do you really want to clear all items from your cart?", preferredStyle: .alert) - alertView.addAction(UIAlertAction(title: "No", style: .default, handler: nil)) - alertView.addAction(UIAlertAction(title: "Clear", style: .destructive, handler: { (alertAction) -> Void in - self.clearCart() - })) - present(alertView, animated: true, completion: nil) - } - - - @IBAction func checkoutButtonPressed(_ sender: AnyObject) { - - // Fire BVPixel - - CartManager.sharedInstance.didPurchase = true - - var transactionItems = [BVTransactionItem]() - // will be used to provide a mock reponse for BVPINRequest.getPendingPins - var products = [BVProduct]() - for index in 0 ..< CartManager.sharedInstance.numberOfItemsInCart() { - - let product = CartManager.sharedInstance.productAtIndex(index) - products.append(product) - - let currTransaction = BVTransactionItem(sku:product.identifier, + + @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var emptyView: UIView! + @IBOutlet weak var checkoutButton: UIButton! + + /// NumberFormatter to get the price of a product formatted right + lazy var productPriceFormatter: NumberFormatter = { + let productPriceFormatter = NumberFormatter() + productPriceFormatter.numberStyle = .currency + productPriceFormatter.locale = NSLocale.current + return productPriceFormatter + }() + + override func viewDidLoad() { + super.viewDidLoad() + title = "Cart" + + self.styleCheckoutButtonButton() + + tableView.estimatedRowHeight = 100 + tableView.rowHeight = UITableViewAutomaticDimension + tableView.tableFooterView = UIView(frame: CGRect.zero) + + let nibCartProductCell = UINib(nibName: "CartProductTableViewCell", bundle: nil) + tableView.register(nibCartProductCell, forCellReuseIdentifier: "CartProductTableViewCell") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + checkEmptyStateOfCart() + tableView.reloadData() + } + + private func styleCheckoutButtonButton(){ + self.checkoutButton.layer.cornerRadius = 4 + self.checkoutButton.layer.backgroundColor = UIColor.bazaarvoiceNavy().cgColor + self.checkoutButton.setTitleColor(UIColor.white, for: .normal) + } + + @IBAction func clearAllButtonPressed() { + let alertView = UIAlertController(title: "Clear all?", message: "Do you really want to clear all items from your cart?", preferredStyle: .alert) + alertView.addAction(UIAlertAction(title: "No", style: .default, handler: nil)) + alertView.addAction(UIAlertAction(title: "Clear", style: .destructive, handler: { (alertAction) -> Void in + self.clearCart() + })) + present(alertView, animated: true, completion: nil) + } + + + @IBAction func checkoutButtonPressed(_ sender: AnyObject) { + + // Fire BVPixel + + CartManager.sharedInstance.didPurchase = true + + var transactionItems = [BVTransactionItem]() + // will be used to provide a mock reponse for BVPINRequest.getPendingPins + var products = [BVProduct]() + for index in 0 ..< CartManager.sharedInstance.numberOfItemsInCart() { + + let product = CartManager.sharedInstance.productAtIndex(index) + products.append(product) + + let currTransaction = BVTransactionItem(sku:product.identifier, name: product.name, category: product.categoryId, price: 0.01, quantity: 1, imageUrl: product.imageUrl) - - - transactionItems.append(currTransaction) - } - - /* - Construct a BVTransaction - OrderId, orderTotal, and orderItems are required. - You may include any other key value params you'd like. - BVTransaction also has convenience setters for commonly used params. - */ - - let transaction = BVTransactionEvent(orderId:"123456", - orderTotal: 0.00, - orderItems: transactionItems, - andOtherParams: ["state":"TX","email":"some.one@domain.com"]) - - transaction.shipping = 0.00 - transaction.tax = 0.00 - - // Use the BVPixel to track the Conversion Transaction Event. - BVPixel.trackEvent(transaction) - - //BVAnalyticsManager.shared().flushQueue() // Send the event immediately (just for demo purposes) - - // Clear out the cart and let user know the demo transaction was complete. - - clearCart() - - createMockPins(products) - - _ = SweetAlert().showAlert("Success!", subTitle: "Thank you for your order!", style: .success) - _ = self.navigationController?.popViewController(animated: true) - - } - - - func clearCart() { - CartManager.sharedInstance.clearCart() - tableView.reloadData() - - setEmptyViewVisible(visible: true) - } - - private func createMockPins(_ products: [BVProduct]) { - MockDataManager.sharedInstance.generateMockPinReponse(fromProducts: products) - } - - func setEmptyViewVisible(visible: Bool) { - emptyView.isHidden = !visible - if visible { - //clearButton.isEnabled = false - self.view.bringSubview(toFront: emptyView) - } else { - //clearButton.isEnabled = true - self.view.sendSubview(toBack: emptyView) - } + + + transactionItems.append(currTransaction) } - func checkEmptyStateOfCart() { - setEmptyViewVisible(visible: CartManager.sharedInstance.numberOfItemsInCart() == 0) + /* + Construct a BVTransaction + OrderId, orderTotal, and orderItems are required. + You may include any other key value params you'd like. + BVTransaction also has convenience setters for commonly used params. + */ + + let transaction = BVTransactionEvent(orderId:"123456", + orderTotal: 0.00, + orderItems: transactionItems, + andOtherParams: ["state":"TX","email":"some.one@domain.com"]) + + transaction.shipping = 0.00 + transaction.tax = 0.00 + + // Use the BVPixel to track the Conversion Transaction Event. + BVPixel.trackEvent(transaction) + + //BVAnalyticsManager.shared().flushQueue() // Send the event immediately (just for demo purposes) + + // Clear out the cart and let user know the demo transaction was complete. + + clearCart() + + createMockPins(products) + + _ = SweetAlert().showAlert("Success!", subTitle: "Thank you for your order!", style: .success) + _ = self.navigationController?.popViewController(animated: true) + + } + + + func clearCart() { + CartManager.sharedInstance.clearCart() + tableView.reloadData() + + setEmptyViewVisible(visible: true) + } + + private func createMockPins(_ products: [BVProduct]) { + MockDataManager.sharedInstance.generateMockPinReponse(fromProducts: products) + } + + func setEmptyViewVisible(visible: Bool) { + emptyView.isHidden = !visible + if visible { + //clearButton.isEnabled = false + self.view.bringSubview(toFront: emptyView) + } else { + //clearButton.isEnabled = true + self.view.sendSubview(toBack: emptyView) } - - // MARK: UITableViewDataSource - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return CartManager.sharedInstance.numberOfItemsInCart() + } + + func checkEmptyStateOfCart() { + setEmptyViewVisible(visible: CartManager.sharedInstance.numberOfItemsInCart() == 0) + } + + // MARK: UITableViewDataSource + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return CartManager.sharedInstance.numberOfItemsInCart() + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let productCell = tableView.dequeueReusableCell(withIdentifier: "CartProductTableViewCell") as! CartProductTableViewCell + + let product = CartManager.sharedInstance.productAtIndex(indexPath.row) + + productCell.product = product + + return productCell + } + + + func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + return true + } + + func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + + let product = CartManager.sharedInstance.productAtIndex(indexPath.row) + let successful = CartManager.sharedInstance.removeProduct(product: product) + + if successful == true { + tableView.beginUpdates() + tableView.deleteRows(at: [indexPath as IndexPath], with: .right) + tableView.endUpdates() + } + + checkEmptyStateOfCart() } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let productCell = tableView.dequeueReusableCell(withIdentifier: "CartProductTableViewCell") as! CartProductTableViewCell - - let product = CartManager.sharedInstance.productAtIndex(indexPath.row) - - productCell.product = product - - return productCell - } - - - func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - return true - } - - func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete { - - let product = CartManager.sharedInstance.productAtIndex(indexPath.row) - let successful = CartManager.sharedInstance.removeProduct(product: product) - - if successful == true { - tableView.beginUpdates() - tableView.deleteRows(at: [indexPath as IndexPath], with: .right) - tableView.endUpdates() - } - - checkEmptyStateOfCart() - } - } - + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/CircleSearchView.swift b/Examples/BVSDKDemo/BVSDKDemo/CircleSearchView.swift index fcba6889..e1170a44 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/CircleSearchView.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/CircleSearchView.swift @@ -9,475 +9,475 @@ import UIKit public class CircleSearchResult { - let title: String - let result: ResultType - - internal init(title: String, result: ResultType) { - self.title = title - self.result = result - } + let title: String + let result: ResultType + + internal init(title: String, result: ResultType) { + self.title = title + self.result = result + } } fileprivate enum ViewState { - case iconOnly, - searchBar, - fullScreen + case iconOnly, + searchBar, + fullScreen } public class CircleSearchView: UIView, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource { - - public typealias SearchCompletionHandler = (_ results: [CircleSearchResult]) -> Void - public typealias SearchChangedHandler = (_ circleSearchView: CircleSearchView, _ searchText: String, _ emptyResults: inout [CircleSearchResult], _ completion: @escaping SearchCompletionHandler) -> Void - public typealias SearchResultTappedHandler = (_ circleSearchView: CircleSearchView, _ result: ResultType) -> Void - - public var minimumSearchLength = 3 - public var minKeyboardRestTimeToSearch: TimeInterval = 0.5 - public let searchTextField:UITextField - public let searchButton:UIButton - public let cancelButton:UIButton - public let searchTableView:UITableView - - private var viewState = ViewState.iconOnly - private var searchTimer: Timer? - private let shortDuration: TimeInterval = 0.06 - private let standardDuration: TimeInterval = 0.12 - private let searchBarHeight: CGFloat = 35.0 - private var searchBarInsetY: CGFloat! - private weak var embeddingScrollView: UIScrollView! - private var searchFieldConstraintPack: ViewStateConstraintPack! - private var searchTableConstraintPack: ViewStateConstraintPack! - private var searchButtonConstraintPack: ViewStateConstraintPack! - private var cancelButtonConstraintPack: ViewStateConstraintPack! - private var centerYConstraint: NSLayoutConstraint! - private var searchResults = [CircleSearchResult]() - private let cellReuseId = "searchResultReuseId" - private let searchChangedHandler: SearchChangedHandler - private let searchResultTappedHandler: SearchResultTappedHandler - - public init(scrollView: UIScrollView, changeHandler: @escaping SearchChangedHandler, tappedHandler: @escaping SearchResultTappedHandler) { - - searchTextField = UITextField() - searchTableView = UITableView() - searchButton = UIButton(type: .custom) - cancelButton = UIButton(type: .custom) - searchChangedHandler = changeHandler - searchResultTappedHandler = tappedHandler - - super.init(frame: scrollView.frame) - searchBarInsetY = searchBarHeight + 16 - - // applyDebugUI() - - setupSelf(scrollView: scrollView) - setupCancelButton() - setupSearchButton() - setupSearchBar() - setupSearchTable() - embeddingScrollView = scrollView - } - - public override func removeFromSuperview() { - superview!.removeObserver(self, forKeyPath: "contentOffset") - super.removeFromSuperview() - } - - func cancelPressed(btn: UIButton) { - searchTextField.resignFirstResponder() - embeddingScrollView.isScrollEnabled = true - updateState(from: embeddingScrollView.contentOffset, force: true) - } - - func searchPressed(btn: UIButton) { - animateFullScreen() - searchTextField.becomeFirstResponder() - } - - private func setupSelf(scrollView: UIScrollView) { - scrollView.addSubview(self) - scrollView.contentInset = UIEdgeInsets(top: searchBarInsetY, left: 0, bottom: 0, right: 0) - self.translatesAutoresizingMaskIntoConstraints = false - scrollView.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil) - - let centerX = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: scrollView, attribute: .centerX, multiplier: 1, constant: 0) - centerYConstraint = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: scrollView, attribute: .centerY, multiplier: 1, constant: searchBarInsetY * -1) - let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: scrollView, attribute: .height, multiplier: 1, constant: 0) - let width = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: scrollView, attribute: .width, multiplier: 1, constant: 0) - - NSLayoutConstraint.activate([centerX, centerYConstraint, height, width]) - } - - private func setupSearchButton() { - self.addSubview(searchButton) - searchButton.translatesAutoresizingMaskIntoConstraints = false - let iconImage = UIImage(named: "searchIcon") - searchButton.setImage(iconImage?.withRenderingMode(.alwaysTemplate), for: .normal) - searchButton.addTarget(self, action: #selector(CircleSearchView.searchPressed(btn:)), for: .touchUpInside) - searchButton.layer.borderColor = UIColor.rgbColor(r: 210, g: 210, b: 210).cgColor - searchButton.layer.borderWidth = 1 - searchButton.backgroundColor = UIColor.white - - let leading = NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal, toItem: searchButton, attribute: .left, multiplier: 1, constant: -8) - let height = NSLayoutConstraint(item: searchButton, attribute: .height, relatedBy: .equal, toItem: searchTextField, attribute: .height, multiplier: 1, constant: 0) - let width = NSLayoutConstraint(item: searchButton, attribute: .width, relatedBy: .equal, toItem: searchButton, attribute: .height, multiplier: 1, constant: 0) - let top = NSLayoutConstraint(item: searchTextField, attribute: .top, relatedBy: .equal, toItem: searchButton, attribute: .top, multiplier: 1, constant: 0) - - let fullWidth = NSLayoutConstraint(item: searchButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 0) - fullWidth.isActive = false - - let iconStateSearch = [leading, height, width, top] - let searchBarStateSearch = [leading, height, width, top] - let fullScreenStateSearch = [leading, height, fullWidth, top] - - searchButtonConstraintPack = ViewStateConstraintPack(iconOnlyStateConstraints: iconStateSearch, - searchBarStateConstraints: searchBarStateSearch, - fullScreenStateConstraints: fullScreenStateSearch) - } - - private func setupCancelButton() { - self.addSubview(cancelButton) - cancelButton.setTitle("Cancel", for: .normal) - cancelButton.addTarget(self, action: #selector(CircleSearchView.cancelPressed(btn:)), for: .touchUpInside) - cancelButton.translatesAutoresizingMaskIntoConstraints = false - cancelButton.setTitleColor(UIColor.black, for: .normal) - cancelButton.layer.borderColor = UIColor.rgbColor(r: 210, g: 210, b: 210).cgColor - cancelButton.layer.borderWidth = 1 - cancelButton.backgroundColor = UIColor.white - - let trailing = NSLayoutConstraint(item: self, attribute: .right, relatedBy: .equal, toItem: cancelButton, attribute: .right, multiplier: 1, constant: 8) - let height = NSLayoutConstraint(item: cancelButton, attribute: .height, relatedBy: .equal, toItem: searchTextField, attribute: .height, multiplier: 1, constant: 0) - let width = NSLayoutConstraint(item: cancelButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 0) - let top = NSLayoutConstraint(item: searchTextField, attribute: .top, relatedBy: .equal, toItem: cancelButton, attribute: .top, multiplier: 1, constant: 0) - - let fullWidth = NSLayoutConstraint(item: cancelButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 65) - fullWidth.isActive = false - - let iconStateSearch = [trailing, height, width, top] - let searchBarStateSearch = [trailing, height, width, top] - let fullScreenStateSearch = [trailing, height, fullWidth, top] - - cancelButtonConstraintPack = ViewStateConstraintPack(iconOnlyStateConstraints: iconStateSearch, - searchBarStateConstraints: searchBarStateSearch, - fullScreenStateConstraints: fullScreenStateSearch) - - } - - private func setupSearchBar() { - self.addSubview(searchTextField) - searchTextField.delegate = self - searchTextField.clearButtonMode = .whileEditing - searchTextField.translatesAutoresizingMaskIntoConstraints = false - searchTextField.backgroundColor = UIColor.white - searchTextField.layer.borderColor = UIColor.rgbColor(r: 210, g: 210, b: 210).cgColor - searchTextField.layer.borderWidth = 1 - let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 4, height: searchBarHeight)) - searchTextField.leftView = paddingView - searchTextField.leftViewMode = .always - searchTextField.autocorrectionType = .no - searchTextField.returnKeyType = .search - - let heightConstraintSearch = NSLayoutConstraint(item: searchTextField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: searchBarHeight) - let leadingConstraintSearch = NSLayoutConstraint(item: searchButton, attribute: .right, relatedBy: .equal, toItem: searchTextField, attribute: .left, multiplier: 1, constant: 0) - let trailingConstraintSearch = NSLayoutConstraint(item: cancelButton, attribute: .left, relatedBy: .equal, toItem: searchTextField, attribute: .right, multiplier: 1, constant: 0) - let topConstraintSearch = NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: searchTextField, attribute: .top , multiplier: 1, constant: -8) - - - let iconSizeWidth = NSLayoutConstraint(item: searchTextField, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 0) - iconSizeWidth.isActive = false - - let topConstraintFull = NSLayoutConstraint(item: self, attribute: .topMargin, relatedBy: .equal, toItem: searchTextField, attribute: .topMargin, multiplier: 1, constant: -8) - topConstraintFull.isActive = false - - searchTextField.addConstraint(heightConstraintSearch) - searchTextField.addConstraint(iconSizeWidth) - - let iconStateSearch = [heightConstraintSearch, leadingConstraintSearch, topConstraintSearch, iconSizeWidth] - let searchBarStateSearch = [heightConstraintSearch, leadingConstraintSearch, trailingConstraintSearch, topConstraintSearch] - let fullScreenStateSearch = [heightConstraintSearch, leadingConstraintSearch, trailingConstraintSearch, topConstraintFull] - - searchFieldConstraintPack = ViewStateConstraintPack(iconOnlyStateConstraints: iconStateSearch, - searchBarStateConstraints: searchBarStateSearch, - fullScreenStateConstraints: fullScreenStateSearch) - - } - - private func setupSearchTable() { - self.addSubview(searchTableView) - searchTableView.delegate = self - searchTableView.dataSource = self - searchTableView.translatesAutoresizingMaskIntoConstraints = false - searchTableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseId) - - let height = NSLayoutConstraint(item: searchTableView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 0) - let width = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: searchTableView, attribute: .width, multiplier: 1, constant: 0) - let top = NSLayoutConstraint(item: searchTextField, attribute: .bottom, relatedBy: .equal, toItem: searchTableView, attribute: .top, multiplier: 1, constant: -8) - let leading = NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal, toItem: searchTableView, attribute: .left, multiplier: 1, constant: 0) - searchTableView.addConstraint(height) - - let fullScreenHeight = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: searchTableView, attribute: .height, multiplier: 1, constant: 0) - fullScreenHeight.isActive = false - let iconState = [height, width, top, leading] - let searchBarState = [height, width, top, leading] - let fullScreenState = [fullScreenHeight, width, top, leading] - - searchTableConstraintPack = ViewStateConstraintPack(iconOnlyStateConstraints: iconState, - searchBarStateConstraints: searchBarState, - fullScreenStateConstraints: fullScreenState) - } - - private func applyDebugUI() { - backgroundColor = UIColor.rgbaColor(r: 0, g: 0, b: 0, a: 125) - searchTextField.backgroundColor = UIColor.green - searchButton.backgroundColor = UIColor.blue - cancelButton.backgroundColor = UIColor.red - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func getTransitionDuration(startState: ViewState, endState: ViewState) -> TimeInterval { - if startState == .iconOnly && endState == .searchBar { - return shortDuration - } - - return standardDuration - } - - private func animateFullScreen() { - embeddingScrollView.isScrollEnabled = false//(searchTableView.isHidden || searchTableView.alpha == 0) - superview?.bringSubview(toFront: self) - animateTransition(to: .fullScreen) - } - - public func animateDismissFullScreen() { - self.cancelPressed(btn: cancelButton) - } - - private func animateTransition(to state: ViewState) { - - if state == .fullScreen { - Timer.scheduledTimer(timeInterval: 0.75, target: self, selector: #selector(updateEmbeddingScrollability), userInfo: nil, repeats: false) - } - - if viewState == state { - return - } - - if state == .iconOnly { - searchTextField.leftViewMode = .never - }else { - searchTextField.leftViewMode = .always - } - - viewState = state - - let duration = getTransitionDuration(startState: viewState, endState: state) - - toggleConstraints(for: state) - - self.viewLayerAnimations(for: state, duration: duration) - - UIView.animate(withDuration: duration) { - self.layoutIfNeeded() - } - } - - @objc - private func updateEmbeddingScrollability() { - self.embeddingScrollView.isScrollEnabled = (self.searchTableView.isHidden || self.searchTableView.alpha == 0) - } - - private func toggleConstraints(for state: ViewState) { - - let deactivate = searchFieldConstraintPack.constraintsToDeactivate() + - searchTableConstraintPack.constraintsToDeactivate() + - searchButtonConstraintPack.constraintsToDeactivate() + - cancelButtonConstraintPack.constraintsToDeactivate() - - NSLayoutConstraint.deactivate(deactivate) - - let activate = searchFieldConstraintPack.constraintsToActivate(state) + - searchTableConstraintPack.constraintsToActivate(state) + - searchButtonConstraintPack.constraintsToActivate(state) + - cancelButtonConstraintPack.constraintsToActivate(state) - - NSLayoutConstraint.activate(activate) - } - - private func viewLayerAnimations(for state: ViewState, duration: TimeInterval) { - switch state { - case .searchBar: - searchButton.addCornerRadiusAnimation(from: searchButton.layer.cornerRadius, to: 0.0, duration: duration) - break - case .iconOnly: - searchButton.addCornerRadiusAnimation(from: searchButton.layer.cornerRadius, to: searchBarHeight / 2, duration: duration) - break - case .fullScreen: - break - } - } - - public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { - - animateFullScreen() - return true - } - - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if searchTimer != nil - { - searchTimer?.invalidate() - searchTimer = nil - } - - let result = textField.text?.replacingCharacters(in: (textField.text?.rangeFromNSRange(range)!)!, with: string) - if result!.characters.count >= minimumSearchLength - { - searchTimer = Timer.scheduledTimer(timeInterval: minKeyboardRestTimeToSearch, target:self, selector: #selector(CircleSearchView.notifyDelegateOfSearch(_:)), userInfo: result, repeats: false) - } - return true - } - - internal func notifyDelegateOfSearch(_ timer:Timer) + + public typealias SearchCompletionHandler = (_ results: [CircleSearchResult]) -> Void + public typealias SearchChangedHandler = (_ circleSearchView: CircleSearchView, _ searchText: String, _ emptyResults: inout [CircleSearchResult], _ completion: @escaping SearchCompletionHandler) -> Void + public typealias SearchResultTappedHandler = (_ circleSearchView: CircleSearchView, _ result: ResultType) -> Void + + public var minimumSearchLength = 3 + public var minKeyboardRestTimeToSearch: TimeInterval = 0.5 + public let searchTextField:UITextField + public let searchButton:UIButton + public let cancelButton:UIButton + public let searchTableView:UITableView + + private var viewState = ViewState.iconOnly + private var searchTimer: Timer? + private let shortDuration: TimeInterval = 0.06 + private let standardDuration: TimeInterval = 0.12 + private let searchBarHeight: CGFloat = 35.0 + private var searchBarInsetY: CGFloat! + private weak var embeddingScrollView: UIScrollView! + private var searchFieldConstraintPack: ViewStateConstraintPack! + private var searchTableConstraintPack: ViewStateConstraintPack! + private var searchButtonConstraintPack: ViewStateConstraintPack! + private var cancelButtonConstraintPack: ViewStateConstraintPack! + private var centerYConstraint: NSLayoutConstraint! + private var searchResults = [CircleSearchResult]() + private let cellReuseId = "searchResultReuseId" + private let searchChangedHandler: SearchChangedHandler + private let searchResultTappedHandler: SearchResultTappedHandler + + public init(scrollView: UIScrollView, changeHandler: @escaping SearchChangedHandler, tappedHandler: @escaping SearchResultTappedHandler) { + + searchTextField = UITextField() + searchTableView = UITableView() + searchButton = UIButton(type: .custom) + cancelButton = UIButton(type: .custom) + searchChangedHandler = changeHandler + searchResultTappedHandler = tappedHandler + + super.init(frame: scrollView.frame) + searchBarInsetY = searchBarHeight + 16 + + // applyDebugUI() + + setupSelf(scrollView: scrollView) + setupCancelButton() + setupSearchButton() + setupSearchBar() + setupSearchTable() + embeddingScrollView = scrollView + } + + public override func removeFromSuperview() { + superview!.removeObserver(self, forKeyPath: "contentOffset") + super.removeFromSuperview() + } + + func cancelPressed(btn: UIButton) { + searchTextField.resignFirstResponder() + embeddingScrollView.isScrollEnabled = true + updateState(from: embeddingScrollView.contentOffset, force: true) + } + + func searchPressed(btn: UIButton) { + animateFullScreen() + searchTextField.becomeFirstResponder() + } + + private func setupSelf(scrollView: UIScrollView) { + scrollView.addSubview(self) + scrollView.contentInset = UIEdgeInsets(top: searchBarInsetY, left: 0, bottom: 0, right: 0) + self.translatesAutoresizingMaskIntoConstraints = false + scrollView.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil) + + let centerX = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: scrollView, attribute: .centerX, multiplier: 1, constant: 0) + centerYConstraint = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: scrollView, attribute: .centerY, multiplier: 1, constant: searchBarInsetY * -1) + let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: scrollView, attribute: .height, multiplier: 1, constant: 0) + let width = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: scrollView, attribute: .width, multiplier: 1, constant: 0) + + NSLayoutConstraint.activate([centerX, centerYConstraint, height, width]) + } + + private func setupSearchButton() { + self.addSubview(searchButton) + searchButton.translatesAutoresizingMaskIntoConstraints = false + let iconImage = UIImage(named: "searchIcon") + searchButton.setImage(iconImage?.withRenderingMode(.alwaysTemplate), for: .normal) + searchButton.addTarget(self, action: #selector(CircleSearchView.searchPressed(btn:)), for: .touchUpInside) + searchButton.layer.borderColor = UIColor.rgbColor(r: 210, g: 210, b: 210).cgColor + searchButton.layer.borderWidth = 1 + searchButton.backgroundColor = UIColor.white + + let leading = NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal, toItem: searchButton, attribute: .left, multiplier: 1, constant: -8) + let height = NSLayoutConstraint(item: searchButton, attribute: .height, relatedBy: .equal, toItem: searchTextField, attribute: .height, multiplier: 1, constant: 0) + let width = NSLayoutConstraint(item: searchButton, attribute: .width, relatedBy: .equal, toItem: searchButton, attribute: .height, multiplier: 1, constant: 0) + let top = NSLayoutConstraint(item: searchTextField, attribute: .top, relatedBy: .equal, toItem: searchButton, attribute: .top, multiplier: 1, constant: 0) + + let fullWidth = NSLayoutConstraint(item: searchButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 0) + fullWidth.isActive = false + + let iconStateSearch = [leading, height, width, top] + let searchBarStateSearch = [leading, height, width, top] + let fullScreenStateSearch = [leading, height, fullWidth, top] + + searchButtonConstraintPack = ViewStateConstraintPack(iconOnlyStateConstraints: iconStateSearch, + searchBarStateConstraints: searchBarStateSearch, + fullScreenStateConstraints: fullScreenStateSearch) + } + + private func setupCancelButton() { + self.addSubview(cancelButton) + cancelButton.setTitle("Cancel", for: .normal) + cancelButton.addTarget(self, action: #selector(CircleSearchView.cancelPressed(btn:)), for: .touchUpInside) + cancelButton.translatesAutoresizingMaskIntoConstraints = false + cancelButton.setTitleColor(UIColor.black, for: .normal) + cancelButton.layer.borderColor = UIColor.rgbColor(r: 210, g: 210, b: 210).cgColor + cancelButton.layer.borderWidth = 1 + cancelButton.backgroundColor = UIColor.white + + let trailing = NSLayoutConstraint(item: self, attribute: .right, relatedBy: .equal, toItem: cancelButton, attribute: .right, multiplier: 1, constant: 8) + let height = NSLayoutConstraint(item: cancelButton, attribute: .height, relatedBy: .equal, toItem: searchTextField, attribute: .height, multiplier: 1, constant: 0) + let width = NSLayoutConstraint(item: cancelButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 0) + let top = NSLayoutConstraint(item: searchTextField, attribute: .top, relatedBy: .equal, toItem: cancelButton, attribute: .top, multiplier: 1, constant: 0) + + let fullWidth = NSLayoutConstraint(item: cancelButton, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 65) + fullWidth.isActive = false + + let iconStateSearch = [trailing, height, width, top] + let searchBarStateSearch = [trailing, height, width, top] + let fullScreenStateSearch = [trailing, height, fullWidth, top] + + cancelButtonConstraintPack = ViewStateConstraintPack(iconOnlyStateConstraints: iconStateSearch, + searchBarStateConstraints: searchBarStateSearch, + fullScreenStateConstraints: fullScreenStateSearch) + + } + + private func setupSearchBar() { + self.addSubview(searchTextField) + searchTextField.delegate = self + searchTextField.clearButtonMode = .whileEditing + searchTextField.translatesAutoresizingMaskIntoConstraints = false + searchTextField.backgroundColor = UIColor.white + searchTextField.layer.borderColor = UIColor.rgbColor(r: 210, g: 210, b: 210).cgColor + searchTextField.layer.borderWidth = 1 + let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 4, height: searchBarHeight)) + searchTextField.leftView = paddingView + searchTextField.leftViewMode = .always + searchTextField.autocorrectionType = .no + searchTextField.returnKeyType = .search + + let heightConstraintSearch = NSLayoutConstraint(item: searchTextField, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: searchBarHeight) + let leadingConstraintSearch = NSLayoutConstraint(item: searchButton, attribute: .right, relatedBy: .equal, toItem: searchTextField, attribute: .left, multiplier: 1, constant: 0) + let trailingConstraintSearch = NSLayoutConstraint(item: cancelButton, attribute: .left, relatedBy: .equal, toItem: searchTextField, attribute: .right, multiplier: 1, constant: 0) + let topConstraintSearch = NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: searchTextField, attribute: .top , multiplier: 1, constant: -8) + + + let iconSizeWidth = NSLayoutConstraint(item: searchTextField, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 0) + iconSizeWidth.isActive = false + + let topConstraintFull = NSLayoutConstraint(item: self, attribute: .topMargin, relatedBy: .equal, toItem: searchTextField, attribute: .topMargin, multiplier: 1, constant: -8) + topConstraintFull.isActive = false + + searchTextField.addConstraint(heightConstraintSearch) + searchTextField.addConstraint(iconSizeWidth) + + let iconStateSearch = [heightConstraintSearch, leadingConstraintSearch, topConstraintSearch, iconSizeWidth] + let searchBarStateSearch = [heightConstraintSearch, leadingConstraintSearch, trailingConstraintSearch, topConstraintSearch] + let fullScreenStateSearch = [heightConstraintSearch, leadingConstraintSearch, trailingConstraintSearch, topConstraintFull] + + searchFieldConstraintPack = ViewStateConstraintPack(iconOnlyStateConstraints: iconStateSearch, + searchBarStateConstraints: searchBarStateSearch, + fullScreenStateConstraints: fullScreenStateSearch) + + } + + private func setupSearchTable() { + self.addSubview(searchTableView) + searchTableView.delegate = self + searchTableView.dataSource = self + searchTableView.translatesAutoresizingMaskIntoConstraints = false + searchTableView.register(UITableViewCell.self, forCellReuseIdentifier: cellReuseId) + + let height = NSLayoutConstraint(item: searchTableView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 0) + let width = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: searchTableView, attribute: .width, multiplier: 1, constant: 0) + let top = NSLayoutConstraint(item: searchTextField, attribute: .bottom, relatedBy: .equal, toItem: searchTableView, attribute: .top, multiplier: 1, constant: -8) + let leading = NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal, toItem: searchTableView, attribute: .left, multiplier: 1, constant: 0) + searchTableView.addConstraint(height) + + let fullScreenHeight = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: searchTableView, attribute: .height, multiplier: 1, constant: 0) + fullScreenHeight.isActive = false + let iconState = [height, width, top, leading] + let searchBarState = [height, width, top, leading] + let fullScreenState = [fullScreenHeight, width, top, leading] + + searchTableConstraintPack = ViewStateConstraintPack(iconOnlyStateConstraints: iconState, + searchBarStateConstraints: searchBarState, + fullScreenStateConstraints: fullScreenState) + } + + private func applyDebugUI() { + backgroundColor = UIColor.rgbaColor(r: 0, g: 0, b: 0, a: 125) + searchTextField.backgroundColor = UIColor.green + searchButton.backgroundColor = UIColor.blue + cancelButton.backgroundColor = UIColor.red + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func getTransitionDuration(startState: ViewState, endState: ViewState) -> TimeInterval { + if startState == .iconOnly && endState == .searchBar { + return shortDuration + } + + return standardDuration + } + + private func animateFullScreen() { + embeddingScrollView.isScrollEnabled = false//(searchTableView.isHidden || searchTableView.alpha == 0) + superview?.bringSubview(toFront: self) + animateTransition(to: .fullScreen) + } + + public func animateDismissFullScreen() { + self.cancelPressed(btn: cancelButton) + } + + private func animateTransition(to state: ViewState) { + + if state == .fullScreen { + Timer.scheduledTimer(timeInterval: 0.75, target: self, selector: #selector(updateEmbeddingScrollability), userInfo: nil, repeats: false) + } + + if viewState == state { + return + } + + if state == .iconOnly { + searchTextField.leftViewMode = .never + }else { + searchTextField.leftViewMode = .always + } + + viewState = state + + let duration = getTransitionDuration(startState: viewState, endState: state) + + toggleConstraints(for: state) + + self.viewLayerAnimations(for: state, duration: duration) + + UIView.animate(withDuration: duration) { + self.layoutIfNeeded() + } + } + + @objc + private func updateEmbeddingScrollability() { + self.embeddingScrollView.isScrollEnabled = (self.searchTableView.isHidden || self.searchTableView.alpha == 0) + } + + private func toggleConstraints(for state: ViewState) { + + let deactivate = searchFieldConstraintPack.constraintsToDeactivate() + + searchTableConstraintPack.constraintsToDeactivate() + + searchButtonConstraintPack.constraintsToDeactivate() + + cancelButtonConstraintPack.constraintsToDeactivate() + + NSLayoutConstraint.deactivate(deactivate) + + let activate = searchFieldConstraintPack.constraintsToActivate(state) + + searchTableConstraintPack.constraintsToActivate(state) + + searchButtonConstraintPack.constraintsToActivate(state) + + cancelButtonConstraintPack.constraintsToActivate(state) + + NSLayoutConstraint.activate(activate) + } + + private func viewLayerAnimations(for state: ViewState, duration: TimeInterval) { + switch state { + case .searchBar: + searchButton.addCornerRadiusAnimation(from: searchButton.layer.cornerRadius, to: 0.0, duration: duration) + break + case .iconOnly: + searchButton.addCornerRadiusAnimation(from: searchButton.layer.cornerRadius, to: searchBarHeight / 2, duration: duration) + break + case .fullScreen: + break + } + } + + public func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool { + + animateFullScreen() + return true + } + + public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if searchTimer != nil { - var emptyResults = [CircleSearchResult]() - searchChangedHandler(self, timer.userInfo as! String, &emptyResults){[weak self](results) in - self?.searchResultsDidChange(searchResults: results) - } - } - - private func searchResultsDidChange(searchResults: [CircleSearchResult]) { - self.searchResults = searchResults - self.searchTableView.reloadData() - } - - public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - if keyPath == "contentOffset" { - if let obj = object as? UIScrollView { - updateState(from: obj.contentOffset, force: false) - } - }else { - super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) - } - } - - private func updateState(from offset: CGPoint, force: Bool) { - let currentContentTop = ((offset.y) + embeddingScrollView.contentInset.top) - centerYConstraint.constant = currentContentTop - searchBarInsetY - if viewState != .fullScreen || force{ - if currentContentTop > 0.0{ - animateTransition(to: .iconOnly) - }else { - animateTransition(to: .searchBar) - } - } - } - - // MARK: UITableViewDataSource - public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return searchResults.count - } - - public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) - cell.textLabel?.text = searchResults[indexPath.row].title - return cell - } - - // MARK: UITableViewDelegate - - public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - let result = searchResults[indexPath.row].result - searchResultTappedHandler(self, result) + searchTimer?.invalidate() + searchTimer = nil } - override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - return searchTextField.frame.contains(point) || - searchButton.frame.contains(point) || - cancelButton.frame.contains(point) || - (searchTableView.frame.contains(point) && !searchTableView.isHidden) - } + let result = textField.text?.replacingCharacters(in: (textField.text?.rangeFromNSRange(range)!)!, with: string) + if result!.characters.count >= minimumSearchLength + { + searchTimer = Timer.scheduledTimer(timeInterval: minKeyboardRestTimeToSearch, target:self, selector: #selector(CircleSearchView.notifyDelegateOfSearch(_:)), userInfo: result, repeats: false) + } + return true + } + + internal func notifyDelegateOfSearch(_ timer:Timer) + { + var emptyResults = [CircleSearchResult]() + searchChangedHandler(self, timer.userInfo as! String, &emptyResults){[weak self](results) in + self?.searchResultsDidChange(searchResults: results) + } + } + + private func searchResultsDidChange(searchResults: [CircleSearchResult]) { + self.searchResults = searchResults + self.searchTableView.reloadData() + } + + public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { + if keyPath == "contentOffset" { + if let obj = object as? UIScrollView { + updateState(from: obj.contentOffset, force: false) + } + }else { + super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) + } + } + + private func updateState(from offset: CGPoint, force: Bool) { + let currentContentTop = ((offset.y) + embeddingScrollView.contentInset.top) + centerYConstraint.constant = currentContentTop - searchBarInsetY + if viewState != .fullScreen || force{ + if currentContentTop > 0.0{ + animateTransition(to: .iconOnly) + }else { + animateTransition(to: .searchBar) + } + } + } + + // MARK: UITableViewDataSource + public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return searchResults.count + } + + public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: cellReuseId, for: indexPath) + cell.textLabel?.text = searchResults[indexPath.row].title + return cell + } + + // MARK: UITableViewDelegate + + public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let result = searchResults[indexPath.row].result + searchResultTappedHandler(self, result) + } + + override public func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return searchTextField.frame.contains(point) || + searchButton.frame.contains(point) || + cancelButton.frame.contains(point) || + (searchTableView.frame.contains(point) && !searchTableView.isHidden) + } } fileprivate class ViewStateConstraintPack { - - private let iconOnlyStateConstraints: [NSLayoutConstraint] - private let searchBarStateConstraints: [NSLayoutConstraint] - private let fullScreenStateConstraints: [NSLayoutConstraint] - - init(iconOnlyStateConstraints: [NSLayoutConstraint], - searchBarStateConstraints: [NSLayoutConstraint], - fullScreenStateConstraints: [NSLayoutConstraint]) { - self.iconOnlyStateConstraints = iconOnlyStateConstraints - self.searchBarStateConstraints = searchBarStateConstraints - self.fullScreenStateConstraints = fullScreenStateConstraints - } - - func constraintsToDeactivate() -> [NSLayoutConstraint] { - return iconOnlyStateConstraints + searchBarStateConstraints + fullScreenStateConstraints - } - - func constraintsToActivate(_ state: ViewState) -> [NSLayoutConstraint] { - var constraints: [NSLayoutConstraint]! - - switch state { - case .searchBar: - constraints = searchBarStateConstraints - break - case .iconOnly: - constraints = iconOnlyStateConstraints - break - case .fullScreen: - constraints = fullScreenStateConstraints - break - } - - return constraints - } + + private let iconOnlyStateConstraints: [NSLayoutConstraint] + private let searchBarStateConstraints: [NSLayoutConstraint] + private let fullScreenStateConstraints: [NSLayoutConstraint] + + init(iconOnlyStateConstraints: [NSLayoutConstraint], + searchBarStateConstraints: [NSLayoutConstraint], + fullScreenStateConstraints: [NSLayoutConstraint]) { + self.iconOnlyStateConstraints = iconOnlyStateConstraints + self.searchBarStateConstraints = searchBarStateConstraints + self.fullScreenStateConstraints = fullScreenStateConstraints + } + + func constraintsToDeactivate() -> [NSLayoutConstraint] { + return iconOnlyStateConstraints + searchBarStateConstraints + fullScreenStateConstraints + } + + func constraintsToActivate(_ state: ViewState) -> [NSLayoutConstraint] { + var constraints: [NSLayoutConstraint]! + + switch state { + case .searchBar: + constraints = searchBarStateConstraints + break + case .iconOnly: + constraints = iconOnlyStateConstraints + break + case .fullScreen: + constraints = fullScreenStateConstraints + break + } + + return constraints + } } fileprivate extension UIView { - func addCornerRadiusAnimation(from: CGFloat, to: CGFloat, duration: CFTimeInterval) - { - let animation = CABasicAnimation(keyPath:"cornerRadius") - animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - animation.fromValue = from - animation.toValue = to - animation.duration = duration - self.layer.add(animation, forKey: "cornerRadius") - self.layer.cornerRadius = to - } + func addCornerRadiusAnimation(from: CGFloat, to: CGFloat, duration: CFTimeInterval) + { + let animation = CABasicAnimation(keyPath:"cornerRadius") + animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + self.layer.add(animation, forKey: "cornerRadius") + self.layer.cornerRadius = to + } } fileprivate extension String { - func rangeFromNSRange(_ nsRange : NSRange) -> Range? { - guard - let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), - let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex), - let from = from16.samePosition(in: self), - let to = to16.samePosition(in: self) - else { return nil } - return from ..< to - } - func NSRangeFromRange(_ range : Range) -> NSRange { - let from = range.lowerBound.samePosition(in: utf16) - let to = range.upperBound.samePosition(in: utf16) - return NSRange(location: utf16.distance(from: utf16.startIndex, to: from), - length: utf16.distance(from: from, to: to)) - } + func rangeFromNSRange(_ nsRange : NSRange) -> Range? { + guard + let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex), + let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex), + let from = from16.samePosition(in: self), + let to = to16.samePosition(in: self) + else { return nil } + return from ..< to + } + func NSRangeFromRange(_ range : Range) -> NSRange { + let from = range.lowerBound.samePosition(in: utf16) + let to = range.upperBound.samePosition(in: utf16) + return NSRange(location: utf16.distance(from: utf16.startIndex, to: from), + length: utf16.distance(from: from, to: to)) + } } fileprivate extension UIColor { - - static func rgbColor(r: Int, g: Int, b: Int) -> UIColor{ - return rgbaColor(r: r, g: g, b: b, a: 255) - } - - static func rgbaColor(r: Int, g: Int, b: Int, a: Int) -> UIColor{ - return UIColor(colorLiteralRed: Float(Double(r) / 255.0), green: Float(Double(g) / 255.0), blue: Float(Double(b) / 255.0), alpha: Float(Double(a) / 255.0)) - } + + static func rgbColor(r: Int, g: Int, b: Int) -> UIColor{ + return rgbaColor(r: r, g: g, b: b, a: 255) + } + + static func rgbaColor(r: Int, g: Int, b: Int, a: Int) -> UIColor{ + return UIColor(colorLiteralRed: Float(Double(r) / 255.0), green: Float(Double(g) / 255.0), blue: Float(Double(b) / 255.0), alpha: Float(Double(a) / 255.0)) + } } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswerTableViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswerTableViewCell.swift index 5e548533..4e131527 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswerTableViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswerTableViewCell.swift @@ -29,64 +29,64 @@ private func > (lhs: T?, rhs: T?) -> Bool { class AnswerTableViewCell: BVAnswerTableViewCell { - - @IBOutlet weak var writtenAtLabel : UILabel! - @IBOutlet weak var authorNickname : UILabel! - @IBOutlet weak var answerText : UILabel! - @IBOutlet weak var usersFoundHelpfulLabel: UILabel! - - var onAuthorNickNameTapped : ((_ authorId : String) -> Void)? = nil - - override var answer : BVAnswer? { - didSet { - if (answer?.userNickname != nil){ - self.authorNickname.linkAuthorNameLabel(fullText: answer!.userNickname!, author: answer!.userNickname!, target: self, selector: #selector(AnswerTableViewCell.tappedAuthor(_:))) - } else { - self.authorNickname.text = "" - } - answerText.text = answer!.answerText - if let submissionTime = answer!.submissionTime{ - writtenAtLabel.text = dateTimeAgo(submissionTime) - } - else { - writtenAtLabel.text = "" - } - - if answer?.totalFeedbackCount?.int32Value > 0 { - - let totalFeedbackCountString = answer?.totalFeedbackCount?.stringValue ?? "" - let totalPositiveFeedbackCountString = answer?.totalPositiveFeedbackCount?.stringValue ?? "" - - let helpfulText = totalPositiveFeedbackCountString + " of " + totalFeedbackCountString + " users found this answer 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 = "" - } - - } + + @IBOutlet weak var writtenAtLabel : UILabel! + @IBOutlet weak var authorNickname : UILabel! + @IBOutlet weak var answerText : UILabel! + @IBOutlet weak var usersFoundHelpfulLabel: UILabel! + + var onAuthorNickNameTapped : ((_ authorId : String) -> Void)? = nil + + override var answer : BVAnswer? { + didSet { + if (answer?.userNickname != nil){ + self.authorNickname.linkAuthorNameLabel(fullText: answer!.userNickname!, author: answer!.userNickname!, target: self, selector: #selector(AnswerTableViewCell.tappedAuthor(_:))) + } else { + self.authorNickname.text = "" + } + answerText.text = answer!.answerText + if let submissionTime = answer!.submissionTime{ + writtenAtLabel.text = dateTimeAgo(submissionTime) + } + else { + writtenAtLabel.text = "" + } + + if answer?.totalFeedbackCount?.int32Value > 0 { + + let totalFeedbackCountString = answer?.totalFeedbackCount?.stringValue ?? "" + let totalPositiveFeedbackCountString = answer?.totalPositiveFeedbackCount?.stringValue ?? "" + + let helpfulText = totalPositiveFeedbackCountString + " of " + totalFeedbackCountString + " users found this answer 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((answer?.authorId)!) - } + } + + + func tappedAuthor(_ sender:UITapGestureRecognizer){ + if let onAuthorNameTapped = self.onAuthorNickNameTapped { + onAuthorNameTapped((answer?.authorId)!) } - - + } + + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.swift index 6e88aa37..4c5ff126 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AnswersViewController.swift @@ -9,116 +9,116 @@ import BVSDK import FontAwesomeKit class AnswersViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { + + @IBOutlet weak var tableView: BVAnswersTableView! + @IBOutlet weak var header : ProductDetailHeaderView! + var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) + + let product : BVProduct + let question : BVQuestion + + init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product: BVProduct, question: BVQuestion) { + self.question = question + self.product = product + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() - @IBOutlet weak var tableView: BVAnswersTableView! - @IBOutlet weak var header : ProductDetailHeaderView! - var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) + self.title = "Answers" - let product : BVProduct - let question : BVQuestion + ProfileUtils.trackViewController(self) - init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product: BVProduct, question: BVQuestion) { - self.question = question - self.product = product - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } + header.product = product - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + self.view.backgroundColor = UIColor.appBackground() + self.tableView.backgroundColor = UIColor.appBackground() - override func viewDidLoad() { - super.viewDidLoad() - - self.title = "Answers" - - ProfileUtils.trackViewController(self) - - header.product = product - - self.view.backgroundColor = UIColor.appBackground() - self.tableView.backgroundColor = UIColor.appBackground() - - tableView.delegate = self - tableView.dataSource = self - tableView.estimatedRowHeight = 40 - tableView.rowHeight = UITableViewAutomaticDimension - - let nib1 = UINib(nibName: "AnswerListHeaderCell", bundle: nil) - tableView.register(nib1, forCellReuseIdentifier: "AnswerListHeaderCell") - - let nib2 = UINib(nibName: "ProductPageButtonCell", bundle: nil) - tableView.register(nib2, forCellReuseIdentifier: "ProductPageButtonCell") - - let nib3 = UINib(nibName: "AnswerTableViewCell", bundle: nil) - tableView.register(nib3, forCellReuseIdentifier: "AnswerTableViewCell") - - } + tableView.delegate = self + tableView.dataSource = self + tableView.estimatedRowHeight = 40 + tableView.rowHeight = UITableViewAutomaticDimension - // MARK: - Table view data source + let nib1 = UINib(nibName: "AnswerListHeaderCell", bundle: nil) + tableView.register(nib1, forCellReuseIdentifier: "AnswerListHeaderCell") - func numberOfSections(in tableView: UITableView) -> Int { - - return 2 - - } + let nib2 = UINib(nibName: "ProductPageButtonCell", bundle: nil) + tableView.register(nib2, forCellReuseIdentifier: "ProductPageButtonCell") - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - - if section == 0 { - return 2 - } - else { - return question.answers.count - } - - } + let nib3 = UINib(nibName: "AnswerTableViewCell", bundle: nil) + tableView.register(nib3, forCellReuseIdentifier: "AnswerTableViewCell") - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - if indexPath.section == 0 { - - if indexPath.row == 0 { - let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerListHeaderCell") as! AnswerListHeaderCell - - cell.question = question - - return cell - } - else { - let cell = tableView.dequeueReusableCell(withIdentifier: "ProductPageButtonCell") as! ProductPageButtonCell - - cell.button.setTitle("Give your answer!", for: UIControlState()) - cell.setCustomLeftIcon(FAKFontAwesome.plusIcon(withSize:)) - cell.setCustomRightIcon(FAKFontAwesome.chevronRightIcon(withSize:)) - cell.button.removeTarget(nil, action: nil, for: .allEvents) - cell.button.addTarget(self, action: #selector(AnswersViewController.writeAnAnswerTapped), for: .touchUpInside) - - return cell - } - - } - - else { - let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerTableViewCell") as! AnswerTableViewCell - - cell.answer = question.answers[indexPath.row] - cell.onAuthorNickNameTapped = { (authorId) -> Void in - let authorVC = AuthorProfileViewController(authorId: authorId) - self.navigationController?.pushViewController(authorVC, animated: true) - } - - return cell - } - + } + + // MARK: - Table view data source + + func numberOfSections(in tableView: UITableView) -> Int { + + return 2 + + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + if section == 0 { + return 2 + } + else { + return question.answers.count } - func writeAnAnswerTapped() { - - let vc = SubmitAnswerViewController(nibName: "SubmitAnswerViewController", bundle: nil, product:product, question: question) - - self.navigationController?.pushViewController(vc, animated: true) - + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + if indexPath.section == 0 { + + if indexPath.row == 0 { + let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerListHeaderCell") as! AnswerListHeaderCell + + cell.question = question + + return cell + } + else { + let cell = tableView.dequeueReusableCell(withIdentifier: "ProductPageButtonCell") as! ProductPageButtonCell + + cell.button.setTitle("Give your answer!", for: UIControlState()) + cell.setCustomLeftIcon(FAKFontAwesome.plusIcon(withSize:)) + cell.setCustomRightIcon(FAKFontAwesome.chevronRightIcon(withSize:)) + cell.button.removeTarget(nil, action: nil, for: .allEvents) + cell.button.addTarget(self, action: #selector(AnswersViewController.writeAnAnswerTapped), for: .touchUpInside) + + return cell + } + } + + else { + let cell = tableView.dequeueReusableCell(withIdentifier: "AnswerTableViewCell") as! AnswerTableViewCell + + cell.answer = question.answers[indexPath.row] + cell.onAuthorNickNameTapped = { (authorId) -> Void in + let authorVC = AuthorProfileViewController(authorId: authorId) + self.navigationController?.pushViewController(authorVC, animated: true) + } + + return cell + } + + } + + func writeAnAnswerTapped() { + + let vc = SubmitAnswerViewController(nibName: "SubmitAnswerViewController", bundle: nil, product:product, question: question) + + self.navigationController?.pushViewController(vc, animated: true) + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AskAQuestionViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AskAQuestionViewController.swift index e780f29f..06d35053 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AskAQuestionViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AskAQuestionViewController.swift @@ -9,173 +9,173 @@ import BVSDK import SDForms class AskAQuestionViewController: UIViewController, SDFormDelegate, SDFormDataSource { - - var form : SDForm? - - // For using SDFormField, this demo presumes one field item per section. - // Hence, the section header will contain the tile, and the row will just contain the widget - // and any placeholder text - var formFields : [SDFormField] = [] - var sectionTitles : [String] = [] - - var questionSubmissionParameters = QuestionSubmissionParamsHolder() - - @IBOutlet weak var tableView: UITableView! - @IBOutlet weak var header : ProductDetailHeaderView! - var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) - let product: BVProduct - - init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product: BVProduct?) { - self.product = product! - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - ProfileUtils.trackViewController(self) - - self.title = "Ask a Question!" - header.product = product - - self.view.backgroundColor = UIColor.appBackground() - self.tableView.backgroundColor = UIColor.white - - // add a submit button - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Submit", style: .done, target: self, action: #selector(AskAQuestionViewController.submitTapped)) - - // form scrolling above keyboard - self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 300, right: 0) - - self.initFormFields() - - } - - func initFormFields(){ - - let questionField = SDMultilineTextField(object: self.questionSubmissionParameters, relatedPropertyKey: "questionSummary") - questionField?.placeholder = "Example: How do I get replacement bolts?" - - let moreDetailsField = SDMultilineTextField(object: self.questionSubmissionParameters, relatedPropertyKey: "questionDetails") - moreDetailsField?.placeholder = "Example: I have looked at the manual and can't figure out what I'm doing wrong." - - let nickNameField : SDTextFormField = SDTextFormField(object: self.questionSubmissionParameters, relatedPropertyKey: "userNickname") - nickNameField.placeholder = "Display name for the question" - - let emailAddressField : SDTextFormField = SDTextFormField(object: self.questionSubmissionParameters, relatedPropertyKey: "userEmail") - emailAddressField.placeholder = "Enter a valid email address." - - let emailOKSwitchField = SDSwitchField(object: self.questionSubmissionParameters, relatedPropertyKey: "sendEmailAlertWhenPublished") - emailOKSwitchField?.title = "Send me status by email?" - - let agreeTermsAndConditions = SDSwitchField(object: self.questionSubmissionParameters, relatedPropertyKey: "agreedToTermsAndConditions") - agreeTermsAndConditions?.title = "Agree?" - - // Keep the formFields and sectionTitles in the same order if you switch them around. - self.formFields = [questionField!, moreDetailsField!, nickNameField, emailAddressField, emailOKSwitchField!, agreeTermsAndConditions!] - self.sectionTitles = ["Question", "More Details (optional)", "Nickname", "Email address", "May we contact you at this email address?", "Do you agree to the Terms & Conditions?"] - - // set up delegate/datasource last! - self.form = SDForm(tableView: self.tableView) - self.form?.delegate = self - self.form?.dataSource = self - - } - - func submitTapped() { - - // TODO: Add in field validator here.... - // Otherwise the API will validate for us. - - self.spinner.center = self.view.center - self.view.addSubview(self.spinner) - - self.tableView.resignFirstResponder() - - // Submit the question - - let submission = BVQuestionSubmission(productId: product.identifier) - submission.action = .preview // don't actually just submit for real, this is just for demo - submission.questionSummary = self.questionSubmissionParameters.questionSummary as? String - submission.questionDetails = self.questionSubmissionParameters.questionDetails as? String - submission.userNickname = self.questionSubmissionParameters.userNickname as? String - submission.userEmail = self.questionSubmissionParameters.userEmail as? String - submission.sendEmailAlertWhenPublished = self.questionSubmissionParameters.sendEmailAlertWhenPublished - submission.agreedToTermsAndConditions = self.questionSubmissionParameters.agreedToTermsAndConditions - - submission.submit({ (response) in - - DispatchQueue.main.async(execute: { - _ = SweetAlert().showAlert("Success!", subTitle: "Your question was submitted. It may take up to 72 hours for us to respond.", style: .success) - _ = self.navigationController?.popViewController(animated: true) - }) - - }) { (errors) in - - DispatchQueue.main.async(execute: { - var errorMessage = "" - for error in errors { - errorMessage += "\(error)." - } - - _ = SweetAlert().showAlert("Error Sumbitting Question", subTitle: errorMessage, style: .error) - - self.spinner.removeFromSuperview() - }) - + + var form : SDForm? + + // For using SDFormField, this demo presumes one field item per section. + // Hence, the section header will contain the tile, and the row will just contain the widget + // and any placeholder text + var formFields : [SDFormField] = [] + var sectionTitles : [String] = [] + + var questionSubmissionParameters = QuestionSubmissionParamsHolder() + + @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var header : ProductDetailHeaderView! + var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) + let product: BVProduct + + init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product: BVProduct?) { + self.product = product! + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + ProfileUtils.trackViewController(self) + + self.title = "Ask a Question!" + header.product = product + + self.view.backgroundColor = UIColor.appBackground() + self.tableView.backgroundColor = UIColor.white + + // add a submit button + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Submit", style: .done, target: self, action: #selector(AskAQuestionViewController.submitTapped)) + + // form scrolling above keyboard + self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 300, right: 0) + + self.initFormFields() + + } + + func initFormFields(){ + + let questionField = SDMultilineTextField(object: self.questionSubmissionParameters, relatedPropertyKey: "questionSummary") + questionField?.placeholder = "Example: How do I get replacement bolts?" + + let moreDetailsField = SDMultilineTextField(object: self.questionSubmissionParameters, relatedPropertyKey: "questionDetails") + moreDetailsField?.placeholder = "Example: I have looked at the manual and can't figure out what I'm doing wrong." + + let nickNameField : SDTextFormField = SDTextFormField(object: self.questionSubmissionParameters, relatedPropertyKey: "userNickname") + nickNameField.placeholder = "Display name for the question" + + let emailAddressField : SDTextFormField = SDTextFormField(object: self.questionSubmissionParameters, relatedPropertyKey: "userEmail") + emailAddressField.placeholder = "Enter a valid email address." + + let emailOKSwitchField = SDSwitchField(object: self.questionSubmissionParameters, relatedPropertyKey: "sendEmailAlertWhenPublished") + emailOKSwitchField?.title = "Send me status by email?" + + let agreeTermsAndConditions = SDSwitchField(object: self.questionSubmissionParameters, relatedPropertyKey: "agreedToTermsAndConditions") + agreeTermsAndConditions?.title = "Agree?" + + // Keep the formFields and sectionTitles in the same order if you switch them around. + self.formFields = [questionField!, moreDetailsField!, nickNameField, emailAddressField, emailOKSwitchField!, agreeTermsAndConditions!] + self.sectionTitles = ["Question", "More Details (optional)", "Nickname", "Email address", "May we contact you at this email address?", "Do you agree to the Terms & Conditions?"] + + // set up delegate/datasource last! + self.form = SDForm(tableView: self.tableView) + self.form?.delegate = self + self.form?.dataSource = self + + } + + func submitTapped() { + + // TODO: Add in field validator here.... + // Otherwise the API will validate for us. + + self.spinner.center = self.view.center + self.view.addSubview(self.spinner) + + self.tableView.resignFirstResponder() + + // Submit the question + + let submission = BVQuestionSubmission(productId: product.identifier) + submission.action = .preview // don't actually just submit for real, this is just for demo + submission.questionSummary = self.questionSubmissionParameters.questionSummary as? String + submission.questionDetails = self.questionSubmissionParameters.questionDetails as? String + submission.userNickname = self.questionSubmissionParameters.userNickname as? String + submission.userEmail = self.questionSubmissionParameters.userEmail as? String + submission.sendEmailAlertWhenPublished = self.questionSubmissionParameters.sendEmailAlertWhenPublished + submission.agreedToTermsAndConditions = self.questionSubmissionParameters.agreedToTermsAndConditions + + submission.submit({ (response) in + + DispatchQueue.main.async(execute: { + _ = SweetAlert().showAlert("Success!", subTitle: "Your question was submitted. It may take up to 72 hours for us to respond.", style: .success) + _ = self.navigationController?.popViewController(animated: true) + }) + + }) { (errors) in + + DispatchQueue.main.async(execute: { + var errorMessage = "" + for error in errors { + errorMessage += "\(error)." } - } - - // MARK: SDKFormDelegate, SDFormDataSource - - func form(_ form: SDForm!, willDisplayHeaderView view: UIView!, forSection section: Int) { - - let hv = view as! UITableViewHeaderFooterView - let color = UIColor.white - hv.tintColor = color - hv.contentView.backgroundColor = color - hv.textLabel?.textColor = UIColor.bazaarvoiceNavy() + _ = SweetAlert().showAlert("Error Sumbitting Question", subTitle: errorMessage, style: .error) + self.spinner.removeFromSuperview() + }) + } - func numberOfSections(for form: SDForm!) -> Int { - return (self.formFields.count) - } - - func form(_ form: SDForm!, numberOfFieldsInSection section: Int) -> Int { - return 1 - } - - func form(_ form: SDForm!, titleForHeaderInSection section: Int) -> String! { - return self.sectionTitles[section] - } - - func form(_ form: SDForm!, fieldForRow row: Int, inSection section: Int) -> SDFormField! { - - return self.formFields[section] - - } - - func viewController(for form: SDForm!) -> UIViewController! { - return self; - } - - + } + + // MARK: SDKFormDelegate, SDFormDataSource + + func form(_ form: SDForm!, willDisplayHeaderView view: UIView!, forSection section: Int) { + + let hv = view as! UITableViewHeaderFooterView + let color = UIColor.white + hv.tintColor = color + hv.contentView.backgroundColor = color + hv.textLabel?.textColor = UIColor.bazaarvoiceNavy() + + } + + func numberOfSections(for form: SDForm!) -> Int { + return (self.formFields.count) + } + + func form(_ form: SDForm!, numberOfFieldsInSection section: Int) -> Int { + return 1 + } + + func form(_ form: SDForm!, titleForHeaderInSection section: Int) -> String! { + return self.sectionTitles[section] + } + + func form(_ form: SDForm!, fieldForRow row: Int, inSection section: Int) -> SDFormField! { + + return self.formFields[section] + + } + + func viewController(for form: SDForm!) -> UIViewController! { + return self; + } + + } @objc class QuestionSubmissionParamsHolder : NSObject { - - var questionSummary : NSString? - var questionDetails : NSString? - var userNickname : NSString? - var userEmail : NSString? - - var sendEmailAlertWhenPublished:NSNumber? - var agreedToTermsAndConditions:NSNumber? - + + var questionSummary : NSString? + var questionDetails : NSString? + var userNickname : NSString? + var userEmail : NSString? + + var sendEmailAlertWhenPublished:NSNumber? + var agreedToTermsAndConditions:NSNumber? + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.swift index bd3ac398..e53afd83 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/AuthorProfileViewController.swift @@ -9,256 +9,256 @@ import UIKit import BVSDK enum TableViewTag : Int { - case ReviewsTableView = 0, QuestionsTableView, AnswersTableView, ReviewCommentsTableView + case ReviewsTableView = 0, QuestionsTableView, AnswersTableView, ReviewCommentsTableView } class AuthorProfileViewController: UIViewController, UITableViewDataSource { - - var authorId : String? - var author : BVAuthor? + + var authorId : String? + var author : BVAuthor? + + var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) + + @IBOutlet weak var userProfileImageView: UIImageView! + @IBOutlet weak var userNameLabel: UILabel! + @IBOutlet weak var userLocationLabel: UILabel! + @IBOutlet weak var userBadgesLabel: UILabel! + + @IBOutlet weak var ugcTypeSegmentedControl: UISegmentedControl! + + @IBOutlet weak var reviewsTableView: UITableView! + @IBOutlet weak var questionsTableView: UITableView! + @IBOutlet weak var answersTableView: UITableView! + @IBOutlet weak var commentsTableView: UITableView! + + init(authorId : String) { - var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) + self.authorId = authorId + super.init(nibName: nil, bundle: nil) - @IBOutlet weak var userProfileImageView: UIImageView! - @IBOutlet weak var userNameLabel: UILabel! - @IBOutlet weak var userLocationLabel: UILabel! - @IBOutlet weak var userBadgesLabel: UILabel! + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() - @IBOutlet weak var ugcTypeSegmentedControl: UISegmentedControl! + self.navigationItem.title = "Profile" - @IBOutlet weak var reviewsTableView: UITableView! - @IBOutlet weak var questionsTableView: UITableView! - @IBOutlet weak var answersTableView: UITableView! - @IBOutlet weak var commentsTableView: UITableView! + let nib1 = UINib(nibName: "RatingTableViewCell", bundle: nil) + reviewsTableView.register(nib1, forCellReuseIdentifier: "RatingTableViewCell") + reviewsTableView.estimatedRowHeight = 40 + reviewsTableView.rowHeight = UITableViewAutomaticDimension + reviewsTableView.allowsSelection = false - init(authorId : String) { - - self.authorId = authorId - super.init(nibName: nil, bundle: nil) - - } + let nib2 = UINib(nibName: "AnswerTableViewCell", bundle: nil) + answersTableView.register(nib2, forCellReuseIdentifier: "AnswerTableViewCell") + answersTableView.estimatedRowHeight = 40 + answersTableView.rowHeight = UITableViewAutomaticDimension + answersTableView.allowsSelection = false - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + let nib3 = UINib(nibName: "QuestionAnswerTableViewCell", bundle: nil) + questionsTableView.register(nib3, forCellReuseIdentifier: "QuestionAnswerTableViewCell") + questionsTableView.estimatedRowHeight = 40 + questionsTableView.rowHeight = UITableViewAutomaticDimension + questionsTableView.allowsSelection = false - override func viewDidLoad() { - super.viewDidLoad() - - self.navigationItem.title = "Profile" - - let nib1 = UINib(nibName: "RatingTableViewCell", bundle: nil) - reviewsTableView.register(nib1, forCellReuseIdentifier: "RatingTableViewCell") - reviewsTableView.estimatedRowHeight = 40 - reviewsTableView.rowHeight = UITableViewAutomaticDimension - reviewsTableView.allowsSelection = false - - let nib2 = UINib(nibName: "AnswerTableViewCell", bundle: nil) - answersTableView.register(nib2, forCellReuseIdentifier: "AnswerTableViewCell") - answersTableView.estimatedRowHeight = 40 - answersTableView.rowHeight = UITableViewAutomaticDimension - answersTableView.allowsSelection = false - - let nib3 = UINib(nibName: "QuestionAnswerTableViewCell", bundle: nil) - questionsTableView.register(nib3, forCellReuseIdentifier: "QuestionAnswerTableViewCell") - questionsTableView.estimatedRowHeight = 40 - 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() - } + let nib4 = UINib(nibName: "ReviewCommentTableViewCell", bundle: nil) + commentsTableView.register(nib4, forCellReuseIdentifier: "ReviewCommentTableViewCell") + commentsTableView.estimatedRowHeight = 40 + commentsTableView.rowHeight = UITableViewAutomaticDimension + commentsTableView.allowsSelection = false - override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - self.spinner.center = self.view.center - } + self.view.layoutIfNeeded() + self.initDefaultUI() + self.fetchAuthorProfile() + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() - private func initDefaultUI() { - - self.view.bringSubview(toFront: self.reviewsTableView) - - self.userNameLabel.text = "" - self.userLocationLabel.text = "" - self.userBadgesLabel.text = "" - - self.userBadgesLabel.layer.cornerRadius = 2 - self.userBadgesLabel.layer.backgroundColor = UIColor.bazaarvoiceTeal().cgColor - - // round the profile image - self.userProfileImageView.setRounded() - - } + self.spinner.center = self.view.center + } + + private func initDefaultUI() { + + self.view.bringSubview(toFront: self.reviewsTableView) + + self.userNameLabel.text = "" + self.userLocationLabel.text = "" + self.userBadgesLabel.text = "" + + self.userBadgesLabel.layer.cornerRadius = 2 + self.userBadgesLabel.layer.backgroundColor = UIColor.bazaarvoiceTeal().cgColor + + // round the profile image + self.userProfileImageView.setRounded() + + } + + private func fetchAuthorProfile(){ + + self.reviewsTableView.addSubview(self.spinner) - private func fetchAuthorProfile(){ - - self.reviewsTableView.addSubview(self.spinner) - - let request = BVAuthorRequest(authorId: self.authorId!) - // stats includes - request.includeStatistics(.answers) - request.includeStatistics(.questions) - request.includeStatistics(.reviews) - // other includes - 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) - request.sortIncludedQuestions(.submissionTime, order: .descending) - - request.load({ (response) in - - self.spinner.removeFromSuperview() - - // success - if response.results.isEmpty { - _ = SweetAlert().showAlert("Empty Profile", subTitle:"There was no profile found.", style: .error) - return - } - - self.author = response.results.first! - - self.userNameLabel.text = self.author?.userNickname - - let location = self.author?.userLocation != nil ? (self.author?.userLocation)! : "" - self.userLocationLabel.text = location - - // Just adding badge text here, really we want to use an image - var badgeString = "" - for badge in (self.author?.badges)! { - badgeString += badge.identifier!.uppercased() - if !(badge.isEqual(self.author?.badges.last)) { - badgeString += ", " - } else { - badgeString = " " + badgeString + " " - } - } - self.userBadgesLabel.text = badgeString - - 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 - - // error - print(error) - _ = SweetAlert().showAlert("Error Loading Profile", subTitle: error.description, style: .error) - self.spinner.removeFromSuperview() - + let request = BVAuthorRequest(authorId: self.authorId!) + // stats includes + request.includeStatistics(.authorAnswers) + request.includeStatistics(.authorQuestions) + request.includeStatistics(.authorReviews) + // other includes + request.include(.authorReviews, limit: 20) + request.include(.authorQuestions, limit: 20) + request.include(.authorAnswers, limit: 20) + request.include(.authorReviewComments, limit: 20) + // sorts + request.sort(by: .answerSubmissionTime, monotonicSortOrderValue: .descending) + request.sort(by: .reviewSubmissionTime, monotonicSortOrderValue: .descending) + request.sort(by: .questionSubmissionTime, monotonicSortOrderValue: .descending) + + request.load({ (response) in + + self.spinner.removeFromSuperview() + + // success + if response.results.isEmpty { + _ = SweetAlert().showAlert("Empty Profile", subTitle:"There was no profile found.", style: .error) + return + } + + self.author = response.results.first! + + self.userNameLabel.text = self.author?.userNickname + + let location = self.author?.userLocation != nil ? (self.author?.userLocation)! : "" + self.userLocationLabel.text = location + + // Just adding badge text here, really we want to use an image + var badgeString = "" + for badge in (self.author?.badges)! { + badgeString += badge.identifier!.uppercased() + if !(badge.isEqual(self.author?.badges.last)) { + badgeString += ", " + } else { + badgeString = " " + badgeString + " " } - + } + self.userBadgesLabel.text = badgeString + + 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 + + // error + print(error) + _ = SweetAlert().showAlert("Error Loading Profile", subTitle: error.description, style: .error) + self.spinner.removeFromSuperview() + } - - // MARK: UITableViewDatasource - func numberOfSections(in tableView: UITableView) -> Int { - return 1 + } + + // MARK: UITableViewDatasource + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + if self.author == nil { + return 0 } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - - if self.author == nil { - return 0 - } - - switch tableView.tag { - case TableViewTag.ReviewsTableView.rawValue: - return (self.author?.includedReviews.count)! - case TableViewTag.QuestionsTableView.rawValue: - 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") - } - - return 0 + switch tableView.tag { + case TableViewTag.ReviewsTableView.rawValue: + return (self.author?.includedReviews.count)! + case TableViewTag.QuestionsTableView.rawValue: + 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") } + return 0 + } + + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - switch tableView.tag { - case TableViewTag.ReviewsTableView.rawValue: - let cell = tableView.dequeueReusableCell(withIdentifier: "RatingTableViewCell") as! RatingTableViewCell - cell.review = self.author?.includedReviews[indexPath.row] - return cell - case TableViewTag.QuestionsTableView.rawValue: - let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionAnswerTableViewCell") as! QuestionAnswerTableViewCell - cell.question = self.author?.includedQuestions[indexPath.row] - return cell - case TableViewTag.AnswersTableView.rawValue: - 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") - } - - return UITableViewCell() + switch tableView.tag { + case TableViewTag.ReviewsTableView.rawValue: + let cell = tableView.dequeueReusableCell(withIdentifier: "RatingTableViewCell") as! RatingTableViewCell + cell.review = self.author?.includedReviews[indexPath.row] + return cell + case TableViewTag.QuestionsTableView.rawValue: + let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionAnswerTableViewCell") as! QuestionAnswerTableViewCell + cell.question = self.author?.includedQuestions[indexPath.row] + return cell + case TableViewTag.AnswersTableView.rawValue: + 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") } - // MARK: IBAction - @IBAction func ugcSegmentToggled(_ sender: AnyObject) { - - switch sender.selectedSegmentIndex { - - case TableViewTag.ReviewsTableView.rawValue: - self.view.bringSubview(toFront: self.reviewsTableView) - case TableViewTag.QuestionsTableView.rawValue: - 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") - } - + return UITableViewCell() + } + + // MARK: IBAction + @IBAction func ugcSegmentToggled(_ sender: AnyObject) { + + switch sender.selectedSegmentIndex { + + case TableViewTag.ReviewsTableView.rawValue: + self.view.bringSubview(toFront: self.reviewsTableView) + case TableViewTag.QuestionsTableView.rawValue: + 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") } + + } } extension UIImageView { - - func setRounded() { - let radius = self.frame.width/2 - self.layer.cornerRadius = radius - self.layer.masksToBounds = true - } + + func setRounded() { + let radius = self.frame.width/2 + self.layer.cornerRadius = radius + self.layer.masksToBounds = true + } } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.swift index c1ce17e4..9cfed29d 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/QuestionAnswerViewController.swift @@ -10,161 +10,161 @@ import BVSDK import FontAwesomeKit class QuestionAnswerViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { + + @IBOutlet weak var tableView: BVQuestionsTableView! + @IBOutlet weak var header : ProductDetailHeaderView! + + var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) + let product: BVProduct + var questions : [BVQuestion] = [] + + init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product:BVProduct?) { + self.product = product! + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() - @IBOutlet weak var tableView: BVQuestionsTableView! - @IBOutlet weak var header : ProductDetailHeaderView! - - var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) - let product: BVProduct - var questions : [BVQuestion] = [] + self.title = "Questions" - init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product:BVProduct?) { - self.product = product! - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } + ProfileUtils.trackViewController(self) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - self.title = "Questions" - - ProfileUtils.trackViewController(self) - - header.product = product - - self.view.backgroundColor = UIColor.appBackground() - self.tableView.backgroundColor = UIColor.appBackground() - tableView.delegate = self - tableView.dataSource = self - tableView.estimatedRowHeight = 40 - tableView.rowHeight = UITableViewAutomaticDimension - - let nib1 = UINib(nibName: "CallToActionCell", bundle: nil) - tableView.register(nib1, forCellReuseIdentifier: "CallToActionCell") - - let nib2 = UINib(nibName: "QuestionAnswerTableViewCell", bundle: nil) - tableView.register(nib2, forCellReuseIdentifier: "QuestionAnswerTableViewCell") - - // add a Ask a question button... - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Ask a question", style: .plain, target: self, action: #selector(QuestionAnswerViewController.askQuestionTapped)) - - self.loadQuestions() - - } + header.product = product - func loadQuestions() { - - let request = BVQuestionsAndAnswersRequest(productId: product.identifier, limit: 20, offset: 0) - - self.tableView.load(request, success: { (response) in - - self.spinner.removeFromSuperview() - self.questions = response.results - self.tableView.reloadData() - - }) - { (errors) in - - print("An error occurred: \(errors)") - - } - - } + self.view.backgroundColor = UIColor.appBackground() + self.tableView.backgroundColor = UIColor.appBackground() + tableView.delegate = self + tableView.dataSource = self + tableView.estimatedRowHeight = 40 + tableView.rowHeight = UITableViewAutomaticDimension - func askQuestionTapped() { - - let askAQuestionVC = AskAQuestionViewController(nibName: "AskAQuestionViewController", bundle: nil, product: product) - - self.navigationController?.pushViewController(askAQuestionVC, animated: true) - - } + let nib1 = UINib(nibName: "CallToActionCell", bundle: nil) + tableView.register(nib1, forCellReuseIdentifier: "CallToActionCell") - func submitAnswerPressed(_ sender: UIButton) { - - let indexPathSection = sender.tag - let question = questions[indexPathSection] - - let vc = SubmitAnswerViewController(nibName: "SubmitAnswerViewController", bundle: nil, product: product, question: question) - self.navigationController?.pushViewController(vc, animated: true) - - } + let nib2 = UINib(nibName: "QuestionAnswerTableViewCell", bundle: nil) + tableView.register(nib2, forCellReuseIdentifier: "QuestionAnswerTableViewCell") - func readAnswersTapped(_ sender: UIButton) { - - let indexPathSection = sender.tag - let question = questions[indexPathSection] - - let vc = AnswersViewController(nibName: "AnswersViewController", bundle: nil, product: product, question: question) - self.navigationController?.pushViewController(vc, animated: true) - - } + // add a Ask a question button... + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Ask a question", style: .plain, target: self, action: #selector(QuestionAnswerViewController.askQuestionTapped)) - // MARK: UITableViewDatasource + self.loadQuestions() - func numberOfSections(in tableView: UITableView) -> Int { - - return questions.count - - } + } + + func loadQuestions() { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - - return 2 - - } + let request = BVQuestionsAndAnswersRequest(productId: product.identifier, limit: 20, offset: 0) - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - let question = questions[(indexPath as NSIndexPath).section] - - if (indexPath as NSIndexPath).row == 0 { - - let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionAnswerTableViewCell") as! QuestionAnswerTableViewCell - cell.question = question - cell.onAuthorNickNameTapped = { (authorId) -> Void in - let authorVC = AuthorProfileViewController(authorId: authorId) - self.navigationController?.pushViewController(authorVC, animated: true) - } - return cell - - } - else { - - let callToActionCell = tableView.dequeueReusableCell(withIdentifier: "CallToActionCell") as! CallToActionCell - callToActionCell.setCustomRightIcon(FAKFontAwesome.chevronRightIcon(withSize:)) - - let numAnswers = question.answers.count - if numAnswers == 0 { - callToActionCell.button.setTitle("Be the first to answer!", for: UIControlState()) - callToActionCell.setCustomLeftIcon(FAKFontAwesome.plusIcon(withSize:)) - callToActionCell.button.removeTarget(nil, action: nil, for: .allEvents) - callToActionCell.button.tag = (indexPath as NSIndexPath).section - callToActionCell.button.addTarget(self, action: #selector(QuestionAnswerViewController.submitAnswerPressed(_:)), for: .touchUpInside) - } - else { - callToActionCell.button.setTitle("Read \(numAnswers) answers", for: UIControlState()) - callToActionCell.setCustomLeftIcon(FAKFontAwesome.commentsIcon(withSize:)) - callToActionCell.button.removeTarget(nil, action: nil, for: .allEvents) - callToActionCell.button.tag = (indexPath as NSIndexPath).section - callToActionCell.button.addTarget(self, action: #selector(QuestionAnswerViewController.readAnswersTapped(_:)), for: .touchUpInside) - } - - return callToActionCell - - } - + self.tableView.load(request, success: { (response) in + + self.spinner.removeFromSuperview() + self.questions = response.results + self.tableView.reloadData() + + }) + { (errors) in + + print("An error occurred: \(errors)") + } - // MARK: UITableViewDelegate + } + + func askQuestionTapped() { + + let askAQuestionVC = AskAQuestionViewController(nibName: "AskAQuestionViewController", bundle: nil, product: product) + + self.navigationController?.pushViewController(askAQuestionVC, animated: true) + + } + + func submitAnswerPressed(_ sender: UIButton) { - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - - tableView.deselectRow(at: indexPath, animated: true) - + let indexPathSection = sender.tag + let question = questions[indexPathSection] + + let vc = SubmitAnswerViewController(nibName: "SubmitAnswerViewController", bundle: nil, product: product, question: question) + self.navigationController?.pushViewController(vc, animated: true) + + } + + func readAnswersTapped(_ sender: UIButton) { + + let indexPathSection = sender.tag + let question = questions[indexPathSection] + + let vc = AnswersViewController(nibName: "AnswersViewController", bundle: nil, product: product, question: question) + self.navigationController?.pushViewController(vc, animated: true) + + } + + // MARK: UITableViewDatasource + + func numberOfSections(in tableView: UITableView) -> Int { + + return questions.count + + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + return 2 + + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let question = questions[(indexPath as NSIndexPath).section] + + if (indexPath as NSIndexPath).row == 0 { + + let cell = tableView.dequeueReusableCell(withIdentifier: "QuestionAnswerTableViewCell") as! QuestionAnswerTableViewCell + cell.question = question + cell.onAuthorNickNameTapped = { (authorId) -> Void in + let authorVC = AuthorProfileViewController(authorId: authorId) + self.navigationController?.pushViewController(authorVC, animated: true) + } + return cell + } + else { + + let callToActionCell = tableView.dequeueReusableCell(withIdentifier: "CallToActionCell") as! CallToActionCell + callToActionCell.setCustomRightIcon(FAKFontAwesome.chevronRightIcon(withSize:)) + + let numAnswers = question.answers.count + if numAnswers == 0 { + callToActionCell.button.setTitle("Be the first to answer!", for: UIControlState()) + callToActionCell.setCustomLeftIcon(FAKFontAwesome.plusIcon(withSize:)) + callToActionCell.button.removeTarget(nil, action: nil, for: .allEvents) + callToActionCell.button.tag = (indexPath as NSIndexPath).section + callToActionCell.button.addTarget(self, action: #selector(QuestionAnswerViewController.submitAnswerPressed(_:)), for: .touchUpInside) + } + else { + callToActionCell.button.setTitle("Read \(numAnswers) answers", for: UIControlState()) + callToActionCell.setCustomLeftIcon(FAKFontAwesome.commentsIcon(withSize:)) + callToActionCell.button.removeTarget(nil, action: nil, for: .allEvents) + callToActionCell.button.tag = (indexPath as NSIndexPath).section + callToActionCell.button.addTarget(self, action: #selector(QuestionAnswerViewController.readAnswersTapped(_:)), for: .touchUpInside) + } + + return callToActionCell + + } + + } + + // MARK: UITableViewDelegate + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + tableView.deselectRow(at: indexPath, animated: true) + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.swift index 00214abf..82504947 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/RatingsAndReviewsViewController.swift @@ -12,320 +12,320 @@ import XLActionController class RatingsAndReviewsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { + + @IBOutlet weak var tableView: BVReviewsTableView! + @IBOutlet weak var header : ProductDetailHeaderView! + 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 + private var reviewFetchPending : Bool = false // Is an API call in progress + var totalReviewCount = 0 + + enum RatingsAndReviewsSections : Int { + case filter = 0 + case reviews - @IBOutlet weak var tableView: BVReviewsTableView! - @IBOutlet weak var header : ProductDetailHeaderView! - var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) + static func count() -> Int { + return 2 + } + } + + enum FilterOptions : Int { - 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 + case mostRecent = 0 + case highestRating + case lowestRating + case mostHelpful + case mostComments + case location // This is a filter, not a sort - private let numReviewToFetch : Int32 = 20 - private var reviewFetchPending : Bool = false // Is an API call in progress - var totalReviewCount = 0 + } + + private let filterActionTitles = ["Most Recent", "Highest Rating", "Lowest Rating", "Most Helpful", "Most Comments", "Location Filter"] + + private var selectedFilterOption : Int = FilterOptions.mostRecent.rawValue + + init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product:BVProduct, totalReviewCount: Int) { - enum RatingsAndReviewsSections : Int { - case filter = 0 - case reviews - - static func count() -> Int { - return 2 - } - } + self.totalReviewCount = totalReviewCount + self.product = product + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - enum FilterOptions : Int { - - case mostRecent = 0 - case highestRating - case lowestRating - case mostHelpful - case mostComments - case location // This is a filter, not a sort - - } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() - private let filterActionTitles = ["Most Recent", "Highest Rating", "Lowest Rating", "Most Helpful", "Most Comments", "Location Filter"] + ProfileUtils.trackViewController(self) - private var selectedFilterOption : Int = FilterOptions.mostRecent.rawValue + header.product = product - init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product:BVProduct, totalReviewCount: Int) { - - self.totalReviewCount = totalReviewCount - self.product = product - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - - } + let nib = UINib(nibName: "RatingTableViewCell", bundle: nil) + tableView.register(nib, forCellReuseIdentifier: "RatingTableViewCell") + tableView.estimatedRowHeight = 40 + tableView.rowHeight = UITableViewAutomaticDimension + tableView.allowsSelection = false - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + // add a Write Review button... + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Write a review", style: .plain, target: self, action: #selector(RatingsAndReviewsViewController.writeReviewTapped)) - override func viewDidLoad() { - super.viewDidLoad() - - ProfileUtils.trackViewController(self) - - header.product = product - - let nib = UINib(nibName: "RatingTableViewCell", bundle: nil) - tableView.register(nib, forCellReuseIdentifier: "RatingTableViewCell") - tableView.estimatedRowHeight = 40 - tableView.rowHeight = UITableViewAutomaticDimension - tableView.allowsSelection = false - - // add a Write Review button... - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Write a review", style: .plain, target: self, action: #selector(RatingsAndReviewsViewController.writeReviewTapped)) - - self.title = "Reviews" - - let nibConversationsCell = UINib(nibName: "ProductPageButtonCell", bundle: nil) - tableView.register(nibConversationsCell, forCellReuseIdentifier: "ProductPageButtonCell") - - self.loadReviews() - - } + self.title = "Reviews" - func writeReviewTapped() { - - let vc = WriteReviewViewController(nibName:"WriteReviewViewController", bundle: nil, product: product) - self.navigationController?.pushViewController(vc, animated: true) - - } + let nibConversationsCell = UINib(nibName: "ProductPageButtonCell", bundle: nil) + tableView.register(nibConversationsCell, forCellReuseIdentifier: "ProductPageButtonCell") - func loadReviews() { - - 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) - } else if selectedFilterOption == FilterOptions.lowestRating.rawValue { - 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) - } - - self.tableView.load(request, success: { (response) in - - self.spinner.removeFromSuperview() - - if (self.reviews.count == 0){ - self.reviews = response.results - } else { - self.reviews.append(contentsOf: response.results) - } - - - self.reviewFetchPending = false - self.tableView.reloadData() - - }) - { (errors) in - - print("An error occurred: \(errors)") - self.reviewFetchPending = false - - } - - } + self.loadReviews() - private func loadAuthorViewController(authorId: String) { - - let authorVC = AuthorProfileViewController(authorId: authorId) - self.navigationController?.pushViewController(authorVC, animated: true) - - } + } + + func writeReviewTapped() { - private func loadCommentsViewController(reviewComments: [BVComment]) { - - let reviewCommentsVC = ReviewCommentsViewController(reviewComments: reviewComments) - self.navigationController?.pushViewController(reviewCommentsVC, animated: true) - - } + let vc = WriteReviewViewController(nibName:"WriteReviewViewController", bundle: nil, product: product) + self.navigationController?.pushViewController(vc, animated: true) - // MARK: UITableViewDatasource + } + + func loadReviews() { - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - - var count = 0 - - switch section { - case RatingsAndReviewsSections.reviews.rawValue: - count = reviews.count - break - case RatingsAndReviewsSections.filter.rawValue: - count = 1 - break - default: - break - } - - return count - - } + reviewFetchPending = true - func numberOfSections(in tableView: UITableView) -> Int { - return RatingsAndReviewsSections.count() + let request = BVReviewsRequest(productId: product.identifier, limit: 20, offset: UInt(self.reviews.count)) + request.include(.reviewComments) + // Check sorting and filter FilterOptions + if selectedFilterOption == FilterOptions.highestRating.rawValue { + request.sort(by: .reviewRating, monotonicSortOrderValue: .descending) + } else if selectedFilterOption == FilterOptions.lowestRating.rawValue { + request.sort(by: .reviewRating, monotonicSortOrderValue: .ascending) + } else if selectedFilterOption == FilterOptions.mostHelpful.rawValue { + request.sort(by: .reviewHelpfulness, monotonicSortOrderValue: .descending) + } else if selectedFilterOption == FilterOptions.mostComments.rawValue { + request.sort(by: .reviewTotalCommentCount, monotonicSortOrderValue: .descending) } - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return 0 + self.tableView.load(request, success: { (response) in + + self.spinner.removeFromSuperview() + + if (self.reviews.count == 0){ + self.reviews = response.results + } else { + self.reviews.append(contentsOf: response.results) + } + + + self.reviewFetchPending = false + self.tableView.reloadData() + + }) + { (errors) in + + print("An error occurred: \(errors)") + self.reviewFetchPending = false + } - func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { - - if section == RatingsAndReviewsSections.reviews.rawValue { - return 22 - } - - - return 0 + } + + private func loadAuthorViewController(authorId: String) { + + 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 { + + var count = 0 + + switch section { + case RatingsAndReviewsSections.reviews.rawValue: + count = reviews.count + break + case RatingsAndReviewsSections.filter.rawValue: + count = 1 + break + default: + break } - func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { - - // TODO: Plain table styling has stickey footer so need fix that - // if section == RatingsAndReviewsSections.Reviews.rawValue && totalReviewCount <= self.reviews.count { - // return "End of Content" - // } - - return "" + return count + + } + + func numberOfSections(in tableView: UITableView) -> Int { + return RatingsAndReviewsSections.count() + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 0 + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + + if section == RatingsAndReviewsSections.reviews.rawValue { + return 22 } - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - let cell = UITableViewCell() - - switch (indexPath as NSIndexPath).section { - case RatingsAndReviewsSections.filter.rawValue: - - let cell = tableView.dequeueReusableCell(withIdentifier: "ProductPageButtonCell") as! ProductPageButtonCell - - cell.button.removeTarget(nil, action: nil, for: .allEvents) - cell.button.addTarget(self, action: #selector(RatingsAndReviewsViewController.filterReviewsButtonPressed), for: .touchUpInside) - cell.setCustomLeftIcon(FAKFontAwesome.sortIcon(withSize:)) - - let titlePrefix = selectedFilterOption == FilterOptions.location.rawValue ? "Filter:" : "Sort:" - cell.button.setTitle("\(titlePrefix) \(filterActionTitles[selectedFilterOption])", for: UIControlState()) - - return cell - - case RatingsAndReviewsSections.reviews.rawValue: - - let cell = tableView.dequeueReusableCell(withIdentifier: "RatingTableViewCell") as! RatingTableViewCell - 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: - break + + return 0 + } + + func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? { + + // TODO: Plain table styling has stickey footer so need fix that + // if section == RatingsAndReviewsSections.Reviews.rawValue && totalReviewCount <= self.reviews.count { + // return "End of Content" + // } + + return "" + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = UITableViewCell() + + switch (indexPath as NSIndexPath).section { + case RatingsAndReviewsSections.filter.rawValue: + + let cell = tableView.dequeueReusableCell(withIdentifier: "ProductPageButtonCell") as! ProductPageButtonCell + + cell.button.removeTarget(nil, action: nil, for: .allEvents) + cell.button.addTarget(self, action: #selector(RatingsAndReviewsViewController.filterReviewsButtonPressed), for: .touchUpInside) + cell.setCustomLeftIcon(FAKFontAwesome.sortIcon(withSize:)) + + let titlePrefix = selectedFilterOption == FilterOptions.location.rawValue ? "Filter:" : "Sort:" + cell.button.setTitle("\(titlePrefix) \(filterActionTitles[selectedFilterOption])", for: UIControlState()) + + return cell + + case RatingsAndReviewsSections.reviews.rawValue: + + let cell = tableView.dequeueReusableCell(withIdentifier: "RatingTableViewCell") as! RatingTableViewCell + 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 - + } + + return cell + + default: + break } - func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - - if (indexPath as NSIndexPath).row == 0 { return } - - let lastElement = reviews.count - 5 - if lastElement > 0 && (indexPath as NSIndexPath).row == lastElement { - - if totalReviewCount > reviews.count && !reviewFetchPending { - self.loadReviews() - print("should load more with current review count at = \(self.reviews.count)") - } - } - } + return cell - func filterReviewsButtonPressed() { - - let actionController = BVSDKDemoActionController() - - actionController.addAction(Action(filterActionTitles[FilterOptions.mostRecent.rawValue], style: .default, handler: { action in - self.didChangeFilterOption(FilterOptions.mostRecent) - })) - actionController.addAction(Action(filterActionTitles[FilterOptions.highestRating.rawValue], style: .default, handler: { action in - self.didChangeFilterOption(FilterOptions.highestRating) - })) - actionController.addAction(Action(filterActionTitles[FilterOptions.lowestRating.rawValue], style: .default, handler: { action in - self.didChangeFilterOption(FilterOptions.lowestRating) - })) - 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) - })) - actionController.addAction(Action("Cancel", style: .cancel, handler: nil)) - - present(actionController, animated: true, completion: nil) - - } + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - func didChangeFilterOption(_ option : FilterOptions){ - - if selectedFilterOption == option.rawValue { - return // ignore, didn't change anything - } - - self.tableView.reloadSections(IndexSet(integer: RatingsAndReviewsSections.filter.rawValue), with: .none) - selectedFilterOption = option.rawValue - print("Selected filter option: \(filterActionTitles[selectedFilterOption])") - - reviews = [] + if (indexPath as NSIndexPath).row == 0 { return } + + let lastElement = reviews.count - 5 + if lastElement > 0 && (indexPath as NSIndexPath).row == lastElement { + + if totalReviewCount > reviews.count && !reviewFetchPending { self.loadReviews() - + print("should load more with current review count at = \(self.reviews.count)") + } } + } + + func filterReviewsButtonPressed() { - // MARK: UITableViewDelegate + let actionController = BVSDKDemoActionController() - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - - // Currently nothing to do when selecting a review - // But we could add further review author details (e.g. profile view) - - return; - + actionController.addAction(Action(filterActionTitles[FilterOptions.mostRecent.rawValue], style: .default, handler: { action in + self.didChangeFilterOption(FilterOptions.mostRecent) + })) + actionController.addAction(Action(filterActionTitles[FilterOptions.highestRating.rawValue], style: .default, handler: { action in + self.didChangeFilterOption(FilterOptions.highestRating) + })) + actionController.addAction(Action(filterActionTitles[FilterOptions.lowestRating.rawValue], style: .default, handler: { action in + self.didChangeFilterOption(FilterOptions.lowestRating) + })) + 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) + })) + actionController.addAction(Action("Cancel", style: .cancel, handler: nil)) + + present(actionController, animated: true, completion: nil) + + } + + func didChangeFilterOption(_ option : FilterOptions){ + + if selectedFilterOption == option.rawValue { + return // ignore, didn't change anything } + self.tableView.reloadSections(IndexSet(integer: RatingsAndReviewsSections.filter.rawValue), with: .none) + selectedFilterOption = option.rawValue + print("Selected filter option: \(filterActionTitles[selectedFilterOption])") + + reviews = [] + self.loadReviews() + + } + + // MARK: UITableViewDelegate + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + // Currently nothing to do when selecting a review + // But we could add further review author details (e.g. profile view) + + return; + + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/ReviewCommentsViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/ReviewCommentsViewController.swift index 0db52982..c1fbc503 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/ReviewCommentsViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/ReviewCommentsViewController.swift @@ -9,65 +9,65 @@ import UIKit import BVSDK class ReviewCommentsViewController: UIViewController, UITableViewDataSource { - - @IBOutlet weak var commentsTableView: UITableView! + + @IBOutlet weak var commentsTableView: UITableView! + + let comments : [BVComment] + + init(reviewComments : [BVComment]) { - let comments : [BVComment] + self.comments = reviewComments + super.init(nibName: nil, bundle: nil) - init(reviewComments : [BVComment]) { - - self.comments = reviewComments - super.init(nibName: nil, bundle: nil) - - } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - - super.viewDidLoad() - self.commentsTableView.dataSource = self - self.title = "Review Comments" - - commentsTableView.estimatedRowHeight = 60 - commentsTableView.rowHeight = UITableViewAutomaticDimension - commentsTableView.allowsSelection = false - - let nib = UINib(nibName: "ReviewCommentTableViewCell", bundle: nil) - commentsTableView.register(nib, forCellReuseIdentifier: "ReviewCommentTableViewCell") - - } + super.viewDidLoad() + self.commentsTableView.dataSource = self + self.title = "Review Comments" - func numberOfSections(in tableView: UITableView) -> Int { - - return 1 - - } + commentsTableView.estimatedRowHeight = 60 + commentsTableView.rowHeight = UITableViewAutomaticDimension + commentsTableView.allowsSelection = false - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - - return self.comments.count - - } + let nib = UINib(nibName: "ReviewCommentTableViewCell", bundle: nil) + commentsTableView.register(nib, forCellReuseIdentifier: "ReviewCommentTableViewCell") - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - - let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewCommentTableViewCell") as! ReviewCommentTableViewCell - - cell.comment = self.comments[indexPath.row] - - cell.onAuthorNickNameTapped = { (authorId) -> Void in - let authorVC = AuthorProfileViewController(authorId: authorId) - self.navigationController?.pushViewController(authorVC, animated: true) - } - - - return cell + } + + func numberOfSections(in tableView: UITableView) -> Int { + + return 1 + + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + return self.comments.count + + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCell(withIdentifier: "ReviewCommentTableViewCell") as! ReviewCommentTableViewCell + + cell.comment = self.comments[indexPath.row] + + cell.onAuthorNickNameTapped = { (authorId) -> Void in + let authorVC = AuthorProfileViewController(authorId: authorId) + self.navigationController?.pushViewController(authorVC, animated: true) } - - - - + + + return cell + } + + + + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Conversations/WriteReviewViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Conversations/WriteReviewViewController.swift index 181b5274..349dea06 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Conversations/WriteReviewViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Conversations/WriteReviewViewController.swift @@ -11,248 +11,248 @@ import SDForms import FontAwesomeKit class WriteReviewViewController: UIViewController, SDFormDelegate, SDFormDataSource { + + let reviewSubmissionParameters = SubmissionParameterHolder() + + // For using SDFormField, this demo presumes one field item per section. + // Hence, the section header will contain the tile, and the row will just contain the widget + // and any placeholder text + var formFields : [SDFormField] = [] + var sectionTitles : [String] = [] + + var form : SDForm? + @IBOutlet weak var tableView: UITableView! + @IBOutlet weak var header : ProductDetailHeaderView! + + var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) + + private var product: BVProduct? + private var productId: String? + + init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product: BVProduct) { + self.product = product + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, productId: String) { + self.productId = productId - let reviewSubmissionParameters = SubmissionParameterHolder() + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() - // For using SDFormField, this demo presumes one field item per section. - // Hence, the section header will contain the tile, and the row will just contain the widget - // and any placeholder text - var formFields : [SDFormField] = [] - var sectionTitles : [String] = [] + if self.presentingViewController != nil { + self.navigationController?.navigationBar.isTranslucent = false + edgesForExtendedLayout = [] + self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(WriteReviewViewController.dismissSelf)) + } - var form : SDForm? - @IBOutlet weak var tableView: UITableView! - @IBOutlet weak var header : ProductDetailHeaderView! - - var spinner = Util.createSpinner(UIColor.bazaarvoiceNavy(), size: CGSize(width: 44,height: 44), padding: 0) - - private var product: BVProduct? - private var productId: String? + ProfileUtils.trackViewController(self) - init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, product: BVProduct) { - self.product = product - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } + self.title = "Write a Review" - init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?, productId: String) { - self.productId = productId - - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - } + self.view.backgroundColor = UIColor.appBackground() - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } + self.tableView.backgroundColor = UIColor.white - override func viewDidLoad() { - super.viewDidLoad() - - if self.presentingViewController != nil { - self.navigationController?.navigationBar.isTranslucent = false - edgesForExtendedLayout = [] - self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(WriteReviewViewController.dismissSelf)) - } - - ProfileUtils.trackViewController(self) - - self.title = "Write a Review" - - self.view.backgroundColor = UIColor.appBackground() - - self.tableView.backgroundColor = UIColor.white - - // form scrolling above keyboard - self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 300, right: 0) - - // add a SUBMIT button... - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Submit", style: .plain, target: self, action: #selector(WriteReviewViewController.submitTapped)) - - if let prod = product { - updateProductUI(product: prod) - }else { - loadProduct(productId: productId!) - } - } + // form scrolling above keyboard + self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 300, right: 0) - private func loadProduct(productId: String) { - DispatchQueue.main.async { - self.spinner.center = self.view.center - self.view.addSubview(self.spinner) - } - let request = BVProductDisplayPageRequest(productId: productId) - .includeStatistics(.reviews) - .includeStatistics(.questions) - request.load({ (response) in - self.product = response.result - self.updateProductUI(product: response.result!) - self.spinner.removeFromSuperview() - }){(errors) in - } - } + // add a SUBMIT button... + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Submit", style: .plain, target: self, action: #selector(WriteReviewViewController.submitTapped)) - private func updateProductUI(product: BVProduct) { - self.initFormFields() - header.product = product + if let prod = product { + updateProductUI(product: prod) + }else { + loadProduct(productId: productId!) } - - func submitTapped() { - - // NOTE: This sample doens't do field validation so we let the API do it for us. - // Typically your UI Controller would do some basic validation and guide your user on the required fields and field lengths. - - self.spinner.center = self.view.center - self.view.addSubview(self.spinner) - - // create a fill out the reviewSubmission object - let reviewSubmission = BVReviewSubmission(reviewTitle: self.reviewSubmissionParameters.title as? String ?? "", - reviewText: self.reviewSubmissionParameters.reviewText as? String ?? "", - rating: UInt(self.reviewSubmissionParameters.rating?.intValue ?? 0), - productId: self.product!.identifier) - - - // a working example of posting a review. - 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())" - - reviewSubmission.userNickname = self.reviewSubmissionParameters.userNickname as? String - reviewSubmission.userEmail = self.reviewSubmissionParameters.userEmail as? String - reviewSubmission.userId = userId - reviewSubmission.isRecommended = self.reviewSubmissionParameters.isRecommended - reviewSubmission.sendEmailAlertWhenPublished = self.reviewSubmissionParameters.sendEmailAlertWhenPublished - reviewSubmission.hostedAuthenticationEmail = self.reviewSubmissionParameters.userEmail as? String - if let photo = self.reviewSubmissionParameters.photo { - reviewSubmission.addPhoto(photo, withPhotoCaption: nil) - } - - reviewSubmission.submit({ (response) in - - DispatchQueue.main.async(execute: { - _ = SweetAlert().showAlert("Success!", subTitle: "Your review was submitted. It may take up to 72 hours before your post is live.", style: .success) - _ = self.navigationController?.popViewController(animated: true) - }) - - }) { (errors) in - - DispatchQueue.main.async(execute: { - _ = SweetAlert().showAlert("Error!", subTitle: errors.first?.localizedDescription, style: .error) - self.spinner.removeFromSuperview() - }) - - } - + } + + private func loadProduct(productId: String) { + DispatchQueue.main.async { + self.spinner.center = self.view.center + self.view.addSubview(self.spinner) + } + let request = BVProductDisplayPageRequest(productId: productId) + .includeStatistics(.reviews) + .includeStatistics(.questions) + request.load({ (response) in + self.product = response.result + self.updateProductUI(product: response.result!) + self.spinner.removeFromSuperview() + }){(errors) in } + } + + private func updateProductUI(product: BVProduct) { + self.initFormFields() + header.product = product + } + + func submitTapped() { + // NOTE: This sample doens't do field validation so we let the API do it for us. + // Typically your UI Controller would do some basic validation and guide your user on the required fields and field lengths. - func initFormFields(){ - - let recommendProductSwitch = SDSwitchField(object: reviewSubmissionParameters, relatedPropertyKey: "isRecommended") - recommendProductSwitch?.title = "I recommend this product." - - let ratingStars = SDRatingStarsField(object: reviewSubmissionParameters, relatedPropertyKey: "rating") - ratingStars?.maximumValue = 5 - ratingStars?.minimumValue = 0 - ratingStars?.starsColor = UIColor.bazaarvoiceGold() - - let reviewTitleField = SDTextFormField(object: reviewSubmissionParameters, relatedPropertyKey: "title") - reviewTitleField?.placeholder = "Add your review title" - - let reviewField = SDMultilineTextField(object: reviewSubmissionParameters, relatedPropertyKey: "reviewText") - reviewField?.placeholder = "Add your thoughts and experinces with this product." - - let nickNameField : SDTextFormField = SDTextFormField(object: reviewSubmissionParameters, relatedPropertyKey: "userNickname") - nickNameField.placeholder = "Display name for the question" - - let emailAddressField : SDTextFormField = SDTextFormField(object: reviewSubmissionParameters, relatedPropertyKey: "userEmail") - emailAddressField.placeholder = "Enter a valid email address." - - let emailOKSwitchField = SDSwitchField(object: reviewSubmissionParameters, relatedPropertyKey: "sendEmailAlertWhenPublished") - emailOKSwitchField?.title = "Send me status by email?" - - let photoField = SDPhotoField(object: reviewSubmissionParameters, relatedPropertyKey: "photo") - photoField?.presentingMode = SDFormFieldPresentingModeModal; - photoField?.title = "photo" - let cameraIcon = FAKFontAwesome.cameraIcon(withSize: 22) - cameraIcon?.addAttribute(NSForegroundColorAttributeName, value: UIColor.lightGray.withAlphaComponent(0.5)) - photoField?.callToActionImage = cameraIcon?.image(with: CGSize(width: 22, height: 22)) - - // Keep the formFields and sectionTitles in the same order if you switch them around. - self.formFields = [recommendProductSwitch!, ratingStars!, reviewTitleField!, reviewField!, nickNameField, emailAddressField, photoField!, emailOKSwitchField!] - self.sectionTitles = ["", "Rate this product", "Review Title", "Your Review", "Nickname", "Email Address", "Add a photo (optional)", "Please send me an email to keep me informed on the status of my review."] - - // set up delegate/datasource last! - self.form = SDForm(tableView: self.tableView) - self.form?.delegate = self - self.form?.dataSource = self - - } + self.spinner.center = self.view.center + self.view.addSubview(self.spinner) - func dismissSelf(){ - - if self.presentingViewController != nil { - self.presentingViewController?.dismiss(animated: true, completion: nil) - } - } - - // MARK: SDKFormDelegate, SDFormDataSource + // create a fill out the reviewSubmission object + let reviewSubmission = BVReviewSubmission(reviewTitle: self.reviewSubmissionParameters.title as? String ?? "", + reviewText: self.reviewSubmissionParameters.reviewText as? String ?? "", + rating: UInt(self.reviewSubmissionParameters.rating?.intValue ?? 0), + productId: self.product!.identifier) - - func form(_ form: SDForm!, willDisplayHeaderView view: UIView!, forSection section: Int) { - - let hv = view as! UITableViewHeaderFooterView - let color = UIColor.white - hv.tintColor = color - hv.contentView.backgroundColor = color - hv.textLabel?.textColor = UIColor.bazaarvoiceNavy() - - } - func form(_ form: SDForm!, willDisplayFooterView view: UIView!, forSection section: Int) { - let fv = view as! UITableViewHeaderFooterView - fv.contentView.backgroundColor = UIColor.white - } + // a working example of posting a review. + reviewSubmission.action = BVSubmissionAction.preview // Don't actually post, just run in preview mode! - func numberOfSections(for form: SDForm!) -> Int { - return (self.formFields.count) - } + // We need to use the same userId for both the photo post and review content + let userId = "123abc\(arc4random())" - func form(_ form: SDForm!, numberOfFieldsInSection section: Int) -> Int { - return 1 + reviewSubmission.userNickname = self.reviewSubmissionParameters.userNickname as? String + reviewSubmission.userEmail = self.reviewSubmissionParameters.userEmail as? String + reviewSubmission.userId = userId + reviewSubmission.isRecommended = self.reviewSubmissionParameters.isRecommended + reviewSubmission.sendEmailAlertWhenPublished = self.reviewSubmissionParameters.sendEmailAlertWhenPublished + reviewSubmission.hostedAuthenticationEmail = self.reviewSubmissionParameters.userEmail as? String + if let photo = self.reviewSubmissionParameters.photo { + reviewSubmission.addPhoto(photo, withPhotoCaption: nil) } - func form(_ form: SDForm!, titleForFooterInSection section: Int) -> String! { - return "" + reviewSubmission.submit({ (response) in + + DispatchQueue.main.async(execute: { + _ = SweetAlert().showAlert("Success!", subTitle: "Your review was submitted. It may take up to 72 hours before your post is live.", style: .success) + _ = self.navigationController?.popViewController(animated: true) + }) + + }) { (errors) in + + DispatchQueue.main.async(execute: { + _ = SweetAlert().showAlert("Error!", subTitle: errors.first?.localizedDescription, style: .error) + self.spinner.removeFromSuperview() + }) + } - func form(_ form: SDForm!, titleForHeaderInSection section: Int) -> String! { - return self.sectionTitles[section] - } + } + + + func initFormFields(){ - func form(_ form: SDForm!, fieldForRow row: Int, inSection section: Int) -> SDFormField! { - - return self.formFields[section] - - } + let recommendProductSwitch = SDSwitchField(object: reviewSubmissionParameters, relatedPropertyKey: "isRecommended") + recommendProductSwitch?.title = "I recommend this product." + + let ratingStars = SDRatingStarsField(object: reviewSubmissionParameters, relatedPropertyKey: "rating") + ratingStars?.maximumValue = 5 + ratingStars?.minimumValue = 0 + ratingStars?.starsColor = UIColor.bazaarvoiceGold() + + let reviewTitleField = SDTextFormField(object: reviewSubmissionParameters, relatedPropertyKey: "title") + reviewTitleField?.placeholder = "Add your review title" + + let reviewField = SDMultilineTextField(object: reviewSubmissionParameters, relatedPropertyKey: "reviewText") + reviewField?.placeholder = "Add your thoughts and experinces with this product." + + let nickNameField : SDTextFormField = SDTextFormField(object: reviewSubmissionParameters, relatedPropertyKey: "userNickname") + nickNameField.placeholder = "Display name for the question" + + let emailAddressField : SDTextFormField = SDTextFormField(object: reviewSubmissionParameters, relatedPropertyKey: "userEmail") + emailAddressField.placeholder = "Enter a valid email address." + + let emailOKSwitchField = SDSwitchField(object: reviewSubmissionParameters, relatedPropertyKey: "sendEmailAlertWhenPublished") + emailOKSwitchField?.title = "Send me status by email?" + + let photoField = SDPhotoField(object: reviewSubmissionParameters, relatedPropertyKey: "photo") + photoField?.presentingMode = SDFormFieldPresentingModeModal; + photoField?.title = "photo" + let cameraIcon = FAKFontAwesome.cameraIcon(withSize: 22) + cameraIcon?.addAttribute(NSForegroundColorAttributeName, value: UIColor.lightGray.withAlphaComponent(0.5)) + photoField?.callToActionImage = cameraIcon?.image(with: CGSize(width: 22, height: 22)) + + // Keep the formFields and sectionTitles in the same order if you switch them around. + self.formFields = [recommendProductSwitch!, ratingStars!, reviewTitleField!, reviewField!, nickNameField, emailAddressField, photoField!, emailOKSwitchField!] + self.sectionTitles = ["", "Rate this product", "Review Title", "Your Review", "Nickname", "Email Address", "Add a photo (optional)", "Please send me an email to keep me informed on the status of my review."] - func viewController(for form: SDForm!) -> UIViewController! { - return self; + // set up delegate/datasource last! + self.form = SDForm(tableView: self.tableView) + self.form?.delegate = self + self.form?.dataSource = self + + } + + func dismissSelf(){ + + if self.presentingViewController != nil { + self.presentingViewController?.dismiss(animated: true, completion: nil) } - - -} - -@objc class SubmissionParameterHolder : NSObject { + } + + // MARK: SDKFormDelegate, SDFormDataSource + + + func form(_ form: SDForm!, willDisplayHeaderView view: UIView!, forSection section: Int) { - var rating : NSNumber? - var title : NSString? - var reviewText : NSString? - var userNickname : NSString? - var userEmail : NSString? + let hv = view as! UITableViewHeaderFooterView + let color = UIColor.white + hv.tintColor = color + hv.contentView.backgroundColor = color + hv.textLabel?.textColor = UIColor.bazaarvoiceNavy() - var isRecommended:NSNumber? - var sendEmailAlertWhenPublished:NSNumber? + } + + func form(_ form: SDForm!, willDisplayFooterView view: UIView!, forSection section: Int) { + let fv = view as! UITableViewHeaderFooterView + fv.contentView.backgroundColor = UIColor.white + } + + func numberOfSections(for form: SDForm!) -> Int { + return (self.formFields.count) + } + + func form(_ form: SDForm!, numberOfFieldsInSection section: Int) -> Int { + return 1 + } + + func form(_ form: SDForm!, titleForFooterInSection section: Int) -> String! { + return "" + } + + func form(_ form: SDForm!, titleForHeaderInSection section: Int) -> String! { + return self.sectionTitles[section] + } + + func form(_ form: SDForm!, fieldForRow row: Int, inSection section: Int) -> SDFormField! { - var photo : UIImage? + return self.formFields[section] + } + + func viewController(for form: SDForm!) -> UIViewController! { + return self; + } + + +} + +@objc class SubmissionParameterHolder : NSObject { + + var rating : NSNumber? + var title : NSString? + var reviewText : NSString? + var userNickname : NSString? + var userEmail : NSString? + + var isRecommended:NSNumber? + var sendEmailAlertWhenPublished:NSNumber? + + var photo : UIImage? + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/ConversationsModel.swift b/Examples/BVSDKDemo/BVSDKDemo/ConversationsModel.swift index 06f4aa14..3e4e5c98 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/ConversationsModel.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/ConversationsModel.swift @@ -408,4 +408,4 @@ // // } // -//} \ No newline at end of file +//} diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/BaseDemoComposeServiceViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/BaseDemoComposeServiceViewController.swift index c89fea3f..f52980ca 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/BaseDemoComposeServiceViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/BaseDemoComposeServiceViewController.swift @@ -13,100 +13,100 @@ import BVSDK // This base class extends SLComposeServiceViewController, used for customizing photo posts to Curations. // Both the in-app and extension demos in this app use this base class for customizing the post view controller UI. class BaseDemoComposeServiceViewController: SLComposeServiceViewController { - - var postRequest : BVCurationsAddPostRequest? + + var postRequest : BVCurationsAddPostRequest? + + convenience init!(shareRequest:BVCurationsAddPostRequest) { - convenience init!(shareRequest:BVCurationsAddPostRequest) { - - self.init() - self.postRequest = shareRequest - } + self.init() + self.postRequest = shareRequest + } + + override func viewDidLoad() { + super.viewDidLoad() - override func viewDidLoad() { - super.viewDidLoad() - - self.configureCustomNaviBar() - - self.validateContent() - - } - - override func didSelectCancel() { - super.dismiss(animated: true, completion: nil) - } + self.configureCustomNaviBar() - override func isContentValid() -> Bool { - // Do validation of contentText and/or NSExtensionContext attachments here - super.isContentValid() - if (self.textView.text.count > 0){ - return true - } - - return false - } + self.validateContent() - override func loadPreviewView() -> UIView! { - - if (self.postRequest != nil && self.postRequest?.image != nil) { - let resizedImage = self.imageWithImage((self.postRequest?.image)!, scaledToSize: CGSize(width: 80,height: 80)) - return UIImageView(image: resizedImage) - } else { - // No postRequest/image was provided so call the super as this class was likely loaded from an extension. - return super.loadPreviewView() - } - - } - - func configureCustomNaviBar() { - - self.navigationController?.navigationBar.tintColor = self.navibarTintColor() - let navSize = self.navigationController?.navigationBar.frame.size - self.navigationController?.navigationBar.setBackgroundImage(getTopWithColor(self.navibarBackgroundColor(), size: navSize!), for: .default) - + } + + override func didSelectCancel() { + super.dismiss(animated: true, completion: nil) + } + + override func isContentValid() -> Bool { + // Do validation of contentText and/or NSExtensionContext attachments here + super.isContentValid() + if (self.textView.text.count > 0){ + return true } - - func navibarBackgroundColor() -> UIColor { - - // bazaarvoice navy - return UIColor(red: 0, green: 0.24, blue: 0.3, alpha: 1.0) - - } + return false + } + + override func loadPreviewView() -> UIView! { - func navibarTintColor() -> UIColor { - - // white color - return UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + if (self.postRequest != nil && self.postRequest?.image != nil) { + let resizedImage = self.imageWithImage((self.postRequest?.image)!, scaledToSize: CGSize(width: 80,height: 80)) + return UIImageView(image: resizedImage) + } else { + // No postRequest/image was provided so call the super as this class was likely loaded from an extension. + return super.loadPreviewView() } - // Provide the logo for the navigation bar - func getTopWithColor(_ color: UIColor, size: CGSize) -> UIImage { - let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) - UIGraphicsBeginImageContextWithOptions(size, false, 0) - color.setFill() - UIRectFill(rect) - if let img = UIImage(named: "icon_bvlogo") { - img.draw(in: CGRect(x: (size.width/2) - (size.height / 2) - 16, y: 1, width: size.height - 2, height: size.height - 2)) - } - let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! - UIGraphicsEndImageContext() - return image + } + + func configureCustomNaviBar() { + + self.navigationController?.navigationBar.tintColor = self.navibarTintColor() + let navSize = self.navigationController?.navigationBar.frame.size + self.navigationController?.navigationBar.setBackgroundImage(getTopWithColor(self.navibarBackgroundColor(), size: navSize!), for: .default) + + } + + + func navibarBackgroundColor() -> UIColor { + + // bazaarvoice navy + return UIColor(red: 0, green: 0.24, blue: 0.3, alpha: 1.0) + + } + + func navibarTintColor() -> UIColor { + + // white color + return UIColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + } + + // Provide the logo for the navigation bar + func getTopWithColor(_ color: UIColor, size: CGSize) -> UIImage { + let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height) + UIGraphicsBeginImageContextWithOptions(size, false, 0) + color.setFill() + UIRectFill(rect) + if let img = UIImage(named: "icon_bvlogo") { + img.draw(in: CGRect(x: (size.width/2) - (size.height / 2) - 16, y: 1, width: size.height - 2, height: size.height - 2)) } + let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + return image + } + + // Resize a given image to newSize + func imageWithImage(_ image:UIImage, scaledToSize newSize:CGSize) -> UIImage{ + UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0); + image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) - // Resize a given image to newSize - func imageWithImage(_ image:UIImage, scaledToSize newSize:CGSize) -> UIImage{ - UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0); - image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) - - if let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext(){ - UIGraphicsEndImageContext() - return newImage - } else { - // wasn't able to get graphics context to scale image - print("Error getting graphics context.") - UIGraphicsEndImageContext() - return image - } - + if let newImage:UIImage = UIGraphicsGetImageFromCurrentImageContext(){ + UIGraphicsEndImageContext() + return newImage + } else { + // wasn't able to get graphics context to scale image + print("Error getting graphics context.") + UIGraphicsEndImageContext() + return image } + + } } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/DemoCustomPostViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/DemoCustomPostViewController.swift index f9a40180..52f180a5 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/DemoCustomPostViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/DemoCustomPostViewController.swift @@ -14,135 +14,135 @@ let SPINNER_DIMENSION : CGFloat = 200.0 class DemoCustomPostViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { + + let shareRequest : BVCurationsAddPostRequest? + var imagePicker = UIImagePickerController() + var placeholderText : String = "" + + let spinner = Util.createSpinner(UIColor.bazaarvoiceTeal(), size: CGSize(width: SPINNER_HEIGHT_WIDTH, height: SPINNER_HEIGHT_WIDTH), padding: 50) + + init(shareRequest: BVCurationsAddPostRequest, placeholderText : String) { + self.shareRequest = shareRequest + self.placeholderText = placeholderText + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) - let shareRequest : BVCurationsAddPostRequest? - var imagePicker = UIImagePickerController() - var placeholderText : String = "" - - let spinner = Util.createSpinner(UIColor.bazaarvoiceTeal(), size: CGSize(width: SPINNER_HEIGHT_WIDTH, height: SPINNER_HEIGHT_WIDTH), padding: 50) - - init(shareRequest: BVCurationsAddPostRequest, placeholderText : String) { - self.shareRequest = shareRequest - self.placeholderText = placeholderText - super.init(nibName: nil, bundle: nil) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") + if (self.isBeingPresented){ + self.uploadPhoto() } + } + + func uploadPhoto(){ - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) + if Platform.isSimulator { + + self.selectPhotoFromGallery() + + } else { + + let optionsMenu = UIAlertController(title: nil, message: "Choose a photo source...", preferredStyle: .actionSheet) + + let closure = { (action: UIAlertAction!) -> Void in - if (self.isBeingPresented){ - self.uploadPhoto() - } - } - - func uploadPhoto(){ + let index : Int = optionsMenu.actions.index(of: action)! - if Platform.isSimulator { - - self.selectPhotoFromGallery() - - } else { - - let optionsMenu = UIAlertController(title: nil, message: "Choose a photo source...", preferredStyle: .actionSheet) - - let closure = { (action: UIAlertAction!) -> Void in - - let index : Int = optionsMenu.actions.index(of: action)! - - switch index { - - case 0: - self.selectPhotoFromCamera() - - case 1: - self.selectPhotoFromGallery() - - case 2: - self.presentingViewController?.dismiss(animated: true, completion: nil) - - default: - // ignored - break - - } - - } - - optionsMenu.addAction(UIAlertAction(title: "Camera", style: .default, handler: closure)) - optionsMenu.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: closure)) - optionsMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: closure)) - - self.present(optionsMenu, animated: true, completion: nil) - + switch index { + + case 0: + self.selectPhotoFromCamera() + + case 1: + self.selectPhotoFromGallery() + + case 2: + self.presentingViewController?.dismiss(animated: true, completion: nil) + + default: + // ignored + break + } + } + + optionsMenu.addAction(UIAlertAction(title: "Camera", style: .default, handler: closure)) + optionsMenu.addAction(UIAlertAction(title: "Photo Library", style: .default, handler: closure)) + optionsMenu.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: closure)) + + self.present(optionsMenu, animated: true, completion: nil) + } - func selectPhotoFromCamera() { - - if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera){ - imagePicker.delegate = self - imagePicker.sourceType = UIImagePickerControllerSourceType.camera; - imagePicker.allowsEditing = true - self.present(imagePicker, animated: true, completion: nil) - } - + } + + func selectPhotoFromCamera() { + + if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera){ + imagePicker.delegate = self + imagePicker.sourceType = UIImagePickerControllerSourceType.camera; + imagePicker.allowsEditing = true + self.present(imagePicker, animated: true, completion: nil) } - func selectPhotoFromGallery() { - - if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.savedPhotosAlbum){ - - imagePicker.delegate = self - imagePicker.sourceType = UIImagePickerControllerSourceType.photoLibrary; - imagePicker.allowsEditing = true - self.present(imagePicker, animated: true, completion: nil) - } - + } + + func selectPhotoFromGallery() { + + if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.savedPhotosAlbum){ + + imagePicker.delegate = self + imagePicker.sourceType = UIImagePickerControllerSourceType.photoLibrary; + imagePicker.allowsEditing = true + self.present(imagePicker, animated: true, completion: nil) } - // MARK: UIImagePickerControllerDelegate - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { - - // image selected... - shareRequest?.image = info[UIImagePickerControllerEditedImage] as! UIImage - // Post an image with a SLComposeServiceViewController - - let shareVC = BVCurationsPostViewController.init(postRequest: self.shareRequest!, logoImage: UIImage(named: "icon_bvlogo")!, bavBarColor: UIColor.bazaarvoiceNavy(), navBarTintColor: UIColor.white) - - shareVC.placeholder = "Enter text" - shareVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext; - - shareVC.didPressCancel = { - self.presentingViewController?.dismiss(animated: true, completion: nil) - } - - shareVC.didBeginPost = { - self.spinner.frame.origin = CGPoint(x: self.view.frame.width/2 - SPINNER_HEIGHT_WIDTH/2, y: self.view.frame.height/4) - self.view.addSubview(self.spinner) - } - - shareVC.didCompletePost = {(error) in - self.presentingViewController?.dismiss(animated: true, completion: nil) - self.spinner.removeFromSuperview() - } - - imagePicker.dismiss(animated: true) { () -> Void in - - self.present(shareVC, animated: true) { () -> Void in - // completion - } - - } + } + + // MARK: UIImagePickerControllerDelegate + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { + + // image selected... + shareRequest?.image = info[UIImagePickerControllerEditedImage] as! UIImage + // Post an image with a SLComposeServiceViewController + + let shareVC = BVCurationsPostViewController.init(postRequest: self.shareRequest!, logoImage: UIImage(named: "icon_bvlogo")!, bavBarColor: UIColor.bazaarvoiceNavy(), navBarTintColor: UIColor.white) + + shareVC.placeholder = "Enter text" + shareVC.modalPresentationStyle = UIModalPresentationStyle.overCurrentContext; + + shareVC.didPressCancel = { + self.presentingViewController?.dismiss(animated: true, completion: nil) } - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - self.presentingViewController?.dismiss(animated: true, completion: nil) + shareVC.didBeginPost = { + self.spinner.frame.origin = CGPoint(x: self.view.frame.width/2 - SPINNER_HEIGHT_WIDTH/2, y: self.view.frame.height/4) + self.view.addSubview(self.spinner) } + shareVC.didCompletePost = {(error) in + self.presentingViewController?.dismiss(animated: true, completion: nil) + self.spinner.removeFromSuperview() + } + imagePicker.dismiss(animated: true) { () -> Void in + + self.present(shareVC, animated: true) { () -> Void in + // completion + } + + } + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + self.presentingViewController?.dismiss(animated: true, completion: nil) + } + + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/ShareViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/ShareViewController.swift index 471f3084..6fc7d85c 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/ShareViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/Curations Custom Post Extension/ShareViewController.swift @@ -13,104 +13,104 @@ let SPINNER_HEIGHT_WIDTH : CGFloat = 200.0 // This class demonstrates using a custom SLComposeServiceViewController within an application to post a comment and photo to Curations. class ShareViewController: BaseDemoComposeServiceViewController { - - let spinner = Util.createSpinner(UIColor.bazaarvoiceTeal(), size: CGSize(width: SPINNER_HEIGHT_WIDTH, height: SPINNER_HEIGHT_WIDTH), padding: 50) + + let spinner = Util.createSpinner(UIColor.bazaarvoiceTeal(), size: CGSize(width: SPINNER_HEIGHT_WIDTH, height: SPINNER_HEIGHT_WIDTH), padding: 50) + + var onDismissComplete: (() -> Void)? + + override func didSelectCancel() { + self.dismissSelf() + } + + override func didSelectPost() { - var onDismissComplete: (() -> Void)? - - override func didSelectCancel() { - self.dismissSelf() - } + postRequest?.text = self.textView.text + + super.didSelectPost() + // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. - override func didSelectPost() { + // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context. + if self.extensionContext != nil { + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + } else { + + self.spinner.frame.origin = CGPoint(x: self.view.frame.width/2 - SPINNER_HEIGHT_WIDTH/2, y: self.view.frame.height/4) + self.view.addSubview(self.spinner) + + // completion + let uploadAPI = BVCurationsPhotoUploader() + + // Upload the photo with the request! + uploadAPI.submitCurationsContent(withParams: self.postRequest, completionHandler: { (void) -> Void in - postRequest?.text = self.textView.text + // Success - super.didSelectPost() - // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. - - // Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context. - if self.extensionContext != nil { - self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) - } else { - - self.spinner.frame.origin = CGPoint(x: self.view.frame.width/2 - SPINNER_HEIGHT_WIDTH/2, y: self.view.frame.height/4) - self.view.addSubview(self.spinner) - - // completion - let uploadAPI = BVCurationsPhotoUploader() - - // Upload the photo with the request! - uploadAPI.submitCurationsContent(withParams: self.postRequest, completionHandler: { (void) -> Void in - - // Success - - self.spinner.removeFromSuperview() - - _ = SweetAlert().showAlert("Success!", subTitle: "Your photo was successfully submitted and is pending approval. Please allow up to 72 hours for your photo to appear on our website.", style: AlertStyle.success, buttonTitle: "OK", action: { (isOtherButton) -> Void in - - // completion - self.dismiss(animated: true, completion: { () -> Void in - if let callback = self.onDismissComplete { - callback () - } - }) - - }) - - }) { (error) -> Void in - - // Error - self.spinner.removeFromSuperview() - _ = SweetAlert().showAlert("Error Submitting Photo!", subTitle: error?.localizedDescription, style: AlertStyle.error, buttonTitle: "OK", action: { (isOtherButton) -> Void in - - // completion - self.dismiss(animated: true, completion: { () -> Void in - if let callback = self.onDismissComplete { - callback () - } - }) - - }) - + self.spinner.removeFromSuperview() + + _ = SweetAlert().showAlert("Success!", subTitle: "Your photo was successfully submitted and is pending approval. Please allow up to 72 hours for your photo to appear on our website.", style: AlertStyle.success, buttonTitle: "OK", action: { (isOtherButton) -> Void in + + // completion + self.dismiss(animated: true, completion: { () -> Void in + if let callback = self.onDismissComplete { + callback () } - - } - } - - override func configurationItems() -> [Any]! { - // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. - guard let aliasConfigItem = SLComposeSheetConfigurationItem() else { return nil } - aliasConfigItem.title = "Username" - aliasConfigItem.value = self.postRequest?.alias + }) + + }) -// Example how you might navigate to a UIViewController with an edit field... -// aliasConfigItem.tapHandler = { -// -// let aliasEditViewController = UIViewController() -// aliasEditViewController.navigationController?.title = "Alias" -// -// let textField = UITextField(frame: CGRectMake(10,10,self.view.frame.width - 50,50)) -// textField.borderStyle = UITextBorderStyle.RoundedRect; -// textField.placeholder = "enter your alias"; -// textField.keyboardType = UIKeyboardType.Default; -// textField.returnKeyType = UIReturnKeyType.Done; -// aliasEditViewController.view.addSubview(textField) -// -// self.pushConfigurationViewController(aliasEditViewController) -// } + }) { (error) -> Void in - return [aliasConfigItem] - } - - - func dismissSelf() { - self.dismiss(animated: true, completion: { () -> Void in - if let handler = self.onDismissComplete { - handler () + // Error + self.spinner.removeFromSuperview() + _ = SweetAlert().showAlert("Error Submitting Photo!", subTitle: error.localizedDescription, style: AlertStyle.error, buttonTitle: "OK", action: { (isOtherButton) -> Void in + + // completion + self.dismiss(animated: true, completion: { () -> Void in + if let callback = self.onDismissComplete { + callback () } + }) + }) + + } + } - + } + + override func configurationItems() -> [Any]! { + // To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here. + guard let aliasConfigItem = SLComposeSheetConfigurationItem() else { return nil } + aliasConfigItem.title = "Username" + aliasConfigItem.value = self.postRequest?.alias + + // Example how you might navigate to a UIViewController with an edit field... + // aliasConfigItem.tapHandler = { + // + // let aliasEditViewController = UIViewController() + // aliasEditViewController.navigationController?.title = "Alias" + // + // let textField = UITextField(frame: CGRectMake(10,10,self.view.frame.width - 50,50)) + // textField.borderStyle = UITextBorderStyle.RoundedRect; + // textField.placeholder = "enter your alias"; + // textField.keyboardType = UIKeyboardType.Default; + // textField.returnKeyType = UIReturnKeyType.Done; + // aliasEditViewController.view.addSubview(textField) + // + // self.pushConfigurationViewController(aliasEditViewController) + // } + return [aliasConfigItem] + } + + + func dismissSelf() { + self.dismiss(animated: true, completion: { () -> Void in + if let handler = self.onDismissComplete { + handler () + } + }) + } + + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/CurationsDemoConstants.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/CurationsDemoConstants.swift index 1a36f102..89947909 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/CurationsDemoConstants.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/CurationsDemoConstants.swift @@ -9,21 +9,21 @@ import Foundation import BVSDK struct CurationsDemoConstants { + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // FIXME: Set up your client id and API key here first! + + static let API_KEY_CURATIONS = "REPLACE_ME" // <--- Add your Curations API key here. + static let CLIENT_ID = "REPLACE_ME" // <--- Add your Curations Client ID here + static let DEFAULT_FEED_GROUPS_CURATIONS = ["__all__"] // This group is used as the default for both posting custom content and fetching feeds. Search the code for this use to customize your own for testing. + static let BVSDK_IS_STAGING = true // Set to false for production! + + //////////////////////////////////////////////////////////// + + static func isSDKConfigured() -> Bool { - // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - // FIXME: Set up your client id and API key here first! - - static let API_KEY_CURATIONS = "REPLACE_ME" // <--- Add your Curations API key here. - static let CLIENT_ID = "REPLACE_ME" // <--- Add your Curations Client ID here - static let DEFAULT_FEED_GROUPS_CURATIONS = ["__all__"] // This group is used as the default for both posting custom content and fetching feeds. Search the code for this use to customize your own for testing. - static let BVSDK_IS_STAGING = true // Set to false for production! - - //////////////////////////////////////////////////////////// - - static func isSDKConfigured() -> Bool { - - return (MockDataManager.sharedInstance.currentConfig.curationsKey != "REPLACE_ME" && - MockDataManager.sharedInstance.currentConfig.clientId != "REPLACE_ME") - } - + return (MockDataManager.sharedInstance.currentConfig.curationsKey != "REPLACE_ME" && + MockDataManager.sharedInstance.currentConfig.clientId != "REPLACE_ME") + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/CurationsPhotoMapViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/CurationsPhotoMapViewController.swift index cc78ef62..3b44f010 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/CurationsPhotoMapViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/CurationsPhotoMapViewController.swift @@ -13,217 +13,217 @@ import BVSDK import SDWebImage class CurationsPhotoMapViewController: UIViewController, MKMapViewDelegate { - - let distanceThreshold = 120000.0 - let minGroupZoom = 0.4 - let storeZoom = 0.025 - var hasShownDefault = false - var curationsFeed : [BVCurationsFeedItem] = [] - @IBOutlet var mapView: MKMapView! - @IBOutlet var zoomOutBtn: UIButton! - @IBOutlet var btnImage: UIImageView! - - var groupedFeedItems = [[BVCurationsFeedItem]]() - var previousRegion: MKCoordinateRegion? - - override func viewDidLoad() { - super.viewDidLoad() - self.title = "Curations Photo Map" - btnImage.image = btnImage.image?.withRenderingMode(.alwaysTemplate) - btnImage.tintColor = UIColor.red - btnImage.backgroundColor = UIColor.white - btnImage.layer.cornerRadius = btnImage.frame.width / 2 - mapView.addAnnotations(self.annotations) + + let distanceThreshold = 120000.0 + let minGroupZoom = 0.4 + let storeZoom = 0.025 + var hasShownDefault = false + var curationsFeed : [BVCurationsFeedItem] = [] + @IBOutlet var mapView: MKMapView! + @IBOutlet var zoomOutBtn: UIButton! + @IBOutlet var btnImage: UIImageView! + + var groupedFeedItems = [[BVCurationsFeedItem]]() + var previousRegion: MKCoordinateRegion? + + override func viewDidLoad() { + super.viewDidLoad() + self.title = "Curations Photo Map" + btnImage.image = btnImage.image?.withRenderingMode(.alwaysTemplate) + btnImage.tintColor = UIColor.red + btnImage.backgroundColor = UIColor.white + btnImage.layer.cornerRadius = btnImage.frame.width / 2 + mapView.addAnnotations(self.annotations) + } + + override func viewDidAppear(_ animated: Bool) { + if !hasShownDefault { + hasShownDefault = true + /* + + guard let storeId = LocationPreferenceUtils.getDefaultStoreId() else { + return + } + + // TODO: This should be fetching a store with lat/long from the default store selected. + for ann in annotations { + if ann.coordinate.latitude == Double(store.latitude) { + mapView.selectAnnotation(ann, animated: true) + break + } + } + */ } + } + + lazy var annotations: [JPSThumbnailAnnotation] = { + [unowned self] in - override func viewDidAppear(_ animated: Bool) { - if !hasShownDefault { - hasShownDefault = true - /* - - guard let storeId = LocationPreferenceUtils.getDefaultStoreId() else { - return - } - - // TODO: This should be fetching a store with lat/long from the default store selected. - for ann in annotations { - if ann.coordinate.latitude == Double(store.latitude) { - mapView.selectAnnotation(ann, animated: true) - break - } - } - */ - } - } + var annotationThumbs : [JPSThumbnailAnnotation] = [] - lazy var annotations: [JPSThumbnailAnnotation] = { - [unowned self] in - - var annotationThumbs : [JPSThumbnailAnnotation] = [] - - var idx : Int = 0 - for feedItem in self.curationsFeed { - let location = feedItem.coordinates - - if let _ = location?.longitude, let _ = location?.latitude, let _ = feedItem.photos{ - if feedItem.photos.count == 0 { - continue; // skip anything that doesn't have any image - } - - self.groupFeedItem(feedItem) - - let i = idx; - let annotation = JPSThumbnailAnnotation(curationsFeedItem: feedItem) { - // navigate to the curations lightbox from the user-selected image from the map annotation - let targetVC = CurationsFeedMasterViewController() - targetVC.socialFeedItems = self.curationsFeed - targetVC.startIndex = i - self.navigationController?.pushViewController(targetVC, animated: true) - } - - annotationThumbs.append(annotation) - idx += 1 - } + var idx : Int = 0 + for feedItem in self.curationsFeed { + let location = feedItem.coordinates + + if let _ = location?.longitude, let _ = location?.latitude, let _ = feedItem.photos{ + if feedItem.photos.count == 0 { + continue; // skip anything that doesn't have any image } - return annotationThumbs - }() - - private func groupFeedItem(_ feedItem: BVCurationsFeedItem){ - var itemGroup : [BVCurationsFeedItem]? - for group in groupedFeedItems { - for item in group { - if getDistanceBetween(feedItem, feedItem2: item) < distanceThreshold { - itemGroup = group - let idx = groupedFeedItems.index(where: {$0 == itemGroup!}) - groupedFeedItems.remove(at: idx!) - break - } - } - if let _ = itemGroup { - break - } - } + self.groupFeedItem(feedItem) - if itemGroup == nil { - itemGroup = [BVCurationsFeedItem]() + let i = idx; + let annotation = JPSThumbnailAnnotation(curationsFeedItem: feedItem) { + // navigate to the curations lightbox from the user-selected image from the map annotation + let targetVC = CurationsFeedMasterViewController() + targetVC.socialFeedItems = self.curationsFeed + targetVC.startIndex = i + self.navigationController?.pushViewController(targetVC, animated: true) } - itemGroup?.append(feedItem) - groupedFeedItems.append(itemGroup!) + annotationThumbs.append(annotation) + idx += 1 + } } - private func getDistanceBetween(_ feedItem1: BVCurationsFeedItem, feedItem2: BVCurationsFeedItem) -> Double { - let loc1 = CLLocation(latitude: feedItem1.coordinates.latitude.doubleValue, longitude: feedItem1.coordinates.longitude.doubleValue) - let loc2 = CLLocation(latitude: feedItem2.coordinates.latitude.doubleValue, longitude: feedItem2.coordinates.longitude.doubleValue) - - return loc1.distance(from: loc2) - } - - // MARK: MKMapViewDelegate - func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { - if !animated { - shouldHideZoomout(false) + return annotationThumbs + }() + + private func groupFeedItem(_ feedItem: BVCurationsFeedItem){ + var itemGroup : [BVCurationsFeedItem]? + for group in groupedFeedItems { + for item in group { + if getDistanceBetween(feedItem, feedItem2: item) < distanceThreshold { + itemGroup = group + let idx = groupedFeedItems.index(where: {$0 == itemGroup!}) + groupedFeedItems.remove(at: idx!) + break } + } + if let _ = itemGroup { + break + } } - func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { - shouldHideZoomout(false) - if let jpsView = view as? JPSThumbnailAnnotationView, let annotation = view.annotation as? JPSThumbnailAnnotation { - let feedItem = annotation.curationsFeedItem - - if mapView.region.span.latitudeDelta < 2.0 { - jpsView.didSelectAnnotationView(inMap: mapView) - let loc = feedItem?.coordinates; - let region = MKCoordinateRegionMake(CLLocationCoordinate2D(latitude: (loc?.latitude!.doubleValue)!, longitude: (loc?.longitude!.doubleValue)!), MKCoordinateSpanMake(storeZoom, storeZoom)) - mapView.setRegion(region, animated: true) - }else { - mapView.deselectAnnotation(annotation, animated: false) - var itemGroup: [BVCurationsFeedItem]? - for group in groupedFeedItems { - for item in group { - if item === feedItem { - itemGroup = group - break - } - } - if let _ = itemGroup { - break - } - } - - var minLong = 0.0 - var maxLong = -180.0 - var minLat = 90.0 - var maxLat = 0.0 - - for item in itemGroup! { - if item.coordinates.longitude.doubleValue < minLong { - minLong = item.coordinates.longitude.doubleValue - } - - if item.coordinates.longitude.doubleValue > maxLong { - maxLong = item.coordinates.longitude.doubleValue - } - - if item.coordinates.latitude.doubleValue < minLat { - minLat = item.coordinates.latitude.doubleValue - } - - if item.coordinates.latitude.doubleValue > maxLat { - maxLat = item.coordinates.latitude.doubleValue - } - } - //Getting center - let avgLat = (minLat + maxLat) / 2.0 - let avgLong = (minLong + maxLong) / 2.0 - //getting viewport + add padding - var spanLong = abs(maxLong - minLong) + 0.3 - spanLong = spanLong > minGroupZoom ? spanLong : minGroupZoom - var spanLat = abs(maxLat - minLat) + 0.3 - spanLat = spanLat > minGroupZoom ? spanLat : minGroupZoom - - let region = MKCoordinateRegionMake(CLLocationCoordinate2D(latitude: avgLat, longitude: avgLong), MKCoordinateSpanMake(spanLat, spanLong)) - mapView.setRegion(region, animated: true) - previousRegion = region - } - } + if itemGroup == nil { + itemGroup = [BVCurationsFeedItem]() } - func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) { - if let jpsView = view as? JPSThumbnailAnnotationView { - jpsView.didDeselectAnnotationView(inMap: mapView) - } - } + itemGroup?.append(feedItem) + groupedFeedItems.append(itemGroup!) + } + + private func getDistanceBetween(_ feedItem1: BVCurationsFeedItem, feedItem2: BVCurationsFeedItem) -> Double { + let loc1 = CLLocation(latitude: feedItem1.coordinates.latitude.doubleValue, longitude: feedItem1.coordinates.longitude.doubleValue) + let loc2 = CLLocation(latitude: feedItem2.coordinates.latitude.doubleValue, longitude: feedItem2.coordinates.longitude.doubleValue) - func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { + return loc1.distance(from: loc2) + } + + // MARK: MKMapViewDelegate + func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { + if !animated { + shouldHideZoomout(false) + } + } + + func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { + shouldHideZoomout(false) + if let jpsView = view as? JPSThumbnailAnnotationView, let annotation = view.annotation as? JPSThumbnailAnnotation { + let feedItem = annotation.curationsFeedItem + + if mapView.region.span.latitudeDelta < 2.0 { + jpsView.didSelectAnnotationView(inMap: mapView) + let loc = feedItem?.coordinates; + let region = MKCoordinateRegionMake(CLLocationCoordinate2D(latitude: (loc?.latitude!.doubleValue)!, longitude: (loc?.longitude!.doubleValue)!), MKCoordinateSpanMake(storeZoom, storeZoom)) + mapView.setRegion(region, animated: true) + }else { + mapView.deselectAnnotation(annotation, animated: false) + var itemGroup: [BVCurationsFeedItem]? + for group in groupedFeedItems { + for item in group { + if item === feedItem { + itemGroup = group + break + } + } + if let _ = itemGroup { + break + } + } + + var minLong = 0.0 + var maxLong = -180.0 + var minLat = 90.0 + var maxLat = 0.0 - if let jpsAnnotation = annotation as? JPSThumbnailAnnotation { - return jpsAnnotation.annotationView(inMap: mapView) + for item in itemGroup! { + if item.coordinates.longitude.doubleValue < minLong { + minLong = item.coordinates.longitude.doubleValue + } + + if item.coordinates.longitude.doubleValue > maxLong { + maxLong = item.coordinates.longitude.doubleValue + } + + if item.coordinates.latitude.doubleValue < minLat { + minLat = item.coordinates.latitude.doubleValue + } + + if item.coordinates.latitude.doubleValue > maxLat { + maxLat = item.coordinates.latitude.doubleValue + } } - return nil + //Getting center + let avgLat = (minLat + maxLat) / 2.0 + let avgLong = (minLong + maxLong) / 2.0 + //getting viewport + add padding + var spanLong = abs(maxLong - minLong) + 0.3 + spanLong = spanLong > minGroupZoom ? spanLong : minGroupZoom + var spanLat = abs(maxLat - minLat) + 0.3 + spanLat = spanLat > minGroupZoom ? spanLat : minGroupZoom + let region = MKCoordinateRegionMake(CLLocationCoordinate2D(latitude: avgLat, longitude: avgLong), MKCoordinateSpanMake(spanLat, spanLong)) + mapView.setRegion(region, animated: true) + previousRegion = region + } } - - @IBAction internal func resetZoomAnimated(_ sender: UIButton?) { - resetZoom(true) + } + + func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) { + if let jpsView = view as? JPSThumbnailAnnotationView { + jpsView.didDeselectAnnotationView(inMap: mapView) } + } + + func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { - private func resetZoom(_ animated: Bool) { - - if previousRegion != nil && mapView.region.span.latitudeDelta < minGroupZoom { - mapView.setRegion(previousRegion!, animated: animated) - }else{ - previousRegion = nil - let coord = CLLocationCoordinate2D(latitude: 39.50, longitude: -98.35) - let region = MKCoordinateRegionMake(coord, MKCoordinateSpanMake(59.0, 59.0)) - mapView.setRegion(region, animated: animated) - shouldHideZoomout(true) - } + if let jpsAnnotation = annotation as? JPSThumbnailAnnotation { + return jpsAnnotation.annotationView(inMap: mapView) } + return nil + + } + + @IBAction internal func resetZoomAnimated(_ sender: UIButton?) { + resetZoom(true) + } + + private func resetZoom(_ animated: Bool) { - private func shouldHideZoomout(_ shouldHide:Bool) { - zoomOutBtn.isHidden = shouldHide - btnImage.isHidden = shouldHide + if previousRegion != nil && mapView.region.span.latitudeDelta < minGroupZoom { + mapView.setRegion(previousRegion!, animated: animated) + }else{ + previousRegion = nil + let coord = CLLocationCoordinate2D(latitude: 39.50, longitude: -98.35) + let region = MKCoordinateRegionMake(coord, MKCoordinateSpanMake(59.0, 59.0)) + mapView.setRegion(region, animated: animated) + shouldHideZoomout(true) } + } + + private func shouldHideZoomout(_ shouldHide:Bool) { + zoomOutBtn.isHidden = shouldHide + btnImage.isHidden = shouldHide + } } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/DateTimeAgo.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/DateTimeAgo.swift index ff04924d..59a599d6 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/DateTimeAgo.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/DateTimeAgo.swift @@ -9,62 +9,62 @@ import Foundation import Darwin func dateTimeAgo(_ date: Date) -> String { - - let deltaSeconds = secondsFrom(date) - let deltaMinutes:Double = Double(deltaSeconds / 60) - - if (deltaSeconds < 5) { - return "Just now" - } - if (deltaSeconds < 60) { - return String(deltaSeconds) + " seconds ago" - } - if (deltaSeconds < 120) { - return "A minute ago" - } - if (deltaMinutes < 60) { - return String(Int(deltaMinutes)) + " minutes ago" - } - if (deltaMinutes < 120) { - return "An hour ago" - } - if (deltaMinutes < (24 * 60)) { - let hours = flooredString(deltaMinutes, dividedBy: 60) - return hours + " hours ago" - } - if (deltaMinutes < (24 * 60 * 2)) { - return "Yesterday"; - } - if (deltaMinutes < (24 * 60 * 7)) { - let days = flooredString(deltaMinutes, dividedBy: (60 * 24)) - return days + " days ago" - } - if (deltaMinutes < (24 * 60 * 14)) { - return "Last week"; - } - if (deltaMinutes < (24 * 60 * 31)) { - let weeks = flooredString(deltaMinutes, dividedBy: (60 * 24 * 7)) - return weeks + " weeks ago" - } - if (deltaMinutes < (24 * 60 * 61)) { - return "Last month"; - } - if (deltaMinutes < (24 * 60 * 365.25)) { - let months = flooredString(deltaMinutes, dividedBy: (60 * 24 * 30)) - return months + " months ago" - } - if (deltaMinutes < (24 * 60 * 731)) { - return "Last year"; - } - - let years = flooredString(deltaMinutes, dividedBy: (60 * 24 * 365)) - return years + " years ago" + + let deltaSeconds = secondsFrom(date) + let deltaMinutes:Double = Double(deltaSeconds / 60) + + if (deltaSeconds < 5) { + return "Just now" + } + if (deltaSeconds < 60) { + return String(deltaSeconds) + " seconds ago" + } + if (deltaSeconds < 120) { + return "A minute ago" + } + if (deltaMinutes < 60) { + return String(Int(deltaMinutes)) + " minutes ago" + } + if (deltaMinutes < 120) { + return "An hour ago" + } + if (deltaMinutes < (24 * 60)) { + let hours = flooredString(deltaMinutes, dividedBy: 60) + return hours + " hours ago" + } + if (deltaMinutes < (24 * 60 * 2)) { + return "Yesterday"; + } + if (deltaMinutes < (24 * 60 * 7)) { + let days = flooredString(deltaMinutes, dividedBy: (60 * 24)) + return days + " days ago" + } + if (deltaMinutes < (24 * 60 * 14)) { + return "Last week"; + } + if (deltaMinutes < (24 * 60 * 31)) { + let weeks = flooredString(deltaMinutes, dividedBy: (60 * 24 * 7)) + return weeks + " weeks ago" + } + if (deltaMinutes < (24 * 60 * 61)) { + return "Last month"; + } + if (deltaMinutes < (24 * 60 * 365.25)) { + let months = flooredString(deltaMinutes, dividedBy: (60 * 24 * 30)) + return months + " months ago" + } + if (deltaMinutes < (24 * 60 * 731)) { + return "Last year"; + } + + let years = flooredString(deltaMinutes, dividedBy: (60 * 24 * 365)) + return years + " years ago" } private func secondsFrom(_ date:Date) -> Int { - return (Calendar.current as NSCalendar).components(.second, from: date, to: Date(), options: []).second! + return (Calendar.current as NSCalendar).components(.second, from: date, to: Date(), options: []).second! } private func flooredString(_ delta: Double, dividedBy: Double) -> String { - return String(Int(floor(delta/dividedBy))) + return String(Int(floor(delta/dividedBy))) } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/ExtensionShareViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/ExtensionShareViewController.swift index 6390e761..9096fc82 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/ExtensionShareViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/ExtensionShareViewController.swift @@ -12,103 +12,103 @@ import BVSDK // This class demonstrates use of the SLComposeServiceViewController for posting a Curation photo + comment. class ExtensionShareViewController: BaseDemoComposeServiceViewController { - - override func viewDidLoad() { - super.viewDidLoad() - - self.configureSDK() - - _ = MockDataManager.sharedInstance - } + + override func viewDidLoad() { + super.viewDidLoad() - // In the event this class is loaded from an extension, the BVSDKManager will be initiazed. - func configureSDK(){ - - let mgr = BVSDKManager.shared() - mgr.setLogLevel(BVLogLevel.error) - } + self.configureSDK() - // User selected to post a photo with a comment - // In the example below, we just post the photo in the completion request completionHandler so we don't block the UI. - override func didSelectPost() { - - print("didSelectPost") - - // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. + _ = MockDataManager.sharedInstance + } + + // In the event this class is loaded from an extension, the BVSDKManager will be initiazed. + func configureSDK(){ - if self.extensionContext != nil { - - let contentType = kUTTypeJPEG as String - let item = self.extensionContext?.inputItems.first as! NSExtensionItem - - for attachment in item.attachments as! [NSItemProvider] { - if attachment.hasItemConformingToTypeIdentifier(contentType) { - - attachment.loadItem(forTypeIdentifier: contentType, options: nil, completionHandler: { (data, error) -> Void in - // completion - if error == nil { - - let url = data as! URL - if let imageData = try? Data(contentsOf: url) { - let selectedImage = UIImage(data: imageData) - - - self.extensionContext!.completeRequest(returningItems: [], completionHandler: { (expired) in - - self.postSelectedPhoto(selectedImage!) - - }) - - } - - } else { - - let alert = UIAlertController(title: "Error", message: "Error loading image", preferredStyle: .alert) - - let action = UIAlertAction(title: "Error", style: .cancel) { _ in - - self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) - } - - alert.addAction(action) - self.present(alert, animated: true, completion: nil) - } - }) - - } - } - - } - - } - - // post the image to curations using an anonymous account - private func postSelectedPhoto(_ image: UIImage){ - - self.postRequest = BVCurationsAddPostRequest(groups: CurationsDemoConstants.DEFAULT_FEED_GROUPS_CURATIONS, withAuthorAlias: "anonymous", withToken: "anon_user", withText: self.textView.text, with:image) - - let uploadAPI = BVCurationsPhotoUploader() - - // Upload the photo with the request! - uploadAPI.submitCurationsContent(withParams: postRequest, completionHandler: { (void) -> Void in - - // Success - - print("Photo upload success") - + let mgr = BVSDKManager.shared() + mgr.setLogLevel(BVLogLevel.error) + } + + // User selected to post a photo with a comment + // In the example below, we just post the photo in the completion request completionHandler so we don't block the UI. + override func didSelectPost() { + + print("didSelectPost") + + // This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments. + + if self.extensionContext != nil { + + let contentType = kUTTypeJPEG as String + let item = self.extensionContext?.inputItems.first as! NSExtensionItem + + for attachment in item.attachments as! [NSItemProvider] { + if attachment.hasItemConformingToTypeIdentifier(contentType) { + + attachment.loadItem(forTypeIdentifier: contentType, options: nil, completionHandler: { (data, error) -> Void in // completion - self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) - - }) { (error) -> Void in + if error == nil { + + let url = data as! URL + if let imageData = try? Data(contentsOf: url) { + let selectedImage = UIImage(data: imageData) + - // Error - print("Photo upload error: " + (error?.localizedDescription)!) + self.extensionContext!.completeRequest(returningItems: [], completionHandler: { (expired) in + + self.postSelectedPhoto(selectedImage!) + + }) + + } + + } else { + + let alert = UIAlertController(title: "Error", message: "Error loading image", preferredStyle: .alert) + + let action = UIAlertAction(title: "Error", style: .cancel) { _ in - // completion self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + } + + alert.addAction(action) + self.present(alert, animated: true, completion: nil) + } + }) + } - + } + } - + } + + // post the image to curations using an anonymous account + private func postSelectedPhoto(_ image: UIImage){ + + self.postRequest = BVCurationsAddPostRequest(groups: CurationsDemoConstants.DEFAULT_FEED_GROUPS_CURATIONS, withAuthorAlias: "anonymous", withToken: "anon_user", withText: self.textView.text, with:image) + + let uploadAPI = BVCurationsPhotoUploader() + + // Upload the photo with the request! + uploadAPI.submitCurationsContent(withParams: postRequest, completionHandler: { (void) -> Void in + + // Success + + print("Photo upload success") + + // completion + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + + }) { (error) -> Void in + + // Error + print("Photo upload error: " + (error.localizedDescription)) + + // completion + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) + } + + } + + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/JPSThumbnailAnnotation+Curations.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/JPSThumbnailAnnotation+Curations.swift index efaea62f..fbea6439 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/JPSThumbnailAnnotation+Curations.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/JPSThumbnailAnnotation+Curations.swift @@ -12,26 +12,26 @@ import ObjectiveC private var feedItemAssociationKey: UInt8 = 0 extension JPSThumbnailAnnotation { - - convenience init(curationsFeedItem: BVCurationsFeedItem, action: @escaping () -> Void ) { - let thumbNail = JPSThumbnail() - - thumbNail.title = curationsFeedItem.author.username - thumbNail.subtitle = curationsFeedItem.channel - thumbNail.imageURL = URL(string: curationsFeedItem.photos[0].imageServiceUrl) - thumbNail.coordinate = CLLocationCoordinate2DMake(curationsFeedItem.coordinates.latitude.doubleValue, curationsFeedItem.coordinates.longitude.doubleValue) - - thumbNail.disclosureBlock = { - action() - } - - self.init(thumbnail: thumbNail) - objc_setAssociatedObject(self, &feedItemAssociationKey, curationsFeedItem, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + + convenience init(curationsFeedItem: BVCurationsFeedItem, action: @escaping () -> Void ) { + let thumbNail = JPSThumbnail() + + thumbNail.title = curationsFeedItem.author.username + thumbNail.subtitle = curationsFeedItem.channel + thumbNail.imageURL = URL(string: curationsFeedItem.photos[0].imageServiceUrl) + thumbNail.coordinate = CLLocationCoordinate2DMake(curationsFeedItem.coordinates.latitude.doubleValue, curationsFeedItem.coordinates.longitude.doubleValue) + + thumbNail.disclosureBlock = { + action() } - var curationsFeedItem: BVCurationsFeedItem! { - get { - return objc_getAssociatedObject(self, &feedItemAssociationKey) as! BVCurationsFeedItem - } + self.init(thumbnail: thumbNail) + objc_setAssociatedObject(self, &feedItemAssociationKey, curationsFeedItem, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + } + + var curationsFeedItem: BVCurationsFeedItem! { + get { + return objc_getAssociatedObject(self, &feedItemAssociationKey) as! BVCurationsFeedItem } + } } 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 e5692943..cfdd995a 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 @@ -9,107 +9,107 @@ import UIKit import BVSDK enum SocialOutlet { - case pinterest - case twitter - case email - case replyComment - case retweet - case userProfile + case pinterest + case twitter + case email + case replyComment + case retweet + case userProfile } class CurationsFeedItemDetailCell: UITableViewCell { - - var isFavorite = false - - @IBOutlet weak var starButton: UIButton! - - @IBOutlet weak var authorImage: UIImageView! - - @IBOutlet weak var authorNameLabel: UILabel! - @IBOutlet weak var postTimeLabel: UILabel! - @IBOutlet weak var postTextLabel: UILabel! - - @IBOutlet weak var detailView: UIView! - - var feedItem : BVCurationsFeedItem? { - - didSet { - - // author image - let author : BVCurationsPostAuthor = (self.feedItem?.author)! - if (author.avatar != nil){ - let avatarURL : URL = URL(string:author.avatar)! - self.authorImage?.sd_setImageWithURLWithFade(avatarURL, placeholderImage: UIImage(named: "")) - } - - let postDate = Date(timeIntervalSince1970: (feedItem?.timestamp.doubleValue)!) - self.postTimeLabel.text = dateTimeAgo(postDate) - - if (author.profile != nil && author.username != nil){ - self.authorNameLabel.linkAuthorNameLabel(fullText: author.username, author: author.username, target: self, selector: #selector(CurationsFeedItemDetailCell.tappedAuthor(_:))) - } else { - self.authorNameLabel.text = author.username - } - - self.postTextLabel.text = feedItem?.text - } - + + var isFavorite = false + + @IBOutlet weak var starButton: UIButton! + + @IBOutlet weak var authorImage: UIImageView! + + @IBOutlet weak var authorNameLabel: UILabel! + @IBOutlet weak var postTimeLabel: UILabel! + @IBOutlet weak var postTextLabel: UILabel! + + @IBOutlet weak var detailView: UIView! + + var feedItem : BVCurationsFeedItem? { + + didSet { + + // author image + let author : BVCurationsPostAuthor = (self.feedItem?.author)! + if (author.avatar != nil){ + let avatarURL : URL = URL(string:author.avatar)! + self.authorImage?.sd_setImageWithURLWithFade(avatarURL, placeholderImage: UIImage(named: "")) + } + + let postDate = Date(timeIntervalSince1970: (feedItem?.timestamp.doubleValue)!) + self.postTimeLabel.text = dateTimeAgo(postDate) + + if (author.profile != nil && author.username != nil){ + self.authorNameLabel.linkAuthorNameLabel(fullText: author.username, author: author.username, target: self, selector: #selector(CurationsFeedItemDetailCell.tappedAuthor(_:))) + } else { + self.authorNameLabel.text = author.username + } + + self.postTextLabel.text = feedItem?.text } - func tappedAuthor(_ sender:UITapGestureRecognizer){ - if let onSocialButtonTapped = self.onSocialButtonTapped { - onSocialButtonTapped(SocialOutlet.userProfile, self.feedItem!) - } + } + + func tappedAuthor(_ sender:UITapGestureRecognizer){ + if let onSocialButtonTapped = self.onSocialButtonTapped { + onSocialButtonTapped(SocialOutlet.userProfile, self.feedItem!) } - - @IBAction func didTapPinterestButton(_ sender: AnyObject) { - if let onSocialButtonTapped = self.onSocialButtonTapped { - onSocialButtonTapped(SocialOutlet.pinterest, self.feedItem!) - } - + } + + @IBAction func didTapPinterestButton(_ sender: AnyObject) { + if let onSocialButtonTapped = self.onSocialButtonTapped { + onSocialButtonTapped(SocialOutlet.pinterest, self.feedItem!) } - @IBAction func didTapTwitterButton(_ sender: AnyObject) { - if let onSocialButtonTapped = self.onSocialButtonTapped { - onSocialButtonTapped(SocialOutlet.twitter, self.feedItem!) - } + } + + @IBAction func didTapTwitterButton(_ sender: AnyObject) { + if let onSocialButtonTapped = self.onSocialButtonTapped { + onSocialButtonTapped(SocialOutlet.twitter, self.feedItem!) } - - - @IBAction func didTapEmailButton(_ sender: AnyObject) { - if let onSocialButtonTapped = self.onSocialButtonTapped { - onSocialButtonTapped(SocialOutlet.email, self.feedItem!) - } + } + + + @IBAction func didTapEmailButton(_ sender: AnyObject) { + if let onSocialButtonTapped = self.onSocialButtonTapped { + onSocialButtonTapped(SocialOutlet.email, self.feedItem!) } - - - @IBAction func didTapReplyButton(_ sender: AnyObject) { - if let onSocialButtonTapped = self.onSocialButtonTapped { - onSocialButtonTapped(SocialOutlet.replyComment, self.feedItem!) - } + } + + + @IBAction func didTapReplyButton(_ sender: AnyObject) { + if let onSocialButtonTapped = self.onSocialButtonTapped { + onSocialButtonTapped(SocialOutlet.replyComment, self.feedItem!) } - - - @IBAction func didTapRetweetButton(_ sender: AnyObject) { - if let onSocialButtonTapped = self.onSocialButtonTapped { - onSocialButtonTapped(SocialOutlet.retweet, self.feedItem!) - } + } + + + @IBAction func didTapRetweetButton(_ sender: AnyObject) { + if let onSocialButtonTapped = self.onSocialButtonTapped { + onSocialButtonTapped(SocialOutlet.retweet, self.feedItem!) } + } + + @IBAction func toggleStarTapped(_ sender: AnyObject) { - @IBAction func toggleStarTapped(_ sender: AnyObject) { - - isFavorite = !isFavorite - - let imageName = isFavorite ? "star_filled" : "star_unfilled" - - self.starButton.setImage(UIImage(named: imageName), for: UIControlState()) - - } + isFavorite = !isFavorite + let imageName = isFavorite ? "star_filled" : "star_unfilled" - var onSocialButtonTapped : ((_ socialOutlet : SocialOutlet, _ product : BVCurationsFeedItem) -> Void)? = nil + self.starButton.setImage(UIImage(named: imageName), for: UIControlState()) + } + + + var onSocialButtonTapped : ((_ socialOutlet : SocialOutlet, _ product : BVCurationsFeedItem) -> Void)? = nil + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsImageTableViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsImageTableViewCell.swift index 716332ae..faa42864 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsImageTableViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsImageTableViewCell.swift @@ -10,89 +10,89 @@ import BVSDK import SDWebImage class CurationsImageTableViewCell: UITableViewCell { - - @IBOutlet weak var postImageView: UIImageView! + + @IBOutlet weak var postImageView: UIImageView! + + @IBOutlet weak var rightChevronImageView: UIImageView! + @IBOutlet weak var leftChevronImageView: UIImageView! + + var hasNext : Bool! { + + didSet { + self.rightChevronImageView.isHidden = !self.hasNext! + } + + } + var hasPrev : Bool! { + + didSet { + self.leftChevronImageView.isHidden = !self.hasPrev! + } - @IBOutlet weak var rightChevronImageView: UIImageView! - @IBOutlet weak var leftChevronImageView: UIImageView! + } + + var feedItem : BVCurationsFeedItem? { - var hasNext : Bool! { + didSet { + print(self.feedItem!) + // remove previos play button is there was one. + for view in self.subviews { - didSet { - self.rightChevronImageView.isHidden = !self.hasNext! + if view.tag == 99 { + view.removeFromSuperview() } - } - var hasPrev : Bool! { + } + + if self.feedItem!.videos.count > 0 { + // video post + let video : BVCurationsVideo = self.feedItem!.videos.first!; + let imageUrl : URL = URL(string:video.imageServiceUrl)! - didSet { - self.leftChevronImageView.isHidden = !self.hasPrev! - } + self.postImageView.sd_setImageWithURLWithFade(imageUrl, placeholderImage: UIImage(named: "loading")) - } - - var feedItem : BVCurationsFeedItem? { + let playIconSizeWidth : CGFloat = 88.0; - didSet { - print(self.feedItem!) - // remove previos play button is there was one. - for view in self.subviews { - - if view.tag == 99 { - view.removeFromSuperview() - } - - } - - if self.feedItem!.videos.count > 0 { - // video post - let video : BVCurationsVideo = self.feedItem!.videos.first!; - let imageUrl : URL = URL(string:video.imageServiceUrl)! - - self.postImageView.sd_setImageWithURLWithFade(imageUrl, placeholderImage: UIImage(named: "loading")) - - let playIconSizeWidth : CGFloat = 88.0; - - let imageView = UIImageView(); - let image = UIImage(named:"play") - imageView.image = image - imageView.frame.size = CGSize(width: playIconSizeWidth, height: playIconSizeWidth) - imageView.tag = 99 - imageView.center = CGPoint(x: self.frame.size.width / 2 + playIconSizeWidth/2, - y: self.frame.size.height / 2); - - self.addSubview(imageView) - - } else { - - // social post image - if self.feedItem!.photos.count > 0 { - let photo : BVCurationsPhoto = self.feedItem!.photos.first!; - let imageUrl : URL = URL(string:photo.imageServiceUrl)! - self.postImageView?.sd_setImageWithURLWithFade(imageUrl, placeholderImage: UIImage(named: "")) - } - - // Add tap gesture on image - let tapImageGesture = UITapGestureRecognizer(target: self, action: #selector(CurationsImageTableViewCell.tappedImage(_:))) - self.postImageView.isUserInteractionEnabled = true - self.postImageView.addGestureRecognizer(tapImageGesture) - - self.rightChevronImageView.isUserInteractionEnabled = true - self.leftChevronImageView.isUserInteractionEnabled = true - - } - + let imageView = UIImageView(); + let image = UIImage(named:"play") + imageView.image = image + imageView.frame.size = CGSize(width: playIconSizeWidth, height: playIconSizeWidth) + imageView.tag = 99 + imageView.center = CGPoint(x: self.frame.size.width / 2 + playIconSizeWidth/2, + y: self.frame.size.height / 2); + + self.addSubview(imageView) + + } else { + + // social post image + if self.feedItem!.photos.count > 0 { + let photo : BVCurationsPhoto = self.feedItem!.photos.first!; + let imageUrl : URL = URL(string:photo.imageServiceUrl)! + self.postImageView?.sd_setImageWithURLWithFade(imageUrl, placeholderImage: UIImage(named: "")) } - - } + // Add tap gesture on image + let tapImageGesture = UITapGestureRecognizer(target: self, action: #selector(CurationsImageTableViewCell.tappedImage(_:))) + self.postImageView.isUserInteractionEnabled = true + self.postImageView.addGestureRecognizer(tapImageGesture) - func tappedImage(_ sender:UITapGestureRecognizer){ - - // open safari - go to author's page - let url = URL(string: self.feedItem!.permalink) - UIApplication.shared.openURL(url!) + self.rightChevronImageView.isUserInteractionEnabled = true + self.leftChevronImageView.isUserInteractionEnabled = true + } + } + + } + + func tappedImage(_ sender:UITapGestureRecognizer){ + + // open safari - go to author's page + let url = URL(string: self.feedItem!.permalink) + UIApplication.shared.openURL(url!) + + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsYouTubePlayerTableViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsYouTubePlayerTableViewCell.swift index 02519b7f..fbe50f6e 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsYouTubePlayerTableViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/CurationsYouTubePlayerTableViewCell.swift @@ -10,40 +10,40 @@ import BVSDK import youtube_ios_player_helper class CurationsYouTubePlayerTableViewCell: UITableViewCell { + + @IBOutlet weak var rightChevronImageView: UIImageView! + + @IBOutlet weak var leftChevronImageView: UIImageView! + + @IBOutlet weak var playerView: YTPlayerView! + + var video : BVCurationsVideo! { - @IBOutlet weak var rightChevronImageView: UIImageView! + didSet { + + let playerVars = ["playsinline": true, "controls": false, "showinfo": true, "modestbraning": true, "autohide": true ] + + self.playerView.load(withVideoId: self.video.token, playerVars: playerVars) + + } - @IBOutlet weak var leftChevronImageView: UIImageView! + } + + var hasNext : Bool! { - @IBOutlet weak var playerView: YTPlayerView! - - var video : BVCurationsVideo! { - - didSet { - - let playerVars = ["playsinline": true, "controls": false, "showinfo": true, "modestbraning": true, "autohide": true ] - - self.playerView.load(withVideoId: self.video.token, playerVars: playerVars) - - } - + didSet { + self.rightChevronImageView.isHidden = !self.hasNext! } - var hasNext : Bool! { - - didSet { - self.rightChevronImageView.isHidden = !self.hasNext! - } - - } - var hasPrev : Bool! { - - didSet { - self.leftChevronImageView.isHidden = !self.hasPrev! - } - - } - + } + var hasPrev : Bool! { + didSet { + self.leftChevronImageView.isHidden = !self.hasPrev! + } + } + + + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/ProductDetailTableViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/ProductDetailTableViewCell.swift index 60c136d8..ed2d7f6a 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/ProductDetailTableViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/Cells/ProductDetailTableViewCell.swift @@ -10,50 +10,50 @@ import BVSDK import HCSStarRatingView class ProductDetailTableViewCell: UITableViewCell { - - @IBOutlet weak var ratingStars: HCSStarRatingView! - @IBOutlet weak var ratingLabel: UILabel! - @IBOutlet weak var productImageView: UIImageView! - - @IBOutlet weak var productNameLabel: UILabel! - - @IBOutlet weak var shopNowButton: UIButton! - - var product : BVCurationsProductDetail? { - - didSet { - - self.productNameLabel.text = product!.productName - let productImageUrl : URL = URL(string:self.product!.productImageUrl)! - self.productImageView?.sd_setImageWithURLWithFade(productImageUrl, placeholderImage: UIImage(named: "")) - - self.shopNowButton.layer.borderColor = UIColor.darkGray.cgColor - self.shopNowButton.layer.borderWidth = 1 - - let nf = NumberFormatter() - nf.numberStyle = .decimal - let ratingAvg = nf.string(from: (product?.avgRating)!) - - self.ratingStars.value = CGFloat((product?.avgRating)!) - - let numReviews = (product?.totalReviewCount.stringValue)! - - let ratingFromat = String(format: "%.1f", Double(ratingAvg!)!) - - self.ratingLabel.text = ratingFromat + " (" + numReviews + " reviews)" - - } + + @IBOutlet weak var ratingStars: HCSStarRatingView! + @IBOutlet weak var ratingLabel: UILabel! + @IBOutlet weak var productImageView: UIImageView! + + @IBOutlet weak var productNameLabel: UILabel! + + @IBOutlet weak var shopNowButton: UIButton! + + var product : BVCurationsProductDetail? { + + didSet { + + self.productNameLabel.text = product!.productName + let productImageUrl : URL = URL(string:self.product!.productImageUrl)! + self.productImageView?.sd_setImageWithURLWithFade(productImageUrl, placeholderImage: UIImage(named: "")) + + self.shopNowButton.layer.borderColor = UIColor.darkGray.cgColor + self.shopNowButton.layer.borderWidth = 1 + + let nf = NumberFormatter() + nf.numberStyle = .decimal + let ratingAvg = nf.string(from: (product?.avgRating)!) + + self.ratingStars.value = CGFloat((product?.avgRating)!) + + let numReviews = (product?.totalReviewCount.stringValue)! + + let ratingFromat = String(format: "%.1f", Double(ratingAvg!)!) + + self.ratingLabel.text = ratingFromat + " (" + numReviews + " reviews)" + } + } + + + @IBAction func shopNowTapped(_ sender: AnyObject) { - - @IBAction func shopNowTapped(_ sender: AnyObject) { - - if let onShopNowButtonTapped = self.onShopNowButtonTapped { - onShopNowButtonTapped(product!) - } + if let onShopNowButtonTapped = self.onShopNowButtonTapped { + onShopNowButtonTapped(product!) } - - - var onShopNowButtonTapped : ((_ product : BVCurationsProductDetail) -> Void)? = nil - + } + + + var onShopNowButtonTapped : ((_ product : BVCurationsProductDetail) -> Void)? = nil + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/CurationsFeedItemDetailTableViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/CurationsFeedItemDetailTableViewController.swift index fc16e63d..3a6d9b6a 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/CurationsFeedItemDetailTableViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/CurationsFeedItemDetailTableViewController.swift @@ -11,283 +11,283 @@ import AVKit import AVFoundation class CurationsFeedItemDetailTableViewController: UITableViewController { + + var itemIndex: Int = 0 + var feedItem : BVCurationsFeedItem? + + var hasNext : Bool! // When used in a pageview controller, does this have a next controller? + var hasPrev : Bool! // When used in a pageview controller, does this have a previous controller? + + override func viewDidLoad() { + super.viewDidLoad() - var itemIndex: Int = 0 - var feedItem : BVCurationsFeedItem? + ProfileUtils.trackViewController(self) - var hasNext : Bool! // When used in a pageview controller, does this have a next controller? - var hasPrev : Bool! // When used in a pageview controller, does this have a previous controller? + self.tableView.register(UINib(nibName: "CurationsFeedItemDetailCell", bundle: nil), forCellReuseIdentifier: "CurationsFeedItemDetailCell") - override func viewDidLoad() { - super.viewDidLoad() - - ProfileUtils.trackViewController(self) - - self.tableView.register(UINib(nibName: "CurationsFeedItemDetailCell", bundle: nil), forCellReuseIdentifier: "CurationsFeedItemDetailCell") + self.tableView.register(UINib(nibName: "ProductDetailTableViewCell", bundle: nil), forCellReuseIdentifier: "ProductDetailTableViewCell") + + self.tableView.register(UINib(nibName: "CurationsImageTableViewCell", bundle: nil), forCellReuseIdentifier: "CurationsImageTableViewCell") + + self.tableView.register(UINib(nibName: "CurationsYouTubePlayerTableViewCell", bundle: nil), forCellReuseIdentifier: "CurationsYouTubePlayerTableViewCell") + + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + // MARK: Table view delegate + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + + if (indexPath as NSIndexPath).section == 0 && (indexPath as NSIndexPath).row == 0 { + + if feedItem!.videos.count > 0 { - self.tableView.register(UINib(nibName: "ProductDetailTableViewCell", bundle: nil), forCellReuseIdentifier: "ProductDetailTableViewCell") + let video : BVCurationsVideo = feedItem!.videos.first!; - self.tableView.register(UINib(nibName: "CurationsImageTableViewCell", bundle: nil), forCellReuseIdentifier: "CurationsImageTableViewCell") + if (video.origin == "instagram") { + + // instagram - play video with AV player + + let videoURL = URL(string: video.remoteUrl) + let player = AVPlayer(url: videoURL!) + let playerViewController = AVPlayerViewController() + playerViewController.player = player + self.present(playerViewController, animated: true) { + playerViewController.player!.play() + } + + } else { + + // Have not yet tested out this video support. + + _ = SweetAlert().showAlert("\(video.origin) is not yet supported in the demo app.", subTitle: "This feature is currenlty under development.", style: AlertStyle.warning) + + } - self.tableView.register(UINib(nibName: "CurationsYouTubePlayerTableViewCell", bundle: nil), forCellReuseIdentifier: "CurationsYouTubePlayerTableViewCell") + } } - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. + } + + // MARK: - Table view data source + + override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + if section == 1 { + let frame = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 22) + let view = UIView(frame: frame) + view.backgroundColor = UIColor.clear + + let labelFrame = CGRect(x: 8, y: 0, width: tableView.bounds.width, height: 22) + let titleLabel = UILabel(frame: labelFrame) + titleLabel.text = "Shop Now" + view.addSubview(titleLabel) + + return view } - // MARK: Table view delegate + return nil + } + + + override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - - if (indexPath as NSIndexPath).section == 0 && (indexPath as NSIndexPath).row == 0 { - - if feedItem!.videos.count > 0 { - - let video : BVCurationsVideo = feedItem!.videos.first!; - - if (video.origin == "instagram") { - - // instagram - play video with AV player - - let videoURL = URL(string: video.remoteUrl) - let player = AVPlayer(url: videoURL!) - let playerViewController = AVPlayerViewController() - playerViewController.player = player - self.present(playerViewController, animated: true) { - playerViewController.player!.play() - } - - } else { - - // Have not yet tested out this video support. - - _ = SweetAlert().showAlert("\(video.origin) is not yet supported in the demo app.", subTitle: "This feature is currenlty under development.", style: AlertStyle.warning) - - } - - - } - } - + if (section == 0) { + return 3 } - // MARK: - Table view data source + if section == 1 { + return 22 + } - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - if section == 1 { - let frame = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: 22) - let view = UIView(frame: frame) - view.backgroundColor = UIColor.clear - - let labelFrame = CGRect(x: 8, y: 0, width: tableView.bounds.width, height: 22) - let titleLabel = UILabel(frame: labelFrame) - titleLabel.text = "Shop Now" - view.addSubview(titleLabel) - - return view - } - - return nil + return 0 + } + + + override func numberOfSections(in tableView: UITableView) -> Int { + return (feedItem?.referencedProducts.count)! > 0 ? 2 : 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + if section == 0 { + return 2 + } else if section == 1 { + return (feedItem?.referencedProducts.count)! } - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 0; + + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat + { + if (indexPath as NSIndexPath).section == 0 { + + if ((indexPath as NSIndexPath).row == 0){ - if (section == 0) { - return 3 - } + let imageCell = tableView.dequeueReusableCell(withIdentifier: "CurationsImageTableViewCell") as! CurationsImageTableViewCell - if section == 1 { - return 22 - } + return imageCell.bounds.size.height - return 0 + } else if ((indexPath as NSIndexPath).row == 1){ + + let detail1Cell = tableView.dequeueReusableCell(withIdentifier: "CurationsFeedItemDetailCell") as! CurationsFeedItemDetailCell + return detail1Cell.bounds.size.height + + } + + } else if (indexPath as NSIndexPath).section == 1 { + + let productCell = tableView.dequeueReusableCell(withIdentifier: "ProductDetailTableViewCell") as! ProductDetailTableViewCell + return productCell.bounds.size.height } + return 0 + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - override func numberOfSections(in tableView: UITableView) -> Int { - return (feedItem?.referencedProducts.count)! > 0 ? 2 : 1 - } - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if ((indexPath as NSIndexPath).section == 0){ + + // Curations post meta-info + // detail summary of the curations item. This always appears + + if ((indexPath as NSIndexPath).row == 0){ - if section == 0 { - return 2 - } else if section == 1 { - return (feedItem?.referencedProducts.count)! + var video : BVCurationsVideo? + + // Check if this is a youtube video + if (feedItem!.videos.count > 0){ + video = feedItem!.videos.first!; + } + if (video != nil && video?.origin == "youtube"){ + + // image view + let youTubeCell = tableView.dequeueReusableCell(withIdentifier: "CurationsYouTubePlayerTableViewCell") as! CurationsYouTubePlayerTableViewCell + + //youTubeCell.feedItem = self.feedItem! + + // set flags so the cell knows whether or not to display there are prev/next pages. + // If you don't set the flags the chevron icons will always show + youTubeCell.hasPrev = self.hasPrev + youTubeCell.hasNext = self.hasNext + youTubeCell.video = video + + return youTubeCell + + } else { + + // image view + let imageCell = tableView.dequeueReusableCell(withIdentifier: "CurationsImageTableViewCell") as! CurationsImageTableViewCell + + imageCell.feedItem = self.feedItem! + + // set flags so the cell knows whether or not to display there are prev/next pages. + // If you don't set the flags the chevron icons will always show + imageCell.hasPrev = self.hasPrev + imageCell.hasNext = self.hasNext + + return imageCell + } - return 0; + } else if ((indexPath as NSIndexPath).row == 1){ - } - - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat - { - if (indexPath as NSIndexPath).section == 0 { - - if ((indexPath as NSIndexPath).row == 0){ - - let imageCell = tableView.dequeueReusableCell(withIdentifier: "CurationsImageTableViewCell") as! CurationsImageTableViewCell - - return imageCell.bounds.size.height - - } else if ((indexPath as NSIndexPath).row == 1){ - - let detail1Cell = tableView.dequeueReusableCell(withIdentifier: "CurationsFeedItemDetailCell") as! CurationsFeedItemDetailCell - return detail1Cell.bounds.size.height - - } - - } else if (indexPath as NSIndexPath).section == 1 { - - let productCell = tableView.dequeueReusableCell(withIdentifier: "ProductDetailTableViewCell") as! ProductDetailTableViewCell - return productCell.bounds.size.height - } + // social post details - return 0 - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let detail1Cell = tableView.dequeueReusableCell(withIdentifier: "CurationsFeedItemDetailCell") as! CurationsFeedItemDetailCell + detail1Cell.feedItem = self.feedItem! + // Demonstration on getting user events to re-share contributions - if ((indexPath as NSIndexPath).section == 0){ - - // Curations post meta-info - // detail summary of the curations item. This always appears + detail1Cell.onSocialButtonTapped = { (socialOutlet, feedItemSelected) -> Void in + + var itemType : String? + + switch socialOutlet { - if ((indexPath as NSIndexPath).row == 0){ - - var video : BVCurationsVideo? - - // Check if this is a youtube video - if (feedItem!.videos.count > 0){ - video = feedItem!.videos.first!; - - } - - if (video != nil && video?.origin == "youtube"){ - - // image view - let youTubeCell = tableView.dequeueReusableCell(withIdentifier: "CurationsYouTubePlayerTableViewCell") as! CurationsYouTubePlayerTableViewCell - - //youTubeCell.feedItem = self.feedItem! - - // set flags so the cell knows whether or not to display there are prev/next pages. - // If you don't set the flags the chevron icons will always show - youTubeCell.hasPrev = self.hasPrev - youTubeCell.hasNext = self.hasNext - youTubeCell.video = video - - return youTubeCell - - } else { - - // image view - let imageCell = tableView.dequeueReusableCell(withIdentifier: "CurationsImageTableViewCell") as! CurationsImageTableViewCell - - imageCell.feedItem = self.feedItem! - - // set flags so the cell knows whether or not to display there are prev/next pages. - // If you don't set the flags the chevron icons will always show - imageCell.hasPrev = self.hasPrev - imageCell.hasNext = self.hasNext - - return imageCell - } - - } else if ((indexPath as NSIndexPath).row == 1){ - - // social post details - - let detail1Cell = tableView.dequeueReusableCell(withIdentifier: "CurationsFeedItemDetailCell") as! CurationsFeedItemDetailCell - detail1Cell.feedItem = self.feedItem! - - // Demonstration on getting user events to re-share contributions - - detail1Cell.onSocialButtonTapped = { (socialOutlet, feedItemSelected) -> Void in - - var itemType : String? - - switch socialOutlet { - - case SocialOutlet.pinterest: - itemType = "Pinterest" - break - - case SocialOutlet.twitter: - itemType = "Twitter" - break - - case SocialOutlet.email: - itemType = "Email" - break - - case SocialOutlet.retweet: - itemType = "Retweet" - break - - case SocialOutlet.replyComment: - itemType = "ReplyComment" - break - - case .userProfile: - itemType = "UserProfile" - if (feedItemSelected.author.profile != nil){ - let url = URL(string: feedItemSelected.author.profile) - UIApplication.shared.openURL(url!) - } else { - print("ERROR: Nil author profile.") - } - - break - - } - - print("Selected " + itemType! + " on item: " + feedItemSelected.description) - - } - - return detail1Cell - - } + case SocialOutlet.pinterest: + itemType = "Pinterest" + break - } + case SocialOutlet.twitter: + itemType = "Twitter" + break - else { + case SocialOutlet.email: + itemType = "Email" + break - // Refernced Product details - // Product info tagged in this feed. This will not be present in all feeds + case SocialOutlet.retweet: + itemType = "Retweet" + break - let productDetailCell = tableView.dequeueReusableCell(withIdentifier: "ProductDetailTableViewCell") as! ProductDetailTableViewCell! - let product = feedItem!.referencedProducts[(indexPath as NSIndexPath).row] - productDetailCell?.product = product + case SocialOutlet.replyComment: + itemType = "ReplyComment" + break - // utilize closure to get the product the user tapped the "Shop Now" button on. - productDetailCell?.onShopNowButtonTapped = { (selectedProduct) -> Void in - - print("Shop Now Selected: " + selectedProduct.description) - - // Demo just to navigate to a page. Really we'd navigate to the native product page here. - - let bvProduct = BVRecommendedProduct() - bvProduct.productId = product.productId - bvProduct.productName = product.productName - bvProduct.imageURL = product.productImageUrl - - let productView = NewProductPageViewController(productId: bvProduct.productId) - - self.navigationController?.pushViewController(productView, animated: true) - + case .userProfile: + itemType = "UserProfile" + if (feedItemSelected.author.profile != nil){ + let url = URL(string: feedItemSelected.author.profile) + UIApplication.shared.openURL(url!) + } else { + print("ERROR: Nil author profile.") } - return productDetailCell! + + break + + } + + print("Selected " + itemType! + " on item: " + feedItemSelected.description) + } - // Will never get here, just keepting Swift compiler happy :/ - return UITableViewCell() + return detail1Cell + + } + + } + + else { + + // Refernced Product details + // Product info tagged in this feed. This will not be present in all feeds + + let productDetailCell = tableView.dequeueReusableCell(withIdentifier: "ProductDetailTableViewCell") as! ProductDetailTableViewCell! + let product = feedItem!.referencedProducts[(indexPath as NSIndexPath).row] + productDetailCell?.product = product + + // utilize closure to get the product the user tapped the "Shop Now" button on. + productDetailCell?.onShopNowButtonTapped = { (selectedProduct) -> Void in + + print("Shop Now Selected: " + selectedProduct.description) + + // Demo just to navigate to a page. Really we'd navigate to the native product page here. + + let bvProduct = BVRecommendedProduct() + bvProduct.productId = product.productId + bvProduct.productName = product.productName + bvProduct.imageURL = product.productImageUrl + + let productView = NewProductPageViewController(productId: bvProduct.productId) + + self.navigationController?.pushViewController(productView, animated: true) + + } + return productDetailCell! } + // Will never get here, just keepting Swift compiler happy :/ + return UITableViewCell() + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/CurationsFeedMasterViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/CurationsFeedMasterViewController.swift index 8629b72d..89266634 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/CurationsFeedMasterViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Curations/View Controllers/Lightbox Demo/CurationsFeedMasterViewController.swift @@ -9,120 +9,120 @@ import UIKit import BVSDK class CurationsFeedMasterViewController: UIViewController, UIPageViewControllerDataSource { - - private var pageViewController: UIPageViewController? - - var socialFeedItems : [BVCurationsFeedItem]? - var startIndex : Int = 0; - - override func viewDidLoad() { - super.viewDidLoad() - - let titleLabel = UILabel(frame: CGRect(x: 0,y: 0,width: 200,height: 44)) - titleLabel.text = "Social Feed"; - titleLabel.textColor = UIColor.white - titleLabel.textAlignment = .center - self.navigationItem.titleView = titleLabel - - createPageViewController() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - let value = UIInterfaceOrientation.portrait.rawValue; - UIDevice.current.setValue(value, forKey: "orientation") - } + + private var pageViewController: UIPageViewController? + + var socialFeedItems : [BVCurationsFeedItem]? + var startIndex : Int = 0; + + override func viewDidLoad() { + super.viewDidLoad() - override var supportedInterfaceOrientations : UIInterfaceOrientationMask { - return UIInterfaceOrientationMask.portrait - } + let titleLabel = UILabel(frame: CGRect(x: 0,y: 0,width: 200,height: 44)) + titleLabel.text = "Social Feed"; + titleLabel.textColor = UIColor.white + titleLabel.textAlignment = .center + self.navigationItem.titleView = titleLabel - override var shouldAutorotate : Bool { - - return true - } + createPageViewController() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) - private func createPageViewController() { - - let pageController = UIPageViewController(nibName: "CurationsFeedPageViewController", bundle: nil) - - pageController.dataSource = self - - if socialFeedItems!.count > 0 { - let firstController = getItemController(self.startIndex)! - let startingViewControllers: NSArray = [firstController] - pageController.setViewControllers(startingViewControllers as? [UIViewController], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil) - } - - pageViewController = pageController - addChildViewController(pageViewController!) - self.view.addSubview(pageViewController!.view) - pageViewController!.didMove(toParentViewController: self) - - - } + let value = UIInterfaceOrientation.portrait.rawValue; + UIDevice.current.setValue(value, forKey: "orientation") + } + + override var supportedInterfaceOrientations : UIInterfaceOrientationMask { + return UIInterfaceOrientationMask.portrait + } + + override var shouldAutorotate : Bool { + + return true + } + + private func createPageViewController() { + + let pageController = UIPageViewController(nibName: "CurationsFeedPageViewController", bundle: nil) + + pageController.dataSource = self - // MARK: - UIPageViewControllerDataSource - - // navigate to previous controller - func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { - - let itemController = viewController as! CurationsFeedItemDetailTableViewController - - if startIndex > 0{ - startIndex -= 1 - return getItemController(startIndex) - } - - if itemController.itemIndex > 0 { - return getItemController(itemController.itemIndex-1) - } - - return nil + if socialFeedItems!.count > 0 { + let firstController = getItemController(self.startIndex)! + let startingViewControllers: NSArray = [firstController] + pageController.setViewControllers(startingViewControllers as? [UIViewController], direction: UIPageViewControllerNavigationDirection.forward, animated: false, completion: nil) } - // navigate to next controller - func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { - - let itemController = viewController as! CurationsFeedItemDetailTableViewController - - if itemController.itemIndex+1 < self.socialFeedItems!.count { - return getItemController(itemController.itemIndex+1) - } - - - return nil + pageViewController = pageController + addChildViewController(pageViewController!) + self.view.addSubview(pageViewController!.view) + pageViewController!.didMove(toParentViewController: self) + + + } + + // MARK: - UIPageViewControllerDataSource + + // navigate to previous controller + func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { + + let itemController = viewController as! CurationsFeedItemDetailTableViewController + + if startIndex > 0{ + startIndex -= 1 + return getItemController(startIndex) } - private func getItemController(_ itemIndex: Int) -> CurationsFeedItemDetailTableViewController? { - - if itemIndex < self.socialFeedItems!.count { - - let pageItemController = CurationsFeedItemDetailTableViewController(nibName: "CurationsFeedItemDetailTableViewController", bundle: nil) - - pageItemController.itemIndex = itemIndex - - pageItemController.feedItem = self.socialFeedItems![itemIndex] - - pageItemController.hasPrev = (itemIndex == 0) ? false : true - - pageItemController.hasNext = (itemIndex+1 == self.socialFeedItems?.count) ? false : true - - return pageItemController - } - - return nil + if itemController.itemIndex > 0 { + return getItemController(itemController.itemIndex-1) } - // MARK: - Page Indicator + return nil + } + + // navigate to next controller + func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { - func presentationCount(for pageViewController: UIPageViewController) -> Int { - return socialFeedItems!.count + let itemController = viewController as! CurationsFeedItemDetailTableViewController + + if itemController.itemIndex+1 < self.socialFeedItems!.count { + return getItemController(itemController.itemIndex+1) } - func presentationIndex(for pageViewController: UIPageViewController) -> Int { - return 0 + + return nil + } + + private func getItemController(_ itemIndex: Int) -> CurationsFeedItemDetailTableViewController? { + + if itemIndex < self.socialFeedItems!.count { + + let pageItemController = CurationsFeedItemDetailTableViewController(nibName: "CurationsFeedItemDetailTableViewController", bundle: nil) + + pageItemController.itemIndex = itemIndex + + pageItemController.feedItem = self.socialFeedItems![itemIndex] + + pageItemController.hasPrev = (itemIndex == 0) ? false : true + + pageItemController.hasNext = (itemIndex+1 == self.socialFeedItems?.count) ? false : true + + return pageItemController } - + + return nil + } + + // MARK: - Page Indicator + + func presentationCount(for pageViewController: UIPageViewController) -> Int { + return socialFeedItems!.count + } + + func presentationIndex(for pageViewController: UIPageViewController) -> Int { + return 0 + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/FacebookLoginViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/FacebookLoginViewController.swift index 7c10700a..8c79e5b6 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/FacebookLoginViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/FacebookLoginViewController.swift @@ -9,27 +9,27 @@ import UIKit import FBSDKLoginKit class FacebookLoginViewController: UIViewController { + + @IBOutlet weak var fbLoginButton: FBSDKLoginButton! + + let descriptionLabel = UILabel() + + override func viewWillAppear(_ animated: Bool) { - @IBOutlet weak var fbLoginButton: FBSDKLoginButton! + super.viewWillAppear(animated) - let descriptionLabel = UILabel() + self.navigationController?.isNavigationBarHidden = true - override func viewWillAppear(_ animated: Bool) { - - super.viewWillAppear(animated) - - self.navigationController?.isNavigationBarHidden = true - - // check that user is logged in to facebook - if(FBSDKAccessToken.current() != nil) { - fbLoginButton.removeFromSuperview() - self.presentingViewController?.dismiss(animated: true, completion: nil) - } - + // check that user is logged in to facebook + if(FBSDKAccessToken.current() != nil) { + fbLoginButton.removeFromSuperview() + self.presentingViewController?.dismiss(animated: true, completion: nil) } - override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - } - + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/HeaderCollectionViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/HeaderCollectionViewCell.swift index 1331d9a6..be4974f2 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/HeaderCollectionViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/HeaderCollectionViewCell.swift @@ -8,12 +8,12 @@ import UIKit class HeaderCollectionViewCell: UICollectionViewCell { - - @IBOutlet weak var textLbl: UILabel? - override func awakeFromNib() { - super.awakeFromNib() - textLbl?.textColor = UIColor.bazaarvoiceNavy() - textLbl?.baselineAdjustment = .alignCenters - } - + + @IBOutlet weak var textLbl: UILabel? + override func awakeFromNib() { + super.awakeFromNib() + textLbl?.textColor = UIColor.bazaarvoiceNavy() + textLbl?.baselineAdjustment = .alignCenters + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/HomeAdvertisementCollectionViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/HomeAdvertisementCollectionViewCell.swift index 16425c35..0b47e36b 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/HomeAdvertisementCollectionViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/HomeAdvertisementCollectionViewCell.swift @@ -9,35 +9,35 @@ import UIKit import GoogleMobileAds class HomeAdvertisementCollectionViewCell: UICollectionViewCell { - - @IBOutlet weak var nativeContentAdView : GADNativeContentAdView! + + @IBOutlet weak var nativeContentAdView : GADNativeContentAdView! + + var nativeContentAd : GADNativeContentAd? { - var nativeContentAd : GADNativeContentAd? { - - didSet{ - - print("self " + (nativeContentAd?.headline)!) - - let titleLabel = nativeContentAdView.headlineView as! UILabel - let imageView = nativeContentAdView.imageView as! UIImageView - let bodyLabel = nativeContentAdView.bodyView as! UILabel - let callToActionLabel = nativeContentAdView.callToActionView as! UILabel - - titleLabel.text = nativeContentAd?.headline - imageView.image = (nativeContentAd?.images![0] as! GADNativeAdImage).image - bodyLabel.text = nativeContentAd?.body - callToActionLabel.text = nativeContentAd?.callToAction - - self.nativeContentAdView.nativeContentAd = nativeContentAd - - } - + didSet{ + + print("self " + (nativeContentAd?.headline)!) + + let titleLabel = nativeContentAdView.headlineView as! UILabel + let imageView = nativeContentAdView.imageView as! UIImageView + let bodyLabel = nativeContentAdView.bodyView as! UILabel + let callToActionLabel = nativeContentAdView.callToActionView as! UILabel + + titleLabel.text = nativeContentAd?.headline + imageView.image = (nativeContentAd?.images![0] as! GADNativeAdImage).image + bodyLabel.text = nativeContentAd?.body + callToActionLabel.text = nativeContentAd?.callToAction + + self.nativeContentAdView.nativeContentAd = nativeContentAd + } - override func awakeFromNib() { - super.awakeFromNib() - // Initialization code - } - - + } + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/HomeHeaderCollectionViewCell.swift b/Examples/BVSDKDemo/BVSDKDemo/HomeHeaderCollectionViewCell.swift index f18aaccc..71646169 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/HomeHeaderCollectionViewCell.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/HomeHeaderCollectionViewCell.swift @@ -9,125 +9,125 @@ import UIKit import Foundation class HomeHeaderCollectionViewCell: UICollectionViewCell, UIScrollViewDelegate { - - @IBOutlet weak var scrollView: UIScrollView! - @IBOutlet weak var pageControl: UIPageControl! - - var currentPageIndex : Int = 0 { - didSet { - pageControl.currentPage = currentPageIndex - } + + @IBOutlet weak var scrollView: UIScrollView! + @IBOutlet weak var pageControl: UIPageControl! + + var currentPageIndex : Int = 0 { + didSet { + pageControl.currentPage = currentPageIndex } + } + + var timer : Timer? + + let images : [UIImageView] = [ + UIImageView(image: UIImage(named: "slide_1.jpg")), + UIImageView(image: UIImage(named: "slide_2.jpg")), + UIImageView(image: UIImage(named: "slide_3.jpg")) + ] + + static let heightCalculator = UIImage(named: "slide_1.jpg")! + class func preferredHeightForWidth (_ preferredWidth: CGFloat) -> CGFloat { - var timer : Timer? + return (preferredWidth / heightCalculator.size.width) * heightCalculator.size.height - let images : [UIImageView] = [ - UIImageView(image: UIImage(named: "slide_1.jpg")), - UIImageView(image: UIImage(named: "slide_2.jpg")), - UIImageView(image: UIImage(named: "slide_3.jpg")) - ] + } + + override func awakeFromNib() { + super.awakeFromNib() - static let heightCalculator = UIImage(named: "slide_1.jpg")! - class func preferredHeightForWidth (_ preferredWidth: CGFloat) -> CGFloat { - - return (preferredWidth / heightCalculator.size.width) * heightCalculator.size.height - - } + setAutoScrollTimer(2.0) - override func awakeFromNib() { - super.awakeFromNib() - - setAutoScrollTimer(2.0) - - configureImages() - configureScrollView() - - } + configureImages() + configureScrollView() + } + + + func configureImages() { - func configureImages() { - - for image in images { - image.contentMode = .scaleAspectFit - } - + for image in images { + image.contentMode = .scaleAspectFit } - func configureScrollView() { - scrollView.isPagingEnabled = true - scrollView.showsHorizontalScrollIndicator = false - scrollView.showsVerticalScrollIndicator = false - scrollView.scrollsToTop = false - - scrollView.delegate = self - - for image in images { - scrollView.addSubview(image) - } - } + } + + func configureScrollView() { + scrollView.isPagingEnabled = true + scrollView.showsHorizontalScrollIndicator = false + scrollView.showsVerticalScrollIndicator = false + scrollView.scrollsToTop = false - override func layoutSubviews() { - - super.layoutSubviews() - - self.scrollView.frame = CGRect( - x: 0, - y: 0, - width: self.bounds.width, - height: self.bounds.height - ) - - for i in 0 ..< images.count { - - images[i].frame = CGRect( - x: CGFloat(i) * self.bounds.width, - y: scrollView.frame.origin.y, - width: self.bounds.width, - height: self.bounds.height - ) - - } - - self.scrollView.contentSize = CGSize( - width: scrollView.frame.size.width * 3, - height: self.frame.size.height - ) - - } - + scrollView.delegate = self - func scrollViewDidScroll(_ scrollView: UIScrollView) { - - currentPageIndex = Int(floor(scrollView.contentOffset.x / self.bounds.width)) - setAutoScrollTimer(4.0) - + for image in images { + scrollView.addSubview(image) } + } + + override func layoutSubviews() { + + super.layoutSubviews() - func setAutoScrollTimer(_ time:Double) { - timer?.invalidate() - timer = nil - timer = Timer.scheduledTimer( - timeInterval: time, - target: self, - selector: #selector(HomeHeaderCollectionViewCell.autoScroll), - userInfo: nil, - repeats: false - ) + self.scrollView.frame = CGRect( + x: 0, + y: 0, + width: self.bounds.width, + height: self.bounds.height + ) + + for i in 0 ..< images.count { + + images[i].frame = CGRect( + x: CGFloat(i) * self.bounds.width, + y: scrollView.frame.origin.y, + width: self.bounds.width, + height: self.bounds.height + ) + } - func autoScroll() { - - currentPageIndex += 1 - if currentPageIndex >= images.count { - currentPageIndex = 0 - } - - var newFrame = scrollView.frame - newFrame.origin.x = newFrame.size.width * CGFloat(currentPageIndex) - UIView.animate(withDuration: 0.6, animations: { - self.scrollView.scrollRectToVisible(newFrame, animated: false) - }) - + self.scrollView.contentSize = CGSize( + width: scrollView.frame.size.width * 3, + height: self.frame.size.height + ) + + } + + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + + currentPageIndex = Int(floor(scrollView.contentOffset.x / self.bounds.width)) + setAutoScrollTimer(4.0) + + } + + func setAutoScrollTimer(_ time:Double) { + timer?.invalidate() + timer = nil + timer = Timer.scheduledTimer( + timeInterval: time, + target: self, + selector: #selector(HomeHeaderCollectionViewCell.autoScroll), + userInfo: nil, + repeats: false + ) + } + + func autoScroll() { + + currentPageIndex += 1 + if currentPageIndex >= images.count { + currentPageIndex = 0 } - + + var newFrame = scrollView.frame + newFrame.origin.x = newFrame.size.width * CGFloat(currentPageIndex) + UIView.animate(withDuration: 0.6, animations: { + self.scrollView.scrollRectToVisible(newFrame, animated: false) + }) + + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/HomeViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/HomeViewController.swift index bf256635..7cb557c3 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/HomeViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/HomeViewController.swift @@ -163,7 +163,7 @@ class HomeViewController: UIViewController, UICollectionViewDataSource, UICollec func refresh(_ refreshControl: UIRefreshControl) { // clear any cached recommendations, and reload latest recommendations from API - BVShopperProfileRequestCache.shared().removeAllCachedResponses() + BVRecommendationsLoader.purgeRecommendationsCache() self.loadProducts() } @@ -301,14 +301,15 @@ class HomeViewController: UIViewController, UICollectionViewDataSource, UICollec } func loadConversations(omitStats : Bool) { - let req = BVBulkProductRequest().addProductSort(.averageOverallRating, order: .descending) - .add(.totalReviewCount, filterOperator: .greaterThanOrEqualTo, value: "10") - .add(.isActive, filterOperator: .equalTo, value: "true") - .add(.isDisabled, filterOperator: .equalTo, value: "false") + let req = BVBulkProductRequest().sort(by: .productAverageOverallRating, monotonicSortOrderValue: .descending) + .filter(on: .productTotalReviewCount, relationalFilterOperatorValue: .greaterThanOrEqualTo, value: "10") + .filter(on: .productIsActive, relationalFilterOperatorValue: .equalTo, value: "true") + .filter(on: .productIsDisabled, relationalFilterOperatorValue: .equalTo, value: "false") if (!omitStats){ req.includeStatistics(.reviews) } - req.sortIncludedReviews(.rating, order: .descending) + + req.sort(by: .reviewRating, monotonicSortOrderValue: .descending) req.load({(response) in self.doneLoading(response.results) }){(errs) in diff --git a/Examples/BVSDKDemo/BVSDKDemo/Login/LocationPermissionViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Login/LocationPermissionViewController.swift index 47d59533..28f19943 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Login/LocationPermissionViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Login/LocationPermissionViewController.swift @@ -9,57 +9,57 @@ import UIKit import CoreLocation class LocationPermissionViewController: PermissionViewController, CLLocationManagerDelegate { - var mngr: CLLocationManager! + var mngr: CLLocationManager! + + override func viewDidLoad() { + super.viewDidLoad() - override func viewDidLoad() { - super.viewDidLoad() - - self.icon?.image = UIImage(named: "LocationIcon") - - self.titleLbl?.text = "Why share my location?" - self.descLbl?.text = "By sharing your location with Endurance Cycles, we can provide you with a more robust instore shopping experience that inclues personalized product recommendations and easy access to product information." - } + self.icon?.image = UIImage(named: "LocationIcon") - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } + self.titleLbl?.text = "Why share my location?" + self.descLbl?.text = "By sharing your location with Endurance Cycles, we can provide you with a more robust instore shopping experience that inclues personalized product recommendations and easy access to product information." + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + override func enablePressed(_ sender: UIButton) { - override func enablePressed(_ sender: UIButton) { - - if CLLocationManager.authorizationStatus() != .authorizedAlways{ - self.mngr = CLLocationManager() - self.mngr.delegate = self - self.mngr.requestAlwaysAuthorization() - } + if CLLocationManager.authorizationStatus() != .authorizedAlways{ + self.mngr = CLLocationManager() + self.mngr.delegate = self + self.mngr.requestAlwaysAuthorization() } - - override func notNowPressed(_ sender: UIButton) { - self.pushNotificationPermissionVC() + } + + override func notNowPressed(_ sender: UIButton) { + self.pushNotificationPermissionVC() + } + + func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { + if status != .notDetermined { + self.pushNotificationPermissionVC() } - func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { - if status != .notDetermined { - self.pushNotificationPermissionVC() - } - - } + } + + private func pushNotificationPermissionVC(){ + + // Check that the user didn't already authorize notifications - private func pushNotificationPermissionVC(){ - - // Check that the user didn't already authorize notifications - - if UIApplication.shared.currentUserNotificationSettings!.types == UIUserNotificationType() { - - let lvc = NotificationPermissionViewController(nibName: "PermissionViewController", bundle: nil) - self.navigationController?.pushViewController(lvc, animated: true) - - } else { - - self.presentingViewController?.dismiss(animated: true, completion: nil) - - } - + if UIApplication.shared.currentUserNotificationSettings!.types == UIUserNotificationType() { + + let lvc = NotificationPermissionViewController(nibName: "PermissionViewController", bundle: nil) + self.navigationController?.pushViewController(lvc, animated: true) + + } else { + + self.presentingViewController?.dismiss(animated: true, completion: nil) + } + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Login/PermissionViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/Login/PermissionViewController.swift index 61754153..7dc73cfe 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Login/PermissionViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Login/PermissionViewController.swift @@ -8,30 +8,30 @@ import UIKit class PermissionViewController: UIViewController { - - @IBOutlet var titleLbl: UILabel? - @IBOutlet var icon: UIImageView? - @IBOutlet var descLbl: UILabel? - - override func viewDidLoad() { - super.viewDidLoad() - self.navigationController?.isNavigationBarHidden = true - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - // set the icon color mask - self.icon!.image? = (self.icon!.image?.withRenderingMode(.alwaysTemplate))! - self.icon!.tintColor = UIColor.bazaarvoiceNavy() - } - - - @IBAction func enablePressed(_ sender: UIButton){ - - } + + @IBOutlet var titleLbl: UILabel? + @IBOutlet var icon: UIImageView? + @IBOutlet var descLbl: UILabel? + + override func viewDidLoad() { + super.viewDidLoad() + self.navigationController?.isNavigationBarHidden = true + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + // set the icon color mask + self.icon!.image? = (self.icon!.image?.withRenderingMode(.alwaysTemplate))! + self.icon!.tintColor = UIColor.bazaarvoiceNavy() + } + + + @IBAction func enablePressed(_ sender: UIButton){ + } + + + @IBAction func notNowPressed(_ sender: UIButton){ - @IBAction func notNowPressed(_ sender: UIButton){ - - } + } } diff --git a/Examples/BVSDKDemo/BVSDKDemo/MockDataManager/MockDataManager.swift b/Examples/BVSDKDemo/BVSDKDemo/MockDataManager/MockDataManager.swift index 5625840d..cafab13e 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/MockDataManager/MockDataManager.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/MockDataManager/MockDataManager.swift @@ -11,489 +11,489 @@ import SwiftyJSON import BVSDK class MockDataManager { + + static let sharedInstance = MockDataManager() + var pinReponse: Data? + var currentConfig: DemoConfig! + var prodConfig: DemoConfig? + var stagingConfig: DemoConfig? + + + init() { + self.setupPreSelectedKeysIfPresent() + self.setupMocking() + } + + static let PRESELECTED_CONFIG_DISPLAY_NAME_KEY = "BV_PRE_SELECTED_CONFIG_DISPLAY_NAME" + + func configure(_ configType: BVConfigurationType) { - static let sharedInstance = MockDataManager() - var pinReponse: Data? - var currentConfig: DemoConfig! - var prodConfig: DemoConfig? - var stagingConfig: DemoConfig? + var config: DemoConfig? + if configType == .prod, prodConfig != nil{ + config = prodConfig! + }else if configType == .staging, stagingConfig != nil { + config = stagingConfig! + } + + if let _ = config { + switchToConfig(config: config!) + } + } + + func setupPreSelectedKeysIfPresent() { + let defaults = UserDefaults(suiteName: "group.bazaarvoice.bvsdkdemo.app") + let preselectedDisplayName = defaults!.string(forKey: MockDataManager.PRESELECTED_CONFIG_DISPLAY_NAME_KEY) ?? mockConfig.displayName - init() { - self.setupPreSelectedKeysIfPresent() - self.setupMocking() + let matchingConfig = configs.filter{ $0.displayName == preselectedDisplayName }.first + if let config = matchingConfig { + switchToConfig(config: config) + }else { + switchToConfig(config: mockConfig) } + } + + func setupMocking() { - static let PRESELECTED_CONFIG_DISPLAY_NAME_KEY = "BV_PRE_SELECTED_CONFIG_DISPLAY_NAME" + OHHTTPStubs.stubRequests(passingTest: { (request) -> Bool in + + return self.shouldMockResponseForRequest(request) + + }) { (request) -> OHHTTPStubsResponse in + + return self.resposneForRequest(request) + + } - func configure(_ configType: BVConfigurationType) { - - var config: DemoConfig? - if configType == .prod, prodConfig != nil{ - config = prodConfig! - }else if configType == .staging, stagingConfig != nil { - config = stagingConfig! - } - - if let _ = config { - switchToConfig(config: config!) - } + } + + let curationsUrlMatch = "bazaarvoice.com/curations/content/get" + let curationsPhotoPostUrlMatch = "https://api.bazaarvoice.com/curations/content/add/" + let recommendationsUrlMatch = "bazaarvoice.com/recommendations" + let profileUrlMatch = "bazaarvoice.com/users" + let analyticsMatch = "bazaarvoice.com/event" + let conversationsMatch = "bazaarvoice.com/data/reviews" + let conversationsQuestionsMatch = "bazaarvoice.com/data/question" + let conversationsProductMatch = "bazaarvoice.com/data/products" + let conversationsAuthorsMatch = "bazaarvoice.com/data/authors" + let submitReviewMatch = "bazaarvoice.com/data/submitreview" + let submitReviewPhotoMatch = "bazaarvoice.com/data/uploadphoto" + let submitQuestionMatch = "bazaarvoice.com/data/submitquestion" + let submitAnswerMatch = "bazaarvoice.com/data/submitanswer" + var convoStoresConfigMatch = String(format:"s3.amazonaws.com/incubator-mobile-apps/sdk/%@/ios/%@/conversations-stores", S3_API_VERSION, "REPLACE_ME") + var pinConfigMatch = String(format:"s3.amazonaws.com/incubator-mobile-apps/sdk/%@/ios/%@/pin", S3_API_VERSION, "REPLACE_ME") + let pinRequestMatch = "bazaarvoice.com/pin/toreview" + + func shouldMockResponseForRequest(_ request: URLRequest) -> Bool { + + guard let url = request.url?.absoluteString else { + return false } - func setupPreSelectedKeysIfPresent() { - - let defaults = UserDefaults(suiteName: "group.bazaarvoice.bvsdkdemo.app") - let preselectedDisplayName = defaults!.string(forKey: MockDataManager.PRESELECTED_CONFIG_DISPLAY_NAME_KEY) ?? mockConfig.displayName - - let matchingConfig = configs.filter{ $0.displayName == preselectedDisplayName }.first - if let config = matchingConfig { - switchToConfig(config: config) - }else { - switchToConfig(config: mockConfig) - } + return self.shouldMockData() && (self.isAnalyticsRequest(url) || self.isSdkRequest(url)); + + } + + func isAnalyticsRequest(_ url: String) -> Bool { + return url.contains(analyticsMatch) + } + + func isSdkRequest(_ url: String) -> Bool { + convoStoresConfigMatch = String(format:"s3.amazonaws.com/incubator-mobile-apps/sdk/%@/ios/%@/conversations-stores", S3_API_VERSION, currentConfig?.clientId ?? "REPLACE_ME") + pinConfigMatch = String(format:"s3.amazonaws.com/incubator-mobile-apps/sdk/%@/ios/%@/pin", S3_API_VERSION, currentConfig?.clientId ?? "REPLACE_ME") + + let containsCurations = url.contains(curationsUrlMatch) + let containsCurationsPhotoPost = url.contains(curationsPhotoPostUrlMatch) + let containsProfile = url.contains(profileUrlMatch) + let containsRecommendations = url.contains(recommendationsUrlMatch) + let containsConversations = url.contains(conversationsMatch) + let containsConversationsQuestions = url.contains(conversationsQuestionsMatch) + let containsConversationsProducts = url.contains(conversationsProductMatch) + let containsConversationsAuthors = url.contains(conversationsAuthorsMatch) + let containsSubmitReviews = url.contains(submitReviewMatch) + let containsSubmitPhotoReviews = url.contains(submitReviewPhotoMatch) + let containsSubmitQuestion = url.contains(submitQuestionMatch) + let containsSubmitAnswers = url.contains(submitAnswerMatch) + let containsConvoStoresConfig = url.contains(convoStoresConfigMatch) + let containsPINConfig = url.contains(pinConfigMatch) + let containsPINRequest = url.contains(pinRequestMatch) + + return containsCurations || containsCurationsPhotoPost || containsRecommendations || containsProfile || containsConversations || containsConversationsQuestions || containsConversationsProducts || containsConversationsAuthors || containsSubmitReviews || containsSubmitPhotoReviews || containsSubmitQuestion || containsSubmitAnswers || containsConvoStoresConfig || containsPINConfig || containsPINRequest + + } + + func shouldMockData() -> Bool { + return currentConfig?.isMock ?? true + } + + let headers = ["Content-Type": "application/json"] + + func resposneForRequest(_ request: URLRequest) -> OHHTTPStubsResponse { + + print("Mocking request: \(request.url!.absoluteString)") + + guard let url = request.url?.absoluteString else { + return OHHTTPStubsResponse() } - func setupMocking() { - - OHHTTPStubs.stubRequests(passingTest: { (request) -> Bool in - - return self.shouldMockResponseForRequest(request) - - }) { (request) -> OHHTTPStubsResponse in - - return self.resposneForRequest(request) - - } - + if self.isAnalyticsRequest(url) { + return self.responseForAnalyticsRequest(url) + } + else if self.isSdkRequest(url) { + return self.responseForSdkRequest(url) } - let curationsUrlMatch = "bazaarvoice.com/curations/content/get" - let curationsPhotoPostUrlMatch = "https://api.bazaarvoice.com/curations/content/add/" - let recommendationsUrlMatch = "bazaarvoice.com/recommendations" - let profileUrlMatch = "bazaarvoice.com/users" - let analyticsMatch = "bazaarvoice.com/event" - let conversationsMatch = "bazaarvoice.com/data/reviews" - let conversationsQuestionsMatch = "bazaarvoice.com/data/question" - let conversationsProductMatch = "bazaarvoice.com/data/products" - let conversationsAuthorsMatch = "bazaarvoice.com/data/authors" - let submitReviewMatch = "bazaarvoice.com/data/submitreview" - let submitReviewPhotoMatch = "bazaarvoice.com/data/uploadphoto" - let submitQuestionMatch = "bazaarvoice.com/data/submitquestion" - let submitAnswerMatch = "bazaarvoice.com/data/submitanswer" - var convoStoresConfigMatch = String(format:"s3.amazonaws.com/incubator-mobile-apps/sdk/%@/ios/%@/conversations-stores", S3_API_VERSION, "REPLACE_ME") - var pinConfigMatch = String(format:"s3.amazonaws.com/incubator-mobile-apps/sdk/%@/ios/%@/pin", S3_API_VERSION, "REPLACE_ME") - let pinRequestMatch = "bazaarvoice.com/pin/toreview" - - func shouldMockResponseForRequest(_ request: URLRequest) -> Bool { - - guard let url = request.url?.absoluteString else { - return false - } - - return self.shouldMockData() && (self.isAnalyticsRequest(url) || self.isSdkRequest(url)); - + return OHHTTPStubsResponse() + + } + + func responseForAnalyticsRequest(_ url: String) -> OHHTTPStubsResponse { + + return OHHTTPStubsResponse( + data: Data(), + statusCode: 200, + headers: nil + ) + + } + + func responseForSdkRequest(_ url: String) -> OHHTTPStubsResponse { + + if url.contains(curationsUrlMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("curationsEnduranceCycles.json", type(of: self))!, + statusCode: 200, + headers: headers + ) + } - func isAnalyticsRequest(_ url: String) -> Bool { - return url.contains(analyticsMatch) + if url.contains(curationsPhotoPostUrlMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("post_successfulCreation.json", type(of: self))!, + statusCode: 200, + headers: headers + ) + } - func isSdkRequest(_ url: String) -> Bool { - convoStoresConfigMatch = String(format:"s3.amazonaws.com/incubator-mobile-apps/sdk/%@/ios/%@/conversations-stores", S3_API_VERSION, currentConfig?.clientId ?? "REPLACE_ME") - pinConfigMatch = String(format:"s3.amazonaws.com/incubator-mobile-apps/sdk/%@/ios/%@/pin", S3_API_VERSION, currentConfig?.clientId ?? "REPLACE_ME") - - let containsCurations = url.contains(curationsUrlMatch) - let containsCurationsPhotoPost = url.contains(curationsPhotoPostUrlMatch) - let containsProfile = url.contains(profileUrlMatch) - let containsRecommendations = url.contains(recommendationsUrlMatch) - let containsConversations = url.contains(conversationsMatch) - let containsConversationsQuestions = url.contains(conversationsQuestionsMatch) - let containsConversationsProducts = url.contains(conversationsProductMatch) - let containsConversationsAuthors = url.contains(conversationsAuthorsMatch) - let containsSubmitReviews = url.contains(submitReviewMatch) - let containsSubmitPhotoReviews = url.contains(submitReviewPhotoMatch) - let containsSubmitQuestion = url.contains(submitQuestionMatch) - let containsSubmitAnswers = url.contains(submitAnswerMatch) - let containsConvoStoresConfig = url.contains(convoStoresConfigMatch) - let containsPINConfig = url.contains(pinConfigMatch) - let containsPINRequest = url.contains(pinRequestMatch) - - return containsCurations || containsCurationsPhotoPost || containsRecommendations || containsProfile || containsConversations || containsConversationsQuestions || containsConversationsProducts || containsConversationsAuthors || containsSubmitReviews || containsSubmitPhotoReviews || containsSubmitQuestion || containsSubmitAnswers || containsConvoStoresConfig || containsPINConfig || containsPINRequest - + + if url.contains(recommendationsUrlMatch) { + + return OHHTTPStubsResponse( + jsonObject: generateRecommendationsResponseDictionary(), + statusCode: 200, + headers: headers + ) + } - func shouldMockData() -> Bool { - return currentConfig?.isMock ?? true + if url.contains(profileUrlMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("userProfile1.json", type(of: self))!, + statusCode: 200, + headers: headers + ) + } - let headers = ["Content-Type": "application/json"] + if url.contains(conversationsMatch) { + + // Conversations requests will vary depending on parameters + // Hence check for specific parameters to set mock results. + + var conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles.json" // default, sorted by most recent + + if url.contains("Sort=Rating:desc"){ + conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles_SortHighestRated.json" + } else if url.contains("Sort=Rating:asc"){ + conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles_SortLowestRated.json" + } else if url.contains("Sort=Helpfulness:desc"){ + conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles_SortMostHelpful.json" + } else if url.contains("UserLocation:eq"){ + conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles_FilterLocation.json" + } + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile(conversationsReviewsResultMockFile, type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + + } - func resposneForRequest(_ request: URLRequest) -> OHHTTPStubsResponse { - - print("Mocking request: \(request.url!.absoluteString)") - - guard let url = request.url?.absoluteString else { - return OHHTTPStubsResponse() - } - - if self.isAnalyticsRequest(url) { - return self.responseForAnalyticsRequest(url) - } - else if self.isSdkRequest(url) { - return self.responseForSdkRequest(url) - } - - return OHHTTPStubsResponse() - + if url.contains(conversationsQuestionsMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("conversationsQuestionsIncludeAnswers.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + } - func responseForAnalyticsRequest(_ url: String) -> OHHTTPStubsResponse { + if url.contains(conversationsProductMatch) { + + // In the demp app, when requesting product status we just use the Filter=Id:eq: param + // When we request a store list, we use the Offset parameter. + // So we'll use that info + if url.contains("Offset=0"){ return OHHTTPStubsResponse( - data: Data(), - statusCode: 200, - headers: nil + fileAtPath: OHPathForFile("storeBulkFeedWithStatistics.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] ) - } - - func responseForSdkRequest(_ url: String) -> OHHTTPStubsResponse { - - if url.contains(curationsUrlMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("curationsEnduranceCycles.json", type(of: self))!, - statusCode: 200, - headers: headers - ) - - } - - if url.contains(curationsPhotoPostUrlMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("post_successfulCreation.json", type(of: self))!, - statusCode: 200, - headers: headers - ) - - } - - - if url.contains(recommendationsUrlMatch) { - - return OHHTTPStubsResponse( - jsonObject: generateRecommendationsResponseDictionary(), - statusCode: 200, - headers: headers - ) - - } - - if url.contains(profileUrlMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("userProfile1.json", type(of: self))!, - statusCode: 200, - headers: headers - ) - - } - - if url.contains(conversationsMatch) { - - // Conversations requests will vary depending on parameters - // Hence check for specific parameters to set mock results. - - var conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles.json" // default, sorted by most recent - - if url.contains("Sort=Rating:desc"){ - conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles_SortHighestRated.json" - } else if url.contains("Sort=Rating:asc"){ - conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles_SortLowestRated.json" - } else if url.contains("Sort=Helpfulness:desc"){ - conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles_SortMostHelpful.json" - } else if url.contains("UserLocation:eq"){ - conversationsReviewsResultMockFile = "conversationsReviewsEnduranceCycles_FilterLocation.json" - } - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile(conversationsReviewsResultMockFile, type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } - - if url.contains(conversationsQuestionsMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("conversationsQuestionsIncludeAnswers.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } - - if url.contains(conversationsProductMatch) { - - // In the demp app, when requesting product status we just use the Filter=Id:eq: param - // When we request a store list, we use the Offset parameter. - // So we'll use that info - if url.contains("Offset=0"){ - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("storeBulkFeedWithStatistics.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } else { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("conversationsProductsIncludeStats.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } - - } - - if url.contains(conversationsAuthorsMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("conversationsAuthorWithIncludes.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } - - if url.contains(submitReviewMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("submitReview.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } - - if url.contains(submitReviewPhotoMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("submitPhotoWithReview.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } - - if url.contains(submitQuestionMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("submitQuestion.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } - - if url.contains(submitAnswerMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("submitAnswer.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } + } else { - if url.contains(convoStoresConfigMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("testNotificationConfig.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - - } - - if url.contains(pinConfigMatch) { - - return OHHTTPStubsResponse( - fileAtPath: OHPathForFile("testNotificationProductConfig.json", type(of: self))!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"] - ) - } - - if url.contains(pinRequestMatch) { - return OHHTTPStubsResponse(data: pinReponse ?? "[]".data(using: .utf8)!, - statusCode: 200, - headers: ["Content-Type": "application/json;charset=utf-8"]) - } - - - return OHHTTPStubsResponse() + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("conversationsProductsIncludeStats.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + } + } - /// randomize the recommendations in JSON file for variation between loads. - func generateRecommendationsResponseDictionary() -> [String: AnyObject] { - - guard let path = Bundle.main.path(forResource: "recommendationsResult", ofType: "json") else { - print("Invalid filename/path.") - return [:] - } - - do { - let data = try Data(contentsOf: URL(fileURLWithPath: path), options: NSData.ReadingOptions.mappedIfSafe) - var json = JSON(data: data) - - let recommendations:[String] = json["profile"]["recommendations"].arrayValue.map { $0.string!} - // randomize order - let shuffledRecommendations = recommendations.sorted() {_, _ in arc4random() % 2 == 0} - json["profile"]["recommendations"] = JSON(shuffledRecommendations) - - return json.dictionaryObject! as [String : AnyObject] - - } catch let error as NSError { - print(error.localizedDescription) - } - - return [:] - + if url.contains(conversationsAuthorsMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("conversationsAuthorWithIncludes.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + } - func generateMockPinReponse(fromProducts: [BVProduct]) { - var pins = [Dictionary]() - for product in fromProducts { - var pinDict = [String: Any]() - pinDict["avg_rating"] = product.reviewStatistics?.averageOverallRating - pinDict["image_url"] = product.imageUrl - pinDict["product_page_url"] = product.productPageUrl - pinDict["name"] = product.name - pinDict["id"] = product.identifier - pins.append(pinDict) - } - - do { - pinReponse = try JSONSerialization.data(withJSONObject: pins, options: .prettyPrinted) - }catch { - - } - + if url.contains(submitReviewMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("submitReview.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + } - private lazy var mockConfig: DemoConfig = { - let configDict = MockDataManager.getDefaultConfigDict() - let config = DemoConfig(dictionary: configDict as NSDictionary) - config.isMock = true - return config - }() - - private class func getDefaultConfigDict() -> Dictionary { - return ["clientId": "REPLACE_ME" as AnyObject, - "displayName": "(Mock) Endurance Cycles" as AnyObject, - "apiKeyShopperAdvertising": "REPLACE_ME" as AnyObject, - "apiKeyConversations": "REPLACE_ME" as AnyObject, - "apiKeyConversationsStores": "REPLACE_ME" as AnyObject, - "apiKeyCurations": "REPLACE_ME" as AnyObject, - "apiKeyPIN": "REPLACE_ME" as AnyObject, - "apiKeyLocation": "00000000-0000-0000-0000-000000000000" as AnyObject] + if url.contains(submitReviewPhotoMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("submitPhotoWithReview.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + } - private lazy var fromFileConfigs: [DemoConfig] = { - - var stagingFilePath = Bundle.main.path(forResource: "bvsdk_config_staging", ofType: "json") - var prodFilePath = Bundle.main.path(forResource: "bvsdk_config_prod", ofType: "json") - - var configs = [DemoConfig]() - - var stagingConfigDict = MockDataManager.getDefaultConfigDict() - if let staging = stagingFilePath, var configDict = MockDataManager.getJSON(from: staging) { - configDict["displayName"] = "Staging Config" as AnyObject? - - for key in configDict.keys { - stagingConfigDict[key] = configDict[key] - } - - self.stagingConfig = DemoConfig(dictionary: stagingConfigDict as NSDictionary) - self.stagingConfig?.configType = .staging - configs.append(self.stagingConfig!) - } - - var prodConfigDict = MockDataManager.getDefaultConfigDict() - if let prod = prodFilePath, var configDict = MockDataManager.getJSON(from: prod) { - configDict["displayName"] = "Prod Config" as AnyObject? - - for key in configDict.keys { - prodConfigDict[key] = configDict[key] - } - - self.prodConfig = DemoConfig(dictionary: prodConfigDict as NSDictionary) - configs.append(self.prodConfig!) - } - - return configs - }() - - private class func getJSON(from filepath: String) -> Dictionary? { - if let data = try? Data(contentsOf: URL(fileURLWithPath: filepath)) { - if let json = try? JSONSerialization.jsonObject(with: data, options:.init(rawValue: 0)) { - return json as? Dictionary; - } - } - return nil + if url.contains(submitQuestionMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("submitQuestion.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + } - func switchToConfig(config: DemoConfig) { - if currentConfig != nil { - currentConfig.isSelected = false - } - - currentConfig = config - currentConfig.isSelected = true - let defaults = UserDefaults(suiteName: "group.bazaarvoice.bvsdkdemo.app") - defaults!.set(currentConfig.displayName, forKey: MockDataManager.PRESELECTED_CONFIG_DISPLAY_NAME_KEY) - BVSDKManager.configure(withConfiguration: currentConfig?.raw as! [AnyHashable : Any], configType: currentConfig.configType) + if url.contains(submitAnswerMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("submitAnswer.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + + } + + if url.contains(convoStoresConfigMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("testNotificationConfig.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + + } + + if url.contains(pinConfigMatch) { + + return OHHTTPStubsResponse( + fileAtPath: OHPathForFile("testNotificationProductConfig.json", type(of: self))!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"] + ) + } + + if url.contains(pinRequestMatch) { + return OHHTTPStubsResponse(data: pinReponse ?? "[]".data(using: .utf8)!, + statusCode: 200, + headers: ["Content-Type": "application/json;charset=utf-8"]) } - lazy var configs : [DemoConfig] = { - var configs = [DemoConfig]() - configs.append(contentsOf: self.fromFileConfigs) - configs.append(self.mockConfig) - - guard let path = Bundle.main.path(forResource: "config/DemoAppConfigs", ofType: "plist") else { return configs } - guard let contents = NSArray(contentsOfFile: path) else { return configs } - print(contents) - configs += contents.map{ DemoConfig(dictionary: $0 as! NSDictionary) } - return configs - }() + return OHHTTPStubsResponse() + + } + + /// randomize the recommendations in JSON file for variation between loads. + func generateRecommendationsResponseDictionary() -> [String: AnyObject] { + + guard let path = Bundle.main.path(forResource: "recommendationsResult", ofType: "json") else { + print("Invalid filename/path.") + return [:] + } + + do { + let data = try Data(contentsOf: URL(fileURLWithPath: path), options: NSData.ReadingOptions.mappedIfSafe) + var json = JSON(data: data) + + let recommendations:[String] = json["profile"]["recommendations"].arrayValue.map { $0.string!} + // randomize order + let shuffledRecommendations = recommendations.sorted() {_, _ in arc4random() % 2 == 0} + json["profile"]["recommendations"] = JSON(shuffledRecommendations) + + return json.dictionaryObject! as [String : AnyObject] + + } catch let error as NSError { + print(error.localizedDescription) + } + + return [:] + + } + + func generateMockPinReponse(fromProducts: [BVProduct]) { + var pins = [Dictionary]() + for product in fromProducts { + var pinDict = [String: Any]() + pinDict["avg_rating"] = product.reviewStatistics?.averageOverallRating + pinDict["image_url"] = product.imageUrl + pinDict["product_page_url"] = product.productPageUrl + pinDict["name"] = product.name + pinDict["id"] = product.identifier + pins.append(pinDict) + } + + do { + pinReponse = try JSONSerialization.data(withJSONObject: pins, options: .prettyPrinted) + }catch { + + } + + } + + private lazy var mockConfig: DemoConfig = { + let configDict = MockDataManager.getDefaultConfigDict() + let config = DemoConfig(dictionary: configDict as NSDictionary) + config.isMock = true + return config + }() + + private class func getDefaultConfigDict() -> Dictionary { + return ["clientId": "REPLACE_ME" as AnyObject, + "displayName": "(Mock) Endurance Cycles" as AnyObject, + "apiKeyShopperAdvertising": "REPLACE_ME" as AnyObject, + "apiKeyConversations": "REPLACE_ME" as AnyObject, + "apiKeyConversationsStores": "REPLACE_ME" as AnyObject, + "apiKeyCurations": "REPLACE_ME" as AnyObject, + "apiKeyPIN": "REPLACE_ME" as AnyObject, + "apiKeyLocation": "00000000-0000-0000-0000-000000000000" as AnyObject] + } + + private lazy var fromFileConfigs: [DemoConfig] = { + + var stagingFilePath = Bundle.main.path(forResource: "bvsdk_config_staging", ofType: "json") + var prodFilePath = Bundle.main.path(forResource: "bvsdk_config_prod", ofType: "json") + + var configs = [DemoConfig]() + + var stagingConfigDict = MockDataManager.getDefaultConfigDict() + if let staging = stagingFilePath, var configDict = MockDataManager.getJSON(from: staging) { + configDict["displayName"] = "Staging Config" as AnyObject? + + for key in configDict.keys { + stagingConfigDict[key] = configDict[key] + } + + self.stagingConfig = DemoConfig(dictionary: stagingConfigDict as NSDictionary) + self.stagingConfig?.configType = .staging + configs.append(self.stagingConfig!) + } + + var prodConfigDict = MockDataManager.getDefaultConfigDict() + if let prod = prodFilePath, var configDict = MockDataManager.getJSON(from: prod) { + configDict["displayName"] = "Prod Config" as AnyObject? + + for key in configDict.keys { + prodConfigDict[key] = configDict[key] + } + + self.prodConfig = DemoConfig(dictionary: prodConfigDict as NSDictionary) + configs.append(self.prodConfig!) + } + + return configs + }() + + private class func getJSON(from filepath: String) -> Dictionary? { + if let data = try? Data(contentsOf: URL(fileURLWithPath: filepath)) { + if let json = try? JSONSerialization.jsonObject(with: data, options:.init(rawValue: 0)) { + return json as? Dictionary; + } + } + return nil + } + + func switchToConfig(config: DemoConfig) { + if currentConfig != nil { + currentConfig.isSelected = false + } + + currentConfig = config + currentConfig.isSelected = true + let defaults = UserDefaults(suiteName: "group.bazaarvoice.bvsdkdemo.app") + defaults!.set(currentConfig.displayName, forKey: MockDataManager.PRESELECTED_CONFIG_DISPLAY_NAME_KEY) + BVSDKManager.configure(withConfiguration: currentConfig?.raw as! [AnyHashable : Any], configType: currentConfig.configType) + } + + lazy var configs : [DemoConfig] = { + var configs = [DemoConfig]() + configs.append(contentsOf: self.fromFileConfigs) + configs.append(self.mockConfig) + + guard let path = Bundle.main.path(forResource: "config/DemoAppConfigs", ofType: "plist") else { return configs } + guard let contents = NSArray(contentsOfFile: path) else { return configs } + print(contents) + configs += contents.map{ DemoConfig(dictionary: $0 as! NSDictionary) } + return configs + }() + } class DemoConfig { - - let clientId, displayName, curationsKey, conversationsKey, conversationsStoresKey, shopperAdvertisingKey, locationKey, pinKey : String - let raw: NSDictionary - var isMock: Bool - var isSelected: Bool! - var configType = BVConfigurationType.prod - - init(dictionary:NSDictionary) { - raw = dictionary; - clientId = dictionary["clientId"] as? String ?? "REPLACE_ME" - displayName = dictionary["displayName"] as? String ?? "REPLACE_ME" - curationsKey = dictionary["apiKeyCurations"] as? String ?? "REPLACE_ME" - conversationsKey = dictionary["apiKeyConversations"] as? String ?? "REPLACE_ME" - conversationsStoresKey = dictionary["apiKeyConversationsStores"] as? String ?? "REPLACE_ME" - shopperAdvertisingKey = dictionary["apiKeyShopperAdvertising"] as? String ?? "REPLACE_ME" - locationKey = dictionary["apiKeyLocation"] as? String ?? "00000000-0000-0000-0000-000000000000" - pinKey = dictionary["apiKeyPIN"] as? String ?? "REPLACE_ME" - isMock = false - isSelected = false - } + + let clientId, displayName, curationsKey, conversationsKey, conversationsStoresKey, shopperAdvertisingKey, locationKey, pinKey : String + let raw: NSDictionary + var isMock: Bool + var isSelected: Bool! + var configType = BVConfigurationType.prod + + init(dictionary:NSDictionary) { + raw = dictionary; + clientId = dictionary["clientId"] as? String ?? "REPLACE_ME" + displayName = dictionary["displayName"] as? String ?? "REPLACE_ME" + curationsKey = dictionary["apiKeyCurations"] as? String ?? "REPLACE_ME" + conversationsKey = dictionary["apiKeyConversations"] as? String ?? "REPLACE_ME" + conversationsStoresKey = dictionary["apiKeyConversationsStores"] as? String ?? "REPLACE_ME" + shopperAdvertisingKey = dictionary["apiKeyShopperAdvertising"] as? String ?? "REPLACE_ME" + locationKey = dictionary["apiKeyLocation"] as? String ?? "00000000-0000-0000-0000-000000000000" + pinKey = dictionary["apiKeyPIN"] as? String ?? "REPLACE_ME" + isMock = false + isSelected = false + } } diff --git a/Examples/BVSDKDemo/BVSDKDemo/NotificationPermissionViewController.swift b/Examples/BVSDKDemo/BVSDKDemo/NotificationPermissionViewController.swift index d45edb53..541e0f9f 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/NotificationPermissionViewController.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/NotificationPermissionViewController.swift @@ -9,35 +9,35 @@ import UIKit import UserNotifications class NotificationPermissionViewController: PermissionViewController { + + override func viewDidLoad() { + super.viewDidLoad() - override func viewDidLoad() { - super.viewDidLoad() - - self.icon?.image = UIImage(named: "notificationIcon") - self.titleLbl?.text = "Why turn on notifications?" - self.descLbl?.text = "By turning on notifications with Endurance Cycles, we can provide you with exclusive in-store recommendations." - } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - // Dispose of any resources that can be recreated. - } - - override func enablePressed(_ sender: UIButton) { - - if #available(iOS 10.0, *) { - let center = UNUserNotificationCenter.current() - center.requestAuthorization(options: [.sound, .alert, .badge]){ (success, error) in - }; - }else { - let settings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) - UIApplication.shared.registerUserNotificationSettings(settings) - } - self.navigationController?.dismiss(animated: true, completion: nil) - } + self.icon?.image = UIImage(named: "notificationIcon") + self.titleLbl?.text = "Why turn on notifications?" + self.descLbl?.text = "By turning on notifications with Endurance Cycles, we can provide you with exclusive in-store recommendations." + } + + override func didReceiveMemoryWarning() { + super.didReceiveMemoryWarning() + // Dispose of any resources that can be recreated. + } + + override func enablePressed(_ sender: UIButton) { - override func notNowPressed(_ sender: UIButton) { - self.navigationController?.dismiss(animated: true, completion: nil) + if #available(iOS 10.0, *) { + let center = UNUserNotificationCenter.current() + center.requestAuthorization(options: [.sound, .alert, .badge]){ (success, error) in + }; + }else { + let settings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil) + UIApplication.shared.registerUserNotificationSettings(settings) } + self.navigationController?.dismiss(animated: true, completion: nil) + } + + override func notNowPressed(_ sender: UIButton) { + self.navigationController?.dismiss(animated: true, completion: nil) + } } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/CardView.swift b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/CardView.swift index 74742c14..019a23af 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/CardView.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/CardView.swift @@ -8,14 +8,14 @@ import UIKit class CardView: UIView { + + override func awakeFromNib() { + super.awakeFromNib() - override func awakeFromNib() { - super.awakeFromNib() - - self.layer.cornerRadius = 0 - self.layer.borderColor = UIColor.lightGray.cgColor - self.layer.borderWidth = 0.5 - - } - + self.layer.cornerRadius = 0 + self.layer.borderColor = UIColor.lightGray.cgColor + self.layer.borderWidth = 0.5 + + } + } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Frameworks/SweetAlert.swift b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Frameworks/SweetAlert.swift index 40598895..bfa312e5 100755 --- a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Frameworks/SweetAlert.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Frameworks/SweetAlert.swift @@ -11,328 +11,328 @@ import UIKit import QuartzCore public enum AlertStyle { - case success,error,warning,none - case customImag(imageFile:String) + case success,error,warning,none + case customImag(imageFile:String) } open class SweetAlert: UIViewController { - let kBakcgroundTansperancy: CGFloat = 0.7 - let kHeightMargin: CGFloat = 10.0 - let KTopMargin: CGFloat = 20.0 - let kWidthMargin: CGFloat = 10.0 - let kAnimatedViewHeight: CGFloat = 70.0 - let kMaxHeight: CGFloat = 300.0 - var kContentWidth: CGFloat = 300.0 - let kButtonHeight: CGFloat = 35.0 - var textViewHeight: CGFloat = 90.0 - let kTitleHeight:CGFloat = 30.0 - var strongSelf:SweetAlert? - var contentView = UIView() - var titleLabel: UILabel = UILabel() - var buttons: [UIButton] = [] - var animatedView: AnimatableView? - var imageView:UIImageView? - var subTitleTextView = UITextView() - var userAction:((_ isOtherButton: Bool) -> Void)? = nil - let kFont = "Helvetica" - - init() { - super.init(nibName: nil, bundle: nil) - self.view.frame = UIScreen.main.bounds - self.view.autoresizingMask = [UIViewAutoresizing.flexibleHeight, UIViewAutoresizing.flexibleWidth] - self.view.backgroundColor = UIColor(red:0, green:0, blue:0, alpha:kBakcgroundTansperancy) - self.view.addSubview(contentView) - - //Retaining itself strongly so can exist without strong refrence - strongSelf = self - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func setupContentView() { - contentView.backgroundColor = UIColor(white: 1.0, alpha: 1.0) - contentView.layer.cornerRadius = 5.0 - contentView.layer.masksToBounds = true - contentView.layer.borderWidth = 0.5 - contentView.addSubview(titleLabel) - contentView.addSubview(subTitleTextView) - contentView.backgroundColor = UIColor.colorFromRGB(0xFFFFFF) - contentView.layer.borderColor = UIColor.colorFromRGB(0xCCCCCC).cgColor - view.addSubview(contentView) - } - - private func setupTitleLabel() { - titleLabel.text = "" - titleLabel.numberOfLines = 1 - titleLabel.textAlignment = .center - titleLabel.font = UIFont(name: kFont, size:25) - titleLabel.textColor = UIColor.colorFromRGB(0x575757) - } - - private func setupSubtitleTextView() { - subTitleTextView.text = "" - subTitleTextView.textAlignment = .center - subTitleTextView.font = UIFont(name: kFont, size:16) - subTitleTextView.textColor = UIColor.colorFromRGB(0x797979) - subTitleTextView.isEditable = false - } - - private func resizeAndRelayout() { - let mainScreenBounds = UIScreen.main.bounds - self.view.frame.size = mainScreenBounds.size - let x: CGFloat = kWidthMargin - var y: CGFloat = KTopMargin - let width: CGFloat = kContentWidth - (kWidthMargin*2) - - if animatedView != nil { - animatedView!.frame = CGRect(x: (kContentWidth - kAnimatedViewHeight) / 2.0, y: y, width: kAnimatedViewHeight, height: kAnimatedViewHeight) - contentView.addSubview(animatedView!) - y += kAnimatedViewHeight + kHeightMargin - } - - if imageView != nil { - imageView!.frame = CGRect(x: (kContentWidth - kAnimatedViewHeight) / 2.0, y: y, width: kAnimatedViewHeight, height: kAnimatedViewHeight) - contentView.addSubview(imageView!) - y += imageView!.frame.size.height + kHeightMargin - } - - // Title - if self.titleLabel.text != nil { - titleLabel.frame = CGRect(x: x, y: y, width: width, height: kTitleHeight) - contentView.addSubview(titleLabel) - y += kTitleHeight + kHeightMargin - } - - // Subtitle - if self.subTitleTextView.text.isEmpty == false { - let subtitleString = subTitleTextView.text! as NSString - let rect = subtitleString.boundingRect(with: CGSize(width: width, height: 0.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName:subTitleTextView.font!], context: nil) - textViewHeight = ceil(rect.size.height) + 10.0 - subTitleTextView.frame = CGRect(x: x, y: y, width: width, height: textViewHeight) - contentView.addSubview(subTitleTextView) - y += textViewHeight + kHeightMargin - } - - var buttonRect:[CGRect] = [] - for button in buttons { - let string = button.title(for: UIControlState())! as NSString - buttonRect.append(string.boundingRect(with: CGSize(width: width, height:0.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes:[NSFontAttributeName:button.titleLabel!.font], context:nil)) - } - - var totalWidth: CGFloat = 0.0 - if buttons.count == 2 { - totalWidth = buttonRect[0].size.width + buttonRect[1].size.width + kWidthMargin + 40.0 - } - else{ - totalWidth = buttonRect[0].size.width + 20.0 - } - y += kHeightMargin - var buttonX = (kContentWidth - totalWidth ) / 2.0 - for i in 0 ..< buttons.count { - - buttons[i].frame = CGRect(x: buttonX, y: y, width: buttonRect[i].size.width + 20.0, height: buttonRect[i].size.height + 10.0) - buttonX = buttons[i].frame.origin.x + kWidthMargin + buttonRect[i].size.width + 20.0 - buttons[i].layer.cornerRadius = 5.0 - self.contentView.addSubview(buttons[i]) - buttons[i].addTarget(self, action: #selector(SweetAlert.pressed(_:)), for: UIControlEvents.touchUpInside) - - } - y += kHeightMargin + buttonRect[0].size.height + 10.0 - if y > kMaxHeight { - let diff = y - kMaxHeight - let sFrame = subTitleTextView.frame - subTitleTextView.frame = CGRect(x: sFrame.origin.x, y: sFrame.origin.y, width: sFrame.width, height: sFrame.height - diff) - - for button in buttons { - let bFrame = button.frame - button.frame = CGRect(x: bFrame.origin.x, y: bFrame.origin.y - diff, width: bFrame.width, height: bFrame.height) - } - - y = kMaxHeight - } - - contentView.frame = CGRect(x: (mainScreenBounds.size.width - kContentWidth) / 2.0, y: (mainScreenBounds.size.height - y) / 2.0, width: kContentWidth, height: y) - contentView.clipsToBounds = true - } - - open func pressed(_ sender: UIButton!) { - self.closeAlert(sender.tag) - } - - open override func viewWillLayoutSubviews() { - super.viewWillLayoutSubviews() - var sz = UIScreen.main.bounds.size - let sver = UIDevice.current.systemVersion as NSString - let ver = sver.floatValue - if ver < 8.0 { - // iOS versions before 7.0 did not switch the width and height on device roration - if UIInterfaceOrientationIsLandscape(UIApplication.shared.statusBarOrientation) { - let ssz = sz - sz = CGSize(width:ssz.height, height:ssz.width) - } - } - self.resizeAndRelayout() - } - - func closeAlert(_ buttonIndex:Int){ - if userAction != nil { - let isOtherButton = buttonIndex == 0 ? true: false - SweetAlertContext.shouldNotAnimate = true - userAction!(isOtherButton) - SweetAlertContext.shouldNotAnimate = false - } - - UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseOut, animations: { () -> Void in - self.view.alpha = 0.0 - }) { (Bool) -> Void in - self.view.removeFromSuperview() - self.cleanUpAlert() - - //Releasing strong refrence of itself. - self.strongSelf = nil - } - } - - func cleanUpAlert() { - - if self.animatedView != nil { - self.animatedView!.removeFromSuperview() - self.animatedView = nil - } - self.contentView.removeFromSuperview() - self.contentView = UIView() - } - - open func showAlert(_ title: String) -> SweetAlert { - _ = self.showAlert(title, subTitle: nil, style: .none) - return self - } - - open func showAlert(_ title: String, subTitle: String?, style: AlertStyle) -> SweetAlert { - _ = self.showAlert(title, subTitle: subTitle, style: style, buttonTitle: "OK") - return self - - } - - open func showAlert(_ title: String, subTitle: String?, style: AlertStyle,buttonTitle: String, action: ((_ isOtherButton: Bool) -> Void)? = nil) -> SweetAlert { - _ = self.showAlert(title, subTitle: subTitle, style: style, buttonTitle: buttonTitle,buttonColor: UIColor.colorFromRGB(0xAEDEF4)) - userAction = action - return self - } - - open func showAlert(_ title: String, subTitle: String?, style: AlertStyle,buttonTitle: String,buttonColor: UIColor,action: ((_ isOtherButton: Bool) -> Void)? = nil) -> SweetAlert { - _ = self.showAlert(title, subTitle: subTitle, style: style, buttonTitle: buttonTitle,buttonColor: buttonColor,otherButtonTitle: - nil) - userAction = action - return self - } - - open func showAlert(_ title: String, subTitle: String?, style: AlertStyle,buttonTitle: String,buttonColor: UIColor,otherButtonTitle: - String?, action: ((_ isOtherButton: Bool) -> Void)? = nil) -> SweetAlert { - self.showAlert(title, subTitle: subTitle, style: style, buttonTitle: buttonTitle,buttonColor: buttonColor,otherButtonTitle: - otherButtonTitle,otherButtonColor: UIColor.red) - userAction = action - return self - } - - open func showAlert(_ title: String, subTitle: String?, style: AlertStyle,buttonTitle: String,buttonColor: UIColor,otherButtonTitle: - String?, otherButtonColor: UIColor?,action: ((_ isOtherButton: Bool) -> Void)? = nil) { - userAction = action - let window: UIWindow = UIApplication.shared.keyWindow! - window.addSubview(view) - window.bringSubview(toFront: view) - view.frame = window.bounds - self.setupContentView() - self.setupTitleLabel() - self.setupSubtitleTextView() - - switch style { - case .success: - self.animatedView = SuccessAnimatedView() - - case .error: - self.animatedView = CancelAnimatedView() - - case .warning: - self.animatedView = InfoAnimatedView() - - case let .customImag(imageFile): - if let image = UIImage(named: imageFile) { - self.imageView = UIImageView(image: image) - } - case .none: - self.animatedView = nil - } - - self.titleLabel.text = title - if subTitle != nil { - self.subTitleTextView.text = subTitle - } - buttons = [] - if buttonTitle.isEmpty == false { - let button: UIButton = UIButton(type: UIButtonType.custom) - button.setTitle(buttonTitle, for: UIControlState()) - button.backgroundColor = buttonColor - button.isUserInteractionEnabled = true - button.tag = 0 - buttons.append(button) - } - - if otherButtonTitle != nil && otherButtonTitle!.isEmpty == false { - let button: UIButton = UIButton(type: UIButtonType.custom) - button.setTitle(otherButtonTitle, for: UIControlState()) - button.backgroundColor = otherButtonColor - button.addTarget(self, action: #selector(SweetAlert.pressed(_:)), for: UIControlEvents.touchUpInside) - button.tag = 1 - buttons.append(button) - } - - resizeAndRelayout() - if SweetAlertContext.shouldNotAnimate == true { - //Do not animate Alert - if self.animatedView != nil { - self.animatedView!.animate() - } - } - else { - animateAlert() - } - } - - func animateAlert() { - - view.alpha = 0; + let kBakcgroundTansperancy: CGFloat = 0.7 + let kHeightMargin: CGFloat = 10.0 + let KTopMargin: CGFloat = 20.0 + let kWidthMargin: CGFloat = 10.0 + let kAnimatedViewHeight: CGFloat = 70.0 + let kMaxHeight: CGFloat = 300.0 + var kContentWidth: CGFloat = 300.0 + let kButtonHeight: CGFloat = 35.0 + var textViewHeight: CGFloat = 90.0 + let kTitleHeight:CGFloat = 30.0 + var strongSelf:SweetAlert? + var contentView = UIView() + var titleLabel: UILabel = UILabel() + var buttons: [UIButton] = [] + var animatedView: AnimatableView? + var imageView:UIImageView? + var subTitleTextView = UITextView() + var userAction:((_ isOtherButton: Bool) -> Void)? = nil + let kFont = "Helvetica" + + init() { + super.init(nibName: nil, bundle: nil) + self.view.frame = UIScreen.main.bounds + self.view.autoresizingMask = [UIViewAutoresizing.flexibleHeight, UIViewAutoresizing.flexibleWidth] + self.view.backgroundColor = UIColor(red:0, green:0, blue:0, alpha:kBakcgroundTansperancy) + self.view.addSubview(contentView) + + //Retaining itself strongly so can exist without strong refrence + strongSelf = self + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setupContentView() { + contentView.backgroundColor = UIColor(white: 1.0, alpha: 1.0) + contentView.layer.cornerRadius = 5.0 + contentView.layer.masksToBounds = true + contentView.layer.borderWidth = 0.5 + contentView.addSubview(titleLabel) + contentView.addSubview(subTitleTextView) + contentView.backgroundColor = UIColor.colorFromRGB(0xFFFFFF) + contentView.layer.borderColor = UIColor.colorFromRGB(0xCCCCCC).cgColor + view.addSubview(contentView) + } + + private func setupTitleLabel() { + titleLabel.text = "" + titleLabel.numberOfLines = 1 + titleLabel.textAlignment = .center + titleLabel.font = UIFont(name: kFont, size:25) + titleLabel.textColor = UIColor.colorFromRGB(0x575757) + } + + private func setupSubtitleTextView() { + subTitleTextView.text = "" + subTitleTextView.textAlignment = .center + subTitleTextView.font = UIFont(name: kFont, size:16) + subTitleTextView.textColor = UIColor.colorFromRGB(0x797979) + subTitleTextView.isEditable = false + } + + private func resizeAndRelayout() { + let mainScreenBounds = UIScreen.main.bounds + self.view.frame.size = mainScreenBounds.size + let x: CGFloat = kWidthMargin + var y: CGFloat = KTopMargin + let width: CGFloat = kContentWidth - (kWidthMargin*2) + + if animatedView != nil { + animatedView!.frame = CGRect(x: (kContentWidth - kAnimatedViewHeight) / 2.0, y: y, width: kAnimatedViewHeight, height: kAnimatedViewHeight) + contentView.addSubview(animatedView!) + y += kAnimatedViewHeight + kHeightMargin + } + + if imageView != nil { + imageView!.frame = CGRect(x: (kContentWidth - kAnimatedViewHeight) / 2.0, y: y, width: kAnimatedViewHeight, height: kAnimatedViewHeight) + contentView.addSubview(imageView!) + y += imageView!.frame.size.height + kHeightMargin + } + + // Title + if self.titleLabel.text != nil { + titleLabel.frame = CGRect(x: x, y: y, width: width, height: kTitleHeight) + contentView.addSubview(titleLabel) + y += kTitleHeight + kHeightMargin + } + + // Subtitle + if self.subTitleTextView.text.isEmpty == false { + let subtitleString = subTitleTextView.text! as NSString + let rect = subtitleString.boundingRect(with: CGSize(width: width, height: 0.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSFontAttributeName:subTitleTextView.font!], context: nil) + textViewHeight = ceil(rect.size.height) + 10.0 + subTitleTextView.frame = CGRect(x: x, y: y, width: width, height: textViewHeight) + contentView.addSubview(subTitleTextView) + y += textViewHeight + kHeightMargin + } + + var buttonRect:[CGRect] = [] + for button in buttons { + let string = button.title(for: UIControlState())! as NSString + buttonRect.append(string.boundingRect(with: CGSize(width: width, height:0.0), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes:[NSFontAttributeName:button.titleLabel!.font], context:nil)) + } + + var totalWidth: CGFloat = 0.0 + if buttons.count == 2 { + totalWidth = buttonRect[0].size.width + buttonRect[1].size.width + kWidthMargin + 40.0 + } + else{ + totalWidth = buttonRect[0].size.width + 20.0 + } + y += kHeightMargin + var buttonX = (kContentWidth - totalWidth ) / 2.0 + for i in 0 ..< buttons.count { + + buttons[i].frame = CGRect(x: buttonX, y: y, width: buttonRect[i].size.width + 20.0, height: buttonRect[i].size.height + 10.0) + buttonX = buttons[i].frame.origin.x + kWidthMargin + buttonRect[i].size.width + 20.0 + buttons[i].layer.cornerRadius = 5.0 + self.contentView.addSubview(buttons[i]) + buttons[i].addTarget(self, action: #selector(SweetAlert.pressed(_:)), for: UIControlEvents.touchUpInside) + + } + y += kHeightMargin + buttonRect[0].size.height + 10.0 + if y > kMaxHeight { + let diff = y - kMaxHeight + let sFrame = subTitleTextView.frame + subTitleTextView.frame = CGRect(x: sFrame.origin.x, y: sFrame.origin.y, width: sFrame.width, height: sFrame.height - diff) + + for button in buttons { + let bFrame = button.frame + button.frame = CGRect(x: bFrame.origin.x, y: bFrame.origin.y - diff, width: bFrame.width, height: bFrame.height) + } + + y = kMaxHeight + } + + contentView.frame = CGRect(x: (mainScreenBounds.size.width - kContentWidth) / 2.0, y: (mainScreenBounds.size.height - y) / 2.0, width: kContentWidth, height: y) + contentView.clipsToBounds = true + } + + open func pressed(_ sender: UIButton!) { + self.closeAlert(sender.tag) + } + + open override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + var sz = UIScreen.main.bounds.size + let sver = UIDevice.current.systemVersion as NSString + let ver = sver.floatValue + if ver < 8.0 { + // iOS versions before 7.0 did not switch the width and height on device roration + if UIInterfaceOrientationIsLandscape(UIApplication.shared.statusBarOrientation) { + let ssz = sz + sz = CGSize(width:ssz.height, height:ssz.width) + } + } + self.resizeAndRelayout() + } + + func closeAlert(_ buttonIndex:Int){ + if userAction != nil { + let isOtherButton = buttonIndex == 0 ? true: false + SweetAlertContext.shouldNotAnimate = true + userAction!(isOtherButton) + SweetAlertContext.shouldNotAnimate = false + } + + UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions.curveEaseOut, animations: { () -> Void in + self.view.alpha = 0.0 + }) { (Bool) -> Void in + self.view.removeFromSuperview() + self.cleanUpAlert() + + //Releasing strong refrence of itself. + self.strongSelf = nil + } + } + + func cleanUpAlert() { + + if self.animatedView != nil { + self.animatedView!.removeFromSuperview() + self.animatedView = nil + } + self.contentView.removeFromSuperview() + self.contentView = UIView() + } + + open func showAlert(_ title: String) -> SweetAlert { + _ = self.showAlert(title, subTitle: nil, style: .none) + return self + } + + open func showAlert(_ title: String, subTitle: String?, style: AlertStyle) -> SweetAlert { + _ = self.showAlert(title, subTitle: subTitle, style: style, buttonTitle: "OK") + return self + + } + + open func showAlert(_ title: String, subTitle: String?, style: AlertStyle,buttonTitle: String, action: ((_ isOtherButton: Bool) -> Void)? = nil) -> SweetAlert { + _ = self.showAlert(title, subTitle: subTitle, style: style, buttonTitle: buttonTitle,buttonColor: UIColor.colorFromRGB(0xAEDEF4)) + userAction = action + return self + } + + open func showAlert(_ title: String, subTitle: String?, style: AlertStyle,buttonTitle: String,buttonColor: UIColor,action: ((_ isOtherButton: Bool) -> Void)? = nil) -> SweetAlert { + _ = self.showAlert(title, subTitle: subTitle, style: style, buttonTitle: buttonTitle,buttonColor: buttonColor,otherButtonTitle: + nil) + userAction = action + return self + } + + open func showAlert(_ title: String, subTitle: String?, style: AlertStyle,buttonTitle: String,buttonColor: UIColor,otherButtonTitle: + String?, action: ((_ isOtherButton: Bool) -> Void)? = nil) -> SweetAlert { + self.showAlert(title, subTitle: subTitle, style: style, buttonTitle: buttonTitle,buttonColor: buttonColor,otherButtonTitle: + otherButtonTitle,otherButtonColor: UIColor.red) + userAction = action + return self + } + + open func showAlert(_ title: String, subTitle: String?, style: AlertStyle,buttonTitle: String,buttonColor: UIColor,otherButtonTitle: + String?, otherButtonColor: UIColor?,action: ((_ isOtherButton: Bool) -> Void)? = nil) { + userAction = action + let window: UIWindow = UIApplication.shared.keyWindow! + window.addSubview(view) + window.bringSubview(toFront: view) + view.frame = window.bounds + self.setupContentView() + self.setupTitleLabel() + self.setupSubtitleTextView() + + switch style { + case .success: + self.animatedView = SuccessAnimatedView() + + case .error: + self.animatedView = CancelAnimatedView() + + case .warning: + self.animatedView = InfoAnimatedView() + + case let .customImag(imageFile): + if let image = UIImage(named: imageFile) { + self.imageView = UIImageView(image: image) + } + case .none: + self.animatedView = nil + } + + self.titleLabel.text = title + if subTitle != nil { + self.subTitleTextView.text = subTitle + } + buttons = [] + if buttonTitle.isEmpty == false { + let button: UIButton = UIButton(type: UIButtonType.custom) + button.setTitle(buttonTitle, for: UIControlState()) + button.backgroundColor = buttonColor + button.isUserInteractionEnabled = true + button.tag = 0 + buttons.append(button) + } + + if otherButtonTitle != nil && otherButtonTitle!.isEmpty == false { + let button: UIButton = UIButton(type: UIButtonType.custom) + button.setTitle(otherButtonTitle, for: UIControlState()) + button.backgroundColor = otherButtonColor + button.addTarget(self, action: #selector(SweetAlert.pressed(_:)), for: UIControlEvents.touchUpInside) + button.tag = 1 + buttons.append(button) + } + + resizeAndRelayout() + if SweetAlertContext.shouldNotAnimate == true { + //Do not animate Alert + if self.animatedView != nil { + self.animatedView!.animate() + } + } + else { + animateAlert() + } + } + + func animateAlert() { + + view.alpha = 0; + UIView.animate(withDuration: 0.1, animations: { () -> Void in + self.view.alpha = 1.0; + }) + + let previousTransform = self.contentView.transform + self.contentView.layer.transform = CATransform3DMakeScale(0.9, 0.9, 0.0); + UIView.animate(withDuration: 0.2, animations: { () -> Void in + self.contentView.layer.transform = CATransform3DMakeScale(1.1, 1.1, 0.0); + }, completion: { (Bool) -> Void in + UIView.animate(withDuration: 0.1, animations: { () -> Void in + self.contentView.layer.transform = CATransform3DMakeScale(0.9, 0.9, 0.0); + }, completion: { (Bool) -> Void in UIView.animate(withDuration: 0.1, animations: { () -> Void in - self.view.alpha = 1.0; + self.contentView.layer.transform = CATransform3DMakeScale(1.0, 1.0, 0.0); + if self.animatedView != nil { + self.animatedView!.animate() + } + + }, completion: { (Bool) -> Void in + + self.contentView.transform = previousTransform }) - - let previousTransform = self.contentView.transform - self.contentView.layer.transform = CATransform3DMakeScale(0.9, 0.9, 0.0); - UIView.animate(withDuration: 0.2, animations: { () -> Void in - self.contentView.layer.transform = CATransform3DMakeScale(1.1, 1.1, 0.0); - }, completion: { (Bool) -> Void in - UIView.animate(withDuration: 0.1, animations: { () -> Void in - self.contentView.layer.transform = CATransform3DMakeScale(0.9, 0.9, 0.0); - }, completion: { (Bool) -> Void in - UIView.animate(withDuration: 0.1, animations: { () -> Void in - self.contentView.layer.transform = CATransform3DMakeScale(1.0, 1.0, 0.0); - if self.animatedView != nil { - self.animatedView!.animate() - } - - }, completion: { (Bool) -> Void in - - self.contentView.transform = previousTransform - }) - }) - }) - } - - private struct SweetAlertContext { - static var shouldNotAnimate = false - } + }) + }) + } + + private struct SweetAlertContext { + static var shouldNotAnimate = false + } } // MARK: - @@ -340,274 +340,274 @@ open class SweetAlert: UIViewController { // MARK: Animatable Views class AnimatableView: UIView { - func animate(){ - print("Should overide by subclasss", terminator: "") - } + func animate(){ + print("Should overide by subclasss", terminator: "") + } } class CancelAnimatedView: AnimatableView { - - var circleLayer = CAShapeLayer() - var crossPathLayer = CAShapeLayer() - - override required init(frame: CGRect) { - super.init(frame: frame) - setupLayers() - var t = CATransform3DIdentity; - t.m34 = 1.0 / -500.0; - t = CATransform3DRotate(t, CGFloat(90.0 * M_PI / 180.0), 1, 0, 0); - circleLayer.transform = t - crossPathLayer.opacity = 0.0 - } - - override func layoutSubviews() { - setupLayers() - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private var outlineCircle: CGPath { - let path = UIBezierPath() - let startAngle: CGFloat = CGFloat((0) / 180.0 * M_PI) //0 - let endAngle: CGFloat = CGFloat((360) / 180.0 * M_PI) //360 - path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.width/2.0), radius: self.frame.size.width/2.0, startAngle: startAngle, endAngle: endAngle, clockwise: false) - - return path.cgPath - } - - private var crossPath: CGPath { - let path = UIBezierPath() - let factor:CGFloat = self.frame.size.width / 5.0 - path.move(to: CGPoint(x: self.frame.size.height/2.0-factor,y: self.frame.size.height/2.0-factor)) - path.addLine(to: CGPoint(x: self.frame.size.height/2.0+factor,y: self.frame.size.height/2.0+factor)) - path.move(to: CGPoint(x: self.frame.size.height/2.0+factor,y: self.frame.size.height/2.0-factor)) - path.addLine(to: CGPoint(x: self.frame.size.height/2.0-factor,y: self.frame.size.height/2.0+factor)) - - return path.cgPath - } - - private func setupLayers() { - circleLayer.path = outlineCircle - circleLayer.fillColor = UIColor.clear.cgColor; - circleLayer.strokeColor = UIColor.colorFromRGB(0xF27474).cgColor; - circleLayer.lineCap = kCALineCapRound - circleLayer.lineWidth = 4; - circleLayer.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height) - circleLayer.position = CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0) - self.layer.addSublayer(circleLayer) - - crossPathLayer.path = crossPath - crossPathLayer.fillColor = UIColor.clear.cgColor; - crossPathLayer.strokeColor = UIColor.colorFromRGB(0xF27474).cgColor; - crossPathLayer.lineCap = kCALineCapRound - crossPathLayer.lineWidth = 4; - crossPathLayer.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height) - crossPathLayer.position = CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0) - self.layer.addSublayer(crossPathLayer) - - } - - override func animate() { - var t = CATransform3DIdentity; - t.m34 = 1.0 / -500.0; - t = CATransform3DRotate(t, CGFloat(90.0 * M_PI / 180.0), 1, 0, 0); - - var t2 = CATransform3DIdentity; - t2.m34 = 1.0 / -500.0; - t2 = CATransform3DRotate(t2, CGFloat(-M_PI), 1, 0, 0); - - let animation = CABasicAnimation(keyPath: "transform") - let time = 0.3 - animation.duration = time; - animation.fromValue = NSValue(caTransform3D: t) - animation.toValue = NSValue(caTransform3D:t2) - animation.isRemovedOnCompletion = false - animation.fillMode = kCAFillModeForwards - self.circleLayer.add(animation, forKey: "transform") - - - var scale = CATransform3DIdentity; - scale = CATransform3DScale(scale, 0.3, 0.3, 0) - - - let crossAnimation = CABasicAnimation(keyPath: "transform") - crossAnimation.duration = 0.3; - crossAnimation.beginTime = CACurrentMediaTime() + time - crossAnimation.fromValue = NSValue(caTransform3D: scale) - crossAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0.8, 0.7, 2.0) - crossAnimation.toValue = NSValue(caTransform3D:CATransform3DIdentity) - self.crossPathLayer.add(crossAnimation, forKey: "scale") - - let fadeInAnimation = CABasicAnimation(keyPath: "opacity") - fadeInAnimation.duration = 0.3; - fadeInAnimation.beginTime = CACurrentMediaTime() + time - fadeInAnimation.fromValue = 0.3 - fadeInAnimation.toValue = 1.0 - fadeInAnimation.isRemovedOnCompletion = false - fadeInAnimation.fillMode = kCAFillModeForwards - self.crossPathLayer.add(fadeInAnimation, forKey: "opacity") - } - + + var circleLayer = CAShapeLayer() + var crossPathLayer = CAShapeLayer() + + override required init(frame: CGRect) { + super.init(frame: frame) + setupLayers() + var t = CATransform3DIdentity; + t.m34 = 1.0 / -500.0; + t = CATransform3DRotate(t, CGFloat(90.0 * M_PI / 180.0), 1, 0, 0); + circleLayer.transform = t + crossPathLayer.opacity = 0.0 + } + + override func layoutSubviews() { + setupLayers() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var outlineCircle: CGPath { + let path = UIBezierPath() + let startAngle: CGFloat = CGFloat((0) / 180.0 * M_PI) //0 + let endAngle: CGFloat = CGFloat((360) / 180.0 * M_PI) //360 + path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.width/2.0), radius: self.frame.size.width/2.0, startAngle: startAngle, endAngle: endAngle, clockwise: false) + + return path.cgPath + } + + private var crossPath: CGPath { + let path = UIBezierPath() + let factor:CGFloat = self.frame.size.width / 5.0 + path.move(to: CGPoint(x: self.frame.size.height/2.0-factor,y: self.frame.size.height/2.0-factor)) + path.addLine(to: CGPoint(x: self.frame.size.height/2.0+factor,y: self.frame.size.height/2.0+factor)) + path.move(to: CGPoint(x: self.frame.size.height/2.0+factor,y: self.frame.size.height/2.0-factor)) + path.addLine(to: CGPoint(x: self.frame.size.height/2.0-factor,y: self.frame.size.height/2.0+factor)) + + return path.cgPath + } + + private func setupLayers() { + circleLayer.path = outlineCircle + circleLayer.fillColor = UIColor.clear.cgColor; + circleLayer.strokeColor = UIColor.colorFromRGB(0xF27474).cgColor; + circleLayer.lineCap = kCALineCapRound + circleLayer.lineWidth = 4; + circleLayer.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height) + circleLayer.position = CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0) + self.layer.addSublayer(circleLayer) + + crossPathLayer.path = crossPath + crossPathLayer.fillColor = UIColor.clear.cgColor; + crossPathLayer.strokeColor = UIColor.colorFromRGB(0xF27474).cgColor; + crossPathLayer.lineCap = kCALineCapRound + crossPathLayer.lineWidth = 4; + crossPathLayer.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height) + crossPathLayer.position = CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0) + self.layer.addSublayer(crossPathLayer) + + } + + override func animate() { + var t = CATransform3DIdentity; + t.m34 = 1.0 / -500.0; + t = CATransform3DRotate(t, CGFloat(90.0 * M_PI / 180.0), 1, 0, 0); + + var t2 = CATransform3DIdentity; + t2.m34 = 1.0 / -500.0; + t2 = CATransform3DRotate(t2, CGFloat(-M_PI), 1, 0, 0); + + let animation = CABasicAnimation(keyPath: "transform") + let time = 0.3 + animation.duration = time; + animation.fromValue = NSValue(caTransform3D: t) + animation.toValue = NSValue(caTransform3D:t2) + animation.isRemovedOnCompletion = false + animation.fillMode = kCAFillModeForwards + self.circleLayer.add(animation, forKey: "transform") + + + var scale = CATransform3DIdentity; + scale = CATransform3DScale(scale, 0.3, 0.3, 0) + + + let crossAnimation = CABasicAnimation(keyPath: "transform") + crossAnimation.duration = 0.3; + crossAnimation.beginTime = CACurrentMediaTime() + time + crossAnimation.fromValue = NSValue(caTransform3D: scale) + crossAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0.8, 0.7, 2.0) + crossAnimation.toValue = NSValue(caTransform3D:CATransform3DIdentity) + self.crossPathLayer.add(crossAnimation, forKey: "scale") + + let fadeInAnimation = CABasicAnimation(keyPath: "opacity") + fadeInAnimation.duration = 0.3; + fadeInAnimation.beginTime = CACurrentMediaTime() + time + fadeInAnimation.fromValue = 0.3 + fadeInAnimation.toValue = 1.0 + fadeInAnimation.isRemovedOnCompletion = false + fadeInAnimation.fillMode = kCAFillModeForwards + self.crossPathLayer.add(fadeInAnimation, forKey: "opacity") + } + } class InfoAnimatedView: AnimatableView { - - var circleLayer = CAShapeLayer() - var crossPathLayer = CAShapeLayer() - - override init(frame: CGRect) { - super.init(frame: frame) - setupLayers() - } - - override func layoutSubviews() { - setupLayers() - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - var outlineCircle: CGPath { - let path = UIBezierPath() - let startAngle: CGFloat = CGFloat((0) / 180.0 * M_PI) //0 - let endAngle: CGFloat = CGFloat((360) / 180.0 * M_PI) //360 - path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.width/2.0), radius: self.frame.size.width/2.0, startAngle: startAngle, endAngle: endAngle, clockwise: false) - - let factor:CGFloat = self.frame.size.width / 1.5 - path.move(to: CGPoint(x: self.frame.size.width/2.0 , y: 15.0)) - path.addLine(to: CGPoint(x: self.frame.size.width/2.0,y: factor)) - path.move(to: CGPoint(x: self.frame.size.width/2.0,y: factor + 10.0)) - path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0,y: factor + 10.0), radius: 1.0, startAngle: startAngle, endAngle: endAngle, clockwise: true) - - return path.cgPath - } - - func setupLayers() { - circleLayer.path = outlineCircle - circleLayer.fillColor = UIColor.clear.cgColor; - circleLayer.strokeColor = UIColor.colorFromRGB(0xF8D486).cgColor; - circleLayer.lineCap = kCALineCapRound - circleLayer.lineWidth = 4; - circleLayer.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height) - circleLayer.position = CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0) - self.layer.addSublayer(circleLayer) - } - - override func animate() { - - let colorAnimation = CABasicAnimation(keyPath:"strokeColor") - colorAnimation.duration = 1.0; - colorAnimation.repeatCount = HUGE - colorAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - colorAnimation.autoreverses = true - colorAnimation.fromValue = UIColor.colorFromRGB(0xF7D58B).cgColor - colorAnimation.toValue = UIColor.colorFromRGB(0xF2A665).cgColor - circleLayer.add(colorAnimation, forKey: "strokeColor") - } + + var circleLayer = CAShapeLayer() + var crossPathLayer = CAShapeLayer() + + override init(frame: CGRect) { + super.init(frame: frame) + setupLayers() + } + + override func layoutSubviews() { + setupLayers() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + var outlineCircle: CGPath { + let path = UIBezierPath() + let startAngle: CGFloat = CGFloat((0) / 180.0 * M_PI) //0 + let endAngle: CGFloat = CGFloat((360) / 180.0 * M_PI) //360 + path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.width/2.0), radius: self.frame.size.width/2.0, startAngle: startAngle, endAngle: endAngle, clockwise: false) + + let factor:CGFloat = self.frame.size.width / 1.5 + path.move(to: CGPoint(x: self.frame.size.width/2.0 , y: 15.0)) + path.addLine(to: CGPoint(x: self.frame.size.width/2.0,y: factor)) + path.move(to: CGPoint(x: self.frame.size.width/2.0,y: factor + 10.0)) + path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0,y: factor + 10.0), radius: 1.0, startAngle: startAngle, endAngle: endAngle, clockwise: true) + + return path.cgPath + } + + func setupLayers() { + circleLayer.path = outlineCircle + circleLayer.fillColor = UIColor.clear.cgColor; + circleLayer.strokeColor = UIColor.colorFromRGB(0xF8D486).cgColor; + circleLayer.lineCap = kCALineCapRound + circleLayer.lineWidth = 4; + circleLayer.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height) + circleLayer.position = CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0) + self.layer.addSublayer(circleLayer) + } + + override func animate() { + + let colorAnimation = CABasicAnimation(keyPath:"strokeColor") + colorAnimation.duration = 1.0; + colorAnimation.repeatCount = HUGE + colorAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + colorAnimation.autoreverses = true + colorAnimation.fromValue = UIColor.colorFromRGB(0xF7D58B).cgColor + colorAnimation.toValue = UIColor.colorFromRGB(0xF2A665).cgColor + circleLayer.add(colorAnimation, forKey: "strokeColor") + } } class SuccessAnimatedView: AnimatableView { - - var circleLayer = CAShapeLayer() - var outlineLayer = CAShapeLayer() - - override init(frame: CGRect) { - super.init(frame: frame) - setupLayers() - circleLayer.strokeStart = 0.0 - circleLayer.strokeEnd = 0.0 - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - setupLayers() - } - - - var outlineCircle: CGPath { - let path = UIBezierPath() - let startAngle: CGFloat = CGFloat((0) / 180.0 * M_PI) //0 - let endAngle: CGFloat = CGFloat((360) / 180.0 * M_PI) //360 - path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0), radius: self.frame.size.width/2.0, startAngle: startAngle, endAngle: endAngle, clockwise: false) - return path.cgPath - } - - var path: CGPath { - let path = UIBezierPath() - let startAngle:CGFloat = CGFloat((60) / 180.0 * M_PI) //60 - let endAngle:CGFloat = CGFloat((200) / 180.0 * M_PI) //190 - path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0), radius: self.frame.size.width/2.0, startAngle: startAngle, endAngle: endAngle, clockwise: false) - path.addLine(to: CGPoint(x: 36.0 - 10.0 ,y: 60.0 - 10.0)) - path.addLine(to: CGPoint(x: 85.0 - 20.0, y: 30.0 - 20.0)) - return path.cgPath - } - - - func setupLayers() { - - outlineLayer.position = CGPoint(x: 0, - y: 0); - outlineLayer.path = outlineCircle - outlineLayer.fillColor = UIColor.clear.cgColor; - outlineLayer.strokeColor = UIColor(red: 150.0/255.0, green: 216.0/255.0, blue: 115.0/255.0, alpha: 1.0).cgColor; - outlineLayer.lineCap = kCALineCapRound - outlineLayer.lineWidth = 4; - outlineLayer.opacity = 0.1 - self.layer.addSublayer(outlineLayer) - - circleLayer.position = CGPoint(x: 0, - y: 0); - circleLayer.path = path - circleLayer.fillColor = UIColor.clear.cgColor; - circleLayer.strokeColor = UIColor(red: 150.0/255.0, green: 216.0/255.0, blue: 115.0/255.0, alpha: 1.0).cgColor; - circleLayer.lineCap = kCALineCapRound - circleLayer.lineWidth = 4; - circleLayer.actions = [ - "strokeStart": NSNull(), - "strokeEnd": NSNull(), - "transform": NSNull() - ] - self.layer.addSublayer(circleLayer) - } - - override func animate() { - let strokeStart = CABasicAnimation(keyPath: "strokeStart") - let strokeEnd = CABasicAnimation(keyPath: "strokeEnd") - let factor = 0.045 - strokeEnd.fromValue = 0.00 - strokeEnd.toValue = 0.93 - strokeEnd.duration = 10.0*factor - let timing = CAMediaTimingFunction(controlPoints: 0.3, 0.6, 0.8, 1.2) - strokeEnd.timingFunction = timing - - strokeStart.fromValue = 0.0 - strokeStart.toValue = 0.68 - strokeStart.duration = 7.0*factor - strokeStart.beginTime = CACurrentMediaTime() + 3.0*factor - strokeStart.fillMode = kCAFillModeBackwards - strokeStart.timingFunction = timing - circleLayer.strokeStart = 0.68 - circleLayer.strokeEnd = 0.93 - self.circleLayer.add(strokeEnd, forKey: "strokeEnd") - self.circleLayer.add(strokeStart, forKey: "strokeStart") - } - + + var circleLayer = CAShapeLayer() + var outlineLayer = CAShapeLayer() + + override init(frame: CGRect) { + super.init(frame: frame) + setupLayers() + circleLayer.strokeStart = 0.0 + circleLayer.strokeEnd = 0.0 + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + setupLayers() + } + + + var outlineCircle: CGPath { + let path = UIBezierPath() + let startAngle: CGFloat = CGFloat((0) / 180.0 * M_PI) //0 + let endAngle: CGFloat = CGFloat((360) / 180.0 * M_PI) //360 + path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0), radius: self.frame.size.width/2.0, startAngle: startAngle, endAngle: endAngle, clockwise: false) + return path.cgPath + } + + var path: CGPath { + let path = UIBezierPath() + let startAngle:CGFloat = CGFloat((60) / 180.0 * M_PI) //60 + let endAngle:CGFloat = CGFloat((200) / 180.0 * M_PI) //190 + path.addArc(withCenter: CGPoint(x: self.frame.size.width/2.0, y: self.frame.size.height/2.0), radius: self.frame.size.width/2.0, startAngle: startAngle, endAngle: endAngle, clockwise: false) + path.addLine(to: CGPoint(x: 36.0 - 10.0 ,y: 60.0 - 10.0)) + path.addLine(to: CGPoint(x: 85.0 - 20.0, y: 30.0 - 20.0)) + return path.cgPath + } + + + func setupLayers() { + + outlineLayer.position = CGPoint(x: 0, + y: 0); + outlineLayer.path = outlineCircle + outlineLayer.fillColor = UIColor.clear.cgColor; + outlineLayer.strokeColor = UIColor(red: 150.0/255.0, green: 216.0/255.0, blue: 115.0/255.0, alpha: 1.0).cgColor; + outlineLayer.lineCap = kCALineCapRound + outlineLayer.lineWidth = 4; + outlineLayer.opacity = 0.1 + self.layer.addSublayer(outlineLayer) + + circleLayer.position = CGPoint(x: 0, + y: 0); + circleLayer.path = path + circleLayer.fillColor = UIColor.clear.cgColor; + circleLayer.strokeColor = UIColor(red: 150.0/255.0, green: 216.0/255.0, blue: 115.0/255.0, alpha: 1.0).cgColor; + circleLayer.lineCap = kCALineCapRound + circleLayer.lineWidth = 4; + circleLayer.actions = [ + "strokeStart": NSNull(), + "strokeEnd": NSNull(), + "transform": NSNull() + ] + self.layer.addSublayer(circleLayer) + } + + override func animate() { + let strokeStart = CABasicAnimation(keyPath: "strokeStart") + let strokeEnd = CABasicAnimation(keyPath: "strokeEnd") + let factor = 0.045 + strokeEnd.fromValue = 0.00 + strokeEnd.toValue = 0.93 + strokeEnd.duration = 10.0*factor + let timing = CAMediaTimingFunction(controlPoints: 0.3, 0.6, 0.8, 1.2) + strokeEnd.timingFunction = timing + + strokeStart.fromValue = 0.0 + strokeStart.toValue = 0.68 + strokeStart.duration = 7.0*factor + strokeStart.beginTime = CACurrentMediaTime() + 3.0*factor + strokeStart.fillMode = kCAFillModeBackwards + strokeStart.timingFunction = timing + circleLayer.strokeStart = 0.68 + circleLayer.strokeEnd = 0.93 + self.circleLayer.add(strokeEnd, forKey: "strokeEnd") + self.circleLayer.add(strokeStart, forKey: "strokeStart") + } + } extension UIColor { - class func colorFromRGB(_ rgbValue: UInt) -> UIColor { - return UIColor( - red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, - green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, - blue: CGFloat(rgbValue & 0x0000FF) / 255.0, - alpha: CGFloat(1.0) - ) - } + class func colorFromRGB(_ rgbValue: UInt) -> UIColor { + return UIColor( + red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0, + alpha: CGFloat(1.0) + ) + } } diff --git a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Util.swift b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Util.swift index e8fd6ce4..ecf2babb 100644 --- a/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Util.swift +++ b/Examples/BVSDKDemo/BVSDKDemo/Product Recommendations/Util.swift @@ -10,194 +10,194 @@ import NVActivityIndicatorView import FontAwesomeKit class Util: NSObject { - - static func createSpinner() -> NVActivityIndicatorView { - - let spinner = NVActivityIndicatorView(frame: CGRect(x: 0,y: 0,width: 40,height: 40), type: .ballScaleMultiple, color: .lightGray, padding: 40) - spinner.startAnimating() - return spinner - - } - - static func createSpinner(_ color : UIColor, size: CGSize, padding: CGFloat) -> NVActivityIndicatorView { - - let spinner = NVActivityIndicatorView(frame: CGRect(x: 0,y: 0,width: size.width,height: size.height), type: .ballScaleMultiple, color: color, padding: padding) - spinner.startAnimating() - return spinner - - } - - static func createErrorLabel() -> UILabel { - - let errorLabel = UILabel() - errorLabel.text = "An Error Occurred" - errorLabel.textAlignment = .center - errorLabel.textColor = UIColor.lightGray - errorLabel.numberOfLines = 1 - return errorLabel - - } - - /// Get a default light grey icon - static func getFontAwesomeIconImage(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!)) -> UIImage { - - return self.getFontAwesomeIconImage(icon, color: UIColor.lightGray, alpha: 0.5, size: 20) - - } - - /// Get an icon with specified size, color, and alpha - static func getFontAwesomeIconImage(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!), - color : UIColor, - alpha : CGFloat, - size : CGFloat) -> UIImage { - - let newIcon = icon(size) - newIcon?.addAttribute( - NSForegroundColorAttributeName, - value: color.withAlphaComponent(alpha) - ) - - return newIcon!.image(with: CGSize(width: size, height: size)) - - } - + + static func createSpinner() -> NVActivityIndicatorView { + + let spinner = NVActivityIndicatorView(frame: CGRect(x: 0,y: 0,width: 40,height: 40), type: .ballScaleMultiple, color: .lightGray, padding: 40) + spinner.startAnimating() + return spinner + + } + + static func createSpinner(_ color : UIColor, size: CGSize, padding: CGFloat) -> NVActivityIndicatorView { + + let spinner = NVActivityIndicatorView(frame: CGRect(x: 0,y: 0,width: size.width,height: size.height), type: .ballScaleMultiple, color: color, padding: padding) + spinner.startAnimating() + return spinner + + } + + static func createErrorLabel() -> UILabel { + + let errorLabel = UILabel() + errorLabel.text = "An Error Occurred" + errorLabel.textAlignment = .center + errorLabel.textColor = UIColor.lightGray + errorLabel.numberOfLines = 1 + return errorLabel + + } + + /// Get a default light grey icon + static func getFontAwesomeIconImage(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!)) -> UIImage { + + return self.getFontAwesomeIconImage(icon, color: UIColor.lightGray, alpha: 0.5, size: 20) + + } + + /// Get an icon with specified size, color, and alpha + static func getFontAwesomeIconImage(_ icon : ((_ size: CGFloat) -> FAKFontAwesome!), + color : UIColor, + alpha : CGFloat, + size : CGFloat) -> UIImage { + + let newIcon = icon(size) + newIcon?.addAttribute( + NSForegroundColorAttributeName, + value: color.withAlphaComponent(alpha) + ) + + return newIcon!.image(with: CGSize(width: size, height: size)) + + } + } extension UIColor { - - static func bazaarvoiceNavy() -> UIColor { - return UIColor(red: 0, green: 0.24, blue: 0.3, alpha: 1.0) - } - - static func bazaarvoiceTeal() -> UIColor { - return UIColor(red: 0, green: 0.67, blue: 0.56, alpha: 1.0) - } - - static func bazaarvoiceGold() -> UIColor { - return UIColor(red: 0.99, green: 0.72, blue: 0.07, alpha: 1.0) - } - - static func appBackground() -> UIColor { - return UIColor(red: 245/255, green: 245/255, blue: 245/255, alpha: 1.0) - } - + + static func bazaarvoiceNavy() -> UIColor { + return UIColor(red: 0, green: 0.24, blue: 0.3, alpha: 1.0) + } + + static func bazaarvoiceTeal() -> UIColor { + return UIColor(red: 0, green: 0.67, blue: 0.56, alpha: 1.0) + } + + static func bazaarvoiceGold() -> UIColor { + return UIColor(red: 0.99, green: 0.72, blue: 0.07, alpha: 1.0) + } + + static func appBackground() -> UIColor { + return UIColor(red: 245/255, green: 245/255, blue: 245/255, alpha: 1.0) + } + } extension UIImageView { - - /*! - Fade in an imageView with a placeholder image, if the image is not cached. - */ - public func sd_setImageWithURLWithFade(_ url: URL!, placeholderImage placeholder: UIImage!) - { - - self.sd_setImage(with: url, placeholderImage: placeholder, options: []) { (image, error, cacheType, url) in - if cacheType == .none - { - self.alpha = 0 - UIView.transition(with: self, duration: 0.6, options: UIViewAnimationOptions.transitionCrossDissolve, animations: { () -> Void in - self.image = image - self.alpha = 1 - }, completion: nil) - - } - } - - } + + /*! + Fade in an imageView with a placeholder image, if the image is not cached. + */ + public func sd_setImageWithURLWithFade(_ url: URL!, placeholderImage placeholder: UIImage!) + { + + self.sd_setImage(with: url, placeholderImage: placeholder, options: []) { (image, error, cacheType, url) in + if cacheType == .none + { + self.alpha = 0 + UIView.transition(with: self, duration: 0.6, options: UIViewAnimationOptions.transitionCrossDissolve, animations: { () -> Void in + self.image = image + self.alpha = 1 + }, completion: nil) + + } + } + + } } // Utility to check if a partcilar ViewController wants to support different orientations than the default build. extension UINavigationController { - open override var supportedInterfaceOrientations : UIInterfaceOrientationMask { - if let visible = visibleViewController { - return visible.supportedInterfaceOrientations - } - - return .portrait + open override var supportedInterfaceOrientations : UIInterfaceOrientationMask { + if let visible = visibleViewController { + return visible.supportedInterfaceOrientations } - open override var shouldAutorotate : Bool { - if ((visibleViewController) != nil){ - return self.visibleViewController!.shouldAutorotate - } else { - return false - } + return .portrait + } + + open override var shouldAutorotate : Bool { + if ((visibleViewController) != nil){ + return self.visibleViewController!.shouldAutorotate + } else { + return false } + } } // Workaround for bug: http://www.openradar.me/22385765 extension UIAlertController { - open override var shouldAutorotate : Bool { - return true - } - open override var supportedInterfaceOrientations : UIInterfaceOrientationMask { - return UIInterfaceOrientationMask.all - } + open override var shouldAutorotate : Bool { + return true + } + open override var supportedInterfaceOrientations : UIInterfaceOrientationMask { + return UIInterfaceOrientationMask.all + } } // Test if current Platform is simulator or not struct Platform { - static let isSimulator: Bool = { - var isSim = false - #if arch(i386) || arch(x86_64) - isSim = true - #endif - return isSim - }() + static let isSimulator: Bool = { + var isSim = false + #if arch(i386) || arch(x86_64) + isSim = true + #endif + return isSim + }() + + + + static func createSpinner() -> NVActivityIndicatorView { + + let spinner = NVActivityIndicatorView(frame: CGRect(x: 0,y: 0,width: 40,height: 40), type: .ballScaleMultiple, color: .lightGray, padding: 40) + spinner.startAnimating() + return spinner + + } + + static func createSpinner(_ color : UIColor, size: CGSize, padding: CGFloat) -> NVActivityIndicatorView { + + let spinner = NVActivityIndicatorView(frame: CGRect(x: 0,y: 0,width: size.width,height: size.height), type: .ballScaleMultiple, color: color, padding: padding) + spinner.startAnimating() + return spinner + + } + + static func createErrorLabel() -> UILabel { + + let errorLabel = UILabel() + errorLabel.text = "An Error Occurred" + errorLabel.textAlignment = .center + errorLabel.textColor = UIColor.lightGray + errorLabel.numberOfLines = 1 + return errorLabel + + } + +} +extension UILabel { + + func linkAuthorNameLabel(fullText : String, author : String, target: Any?, selector : Selector?) { + let attributedString = NSMutableAttributedString(string: fullText) + attributedString.setAttributes([:], range: NSRange(0.. NVActivityIndicatorView { - - let spinner = NVActivityIndicatorView(frame: CGRect(x: 0,y: 0,width: 40,height: 40), type: .ballScaleMultiple, color: .lightGray, padding: 40) - spinner.startAnimating() - return spinner - - } - - static func createSpinner(_ color : UIColor, size: CGSize, padding: CGFloat) -> NVActivityIndicatorView { - - let spinner = NVActivityIndicatorView(frame: CGRect(x: 0,y: 0,width: size.width,height: size.height), type: .ballScaleMultiple, color: color, padding: padding) - spinner.startAnimating() - return spinner - - } + let colorFontAttribute = [NSForegroundColorAttributeName: UIColor.blue] - static func createErrorLabel() -> UILabel { - - let errorLabel = UILabel() - errorLabel.text = "An Error Occurred" - errorLabel.textAlignment = .center - errorLabel.textColor = UIColor.lightGray - errorLabel.numberOfLines = 1 - return errorLabel - - } + attributedString.addAttributes(colorFontAttribute , range: (fullText as NSString).range(of: author, options: .backwards)) -} - -extension UILabel { + self.attributedText = attributedString + self.isUserInteractionEnabled = true - func linkAuthorNameLabel(fullText : String, author : String, target: Any?, selector : Selector?) { - - let attributedString = NSMutableAttributedString(string: fullText) - attributedString.setAttributes([:], range: NSRange(0..