diff --git a/CHANGES.rst b/CHANGES.rst index aacada115..72a325281 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,19 @@ +Changes in 0.6.16 (2018-05-23) +=============================================== + +Improvements: + * Upgrade MatrixKit version (v0.7.12). + * Display quick replies in timeline (#1858). + * Beginning of "Send sticker" support (#1860). + * Use existing message.mp3 for notification sounds, thanks to @pixlwave (PR #1835). + * GDPR: Display the consent tool in case of M_CONSENT_NOT_GIVEN error (#1871). + +Bug fix: + * Fix the display of side borders of HTML blockquotes (#1857). + * Moved UI update to main queue, thanks to @Taiwo (PR #1854). + * Timestamps say 'Yesterday' when it is today (#1274), thanks to @pixlwave (PR #1865). + * RoomVC: messages with link blink forever #1869 + Changes in 0.6.15 (2018-04-23) =============================================== diff --git a/Podfile b/Podfile index 5a7f90730..f00ef7e3f 100644 --- a/Podfile +++ b/Podfile @@ -9,7 +9,7 @@ source 'https://github.com/CocoaPods/Specs.git' # Different flavours of pods to MatrixKit # The current MatrixKit pod version -$matrixKitVersion = '0.7.11' +$matrixKitVersion = '0.7.12' # The develop branch version #$matrixKitVersion = 'develop' diff --git a/Podfile.lock b/Podfile.lock index 756593f9a..f99897a29 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,18 +1,18 @@ PODS: - - AFNetworking (3.2.0): - - AFNetworking/NSURLSession (= 3.2.0) - - AFNetworking/Reachability (= 3.2.0) - - AFNetworking/Security (= 3.2.0) - - AFNetworking/Serialization (= 3.2.0) - - AFNetworking/UIKit (= 3.2.0) - - AFNetworking/NSURLSession (3.2.0): + - AFNetworking (3.2.1): + - AFNetworking/NSURLSession (= 3.2.1) + - AFNetworking/Reachability (= 3.2.1) + - AFNetworking/Security (= 3.2.1) + - AFNetworking/Serialization (= 3.2.1) + - AFNetworking/UIKit (= 3.2.1) + - AFNetworking/NSURLSession (3.2.1): - AFNetworking/Reachability - AFNetworking/Security - AFNetworking/Serialization - - AFNetworking/Reachability (3.2.0) - - AFNetworking/Security (3.2.0) - - AFNetworking/Serialization (3.2.0) - - AFNetworking/UIKit (3.2.0): + - AFNetworking/Reachability (3.2.1) + - AFNetworking/Security (3.2.1) + - AFNetworking/Serialization (3.2.1) + - AFNetworking/UIKit (3.2.1): - AFNetworking/NSURLSession - cmark (0.24.1) - DTCoreText (1.6.21): @@ -43,34 +43,34 @@ PODS: - GZIP (1.2.1) - HPGrowingTextView (1.1) - libPhoneNumber-iOS (0.9.13) - - MatrixKit (0.7.11): + - MatrixKit (0.7.12): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.7.11) - - MatrixSDK (= 0.10.9) - - MatrixKit/AppExtension (0.7.11): + - MatrixKit/Core (= 0.7.12) + - MatrixSDK (= 0.10.10) + - MatrixKit/AppExtension (0.7.12): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - DTCoreText/Extension - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.10.9) - - MatrixKit/Core (0.7.11): + - MatrixSDK (= 0.10.10) + - MatrixKit/Core (0.7.12): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.10.9) - - MatrixSDK (0.10.9): - - MatrixSDK/Core (= 0.10.9) - - MatrixSDK/Core (0.10.9): + - MatrixSDK (= 0.10.10) + - MatrixSDK (0.10.10): + - MatrixSDK/Core (= 0.10.10) + - MatrixSDK/Core (0.10.10): - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.1) - OLMKit (~> 2.2.2) - Realm (~> 3.3.2) - - MatrixSDK/JingleCallStack (0.10.9): + - MatrixSDK/JingleCallStack (0.10.10): - MatrixSDK/Core - WebRTC (= 63.11.20455) - OLMKit (2.2.2): @@ -90,12 +90,28 @@ DEPENDENCIES: - cmark - DTCoreText - GBDeviceInfo (~> 5.1.0) - - MatrixKit (= 0.7.11) - - MatrixKit/AppExtension (= 0.7.11) + - MatrixKit (= 0.7.12) + - MatrixKit/AppExtension (= 0.7.12) - MatrixSDK/JingleCallStack - OLMKit - PiwikTracker (from `https://github.com/manuroe/matomo-sdk-ios.git`, branch `feature/CustomVariables`) +SPEC REPOS: + https://github.com/CocoaPods/Specs.git: + - AFNetworking + - cmark + - DTCoreText + - DTFoundation + - GBDeviceInfo + - GZIP + - HPGrowingTextView + - libPhoneNumber-iOS + - MatrixKit + - MatrixSDK + - OLMKit + - Realm + - WebRTC + EXTERNAL SOURCES: PiwikTracker: :branch: feature/CustomVariables @@ -107,7 +123,7 @@ CHECKOUT OPTIONS: :git: https://github.com/manuroe/matomo-sdk-ios.git SPEC CHECKSUMS: - AFNetworking: 8ac6017b94ea105479f7776e5288e48ae9c59bb4 + AFNetworking: b6f891fdfaed196b46c7a83cf209e09697b94057 cmark: ec0275215b504780287b6fca360224e384368af8 DTCoreText: e5d688cffc9f6a61eddd1a4f94e2046851230de3 DTFoundation: f03be9fd786f11e505bb8fc44e2a3732bf0917df @@ -115,13 +131,13 @@ SPEC CHECKSUMS: GZIP: 7ee835f989fb3c6ea79005fc90b8fa6af710a70d HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 libPhoneNumber-iOS: e444379ac18bbfbdefad571da735b2cd7e096caa - MatrixKit: fa148ae8eef4e60e65d36af0af1be28fc8264935 - MatrixSDK: 5c3a0ddfa141775d88565e627f491a066dd1e87c + MatrixKit: ac91b2667d1be1895994ee78654d6193a6644b09 + MatrixSDK: c74ed8b41e3e55b7510d7bddf612ed1985816e04 OLMKit: b9d8c0ffee9ea8c45bc0aaa9afb47f93fba7efbd PiwikTracker: 42862c7b13028065c3dfd36b4dc38db8a5765acf Realm: d927fbf66df5532cfafc08afb5f7e53ded37b894 WebRTC: f2a6203584745fe53532633397557876b5d71640 -PODFILE CHECKSUM: a3ec9e9795d74a34aeaa86fdbc1983a365e93e9a +PODFILE CHECKSUM: ba3cdb1f77ff6463e50f031aefd807535f5612ad -COCOAPODS: 1.4.0 +COCOAPODS: 1.5.0 diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj old mode 100755 new mode 100644 index 80875bba0..2908c6779 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -70,11 +70,11 @@ 327382C41F276AED00356143 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382C01F276AED00356143 /* Vector.strings */; }; 32795C0B203DA4C4002420E2 /* DisabledRoomInputToolbarView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32795C09203DA4C3002420E2 /* DisabledRoomInputToolbarView.m */; }; 32795C0C203DA4C4002420E2 /* DisabledRoomInputToolbarView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32795C0A203DA4C4002420E2 /* DisabledRoomInputToolbarView.xib */; }; + 3284A35120A07C210044F922 /* postMessageAPI.js in Resources */ = {isa = PBXBuildFile; fileRef = 3284A35020A07C210044F922 /* postMessageAPI.js */; }; 3284D7FF1FBB34B70090AA80 /* RoomKeyRequestViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3284D7FE1FBB34B70090AA80 /* RoomKeyRequestViewController.m */; }; 32918EA91F473BDB0076CA16 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32918EA51F473BDB0076CA16 /* Localizable.strings */; }; 32918EAA1F473BDB0076CA16 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32918EA71F473BDB0076CA16 /* Vector.strings */; }; 32935CB11F6056FD006888C8 /* IntegrationManagerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32935CB01F6056FD006888C8 /* IntegrationManagerViewController.m */; }; - 32935CB41F628BCE006888C8 /* IntegrationManager.js in Resources */ = {isa = PBXBuildFile; fileRef = 32935CB31F628BCE006888C8 /* IntegrationManager.js */; }; 329DCE191F988F8100468420 /* riot_icon_callkit.png in Resources */ = {isa = PBXBuildFile; fileRef = 329DCE161F988F8100468420 /* riot_icon_callkit.png */; }; 329DCE1A1F988F8100468420 /* riot_icon_callkit@2.png in Resources */ = {isa = PBXBuildFile; fileRef = 329DCE171F988F8100468420 /* riot_icon_callkit@2.png */; }; 329DCE1B1F988F8100468420 /* riot_icon_callkit@3.png in Resources */ = {isa = PBXBuildFile; fileRef = 329DCE181F988F8100468420 /* riot_icon_callkit@3.png */; }; @@ -733,13 +733,13 @@ 32795C08203DA4C3002420E2 /* DisabledRoomInputToolbarView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DisabledRoomInputToolbarView.h; sourceTree = ""; }; 32795C09203DA4C3002420E2 /* DisabledRoomInputToolbarView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DisabledRoomInputToolbarView.m; sourceTree = ""; }; 32795C0A203DA4C4002420E2 /* DisabledRoomInputToolbarView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DisabledRoomInputToolbarView.xib; sourceTree = ""; }; + 3284A35020A07C210044F922 /* postMessageAPI.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = postMessageAPI.js; sourceTree = ""; }; 3284D7FD1FBB34B70090AA80 /* RoomKeyRequestViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomKeyRequestViewController.h; sourceTree = ""; }; 3284D7FE1FBB34B70090AA80 /* RoomKeyRequestViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomKeyRequestViewController.m; sourceTree = ""; }; 32918EA61F473BDB0076CA16 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Localizable.strings; sourceTree = ""; }; 32918EA81F473BDB0076CA16 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Vector.strings; sourceTree = ""; }; 32935CAF1F6056FD006888C8 /* IntegrationManagerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IntegrationManagerViewController.h; sourceTree = ""; }; 32935CB01F6056FD006888C8 /* IntegrationManagerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IntegrationManagerViewController.m; sourceTree = ""; }; - 32935CB31F628BCE006888C8 /* IntegrationManager.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = IntegrationManager.js; sourceTree = ""; }; 329DCE161F988F8100468420 /* riot_icon_callkit.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = riot_icon_callkit.png; sourceTree = ""; }; 329DCE171F988F8100468420 /* riot_icon_callkit@2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "riot_icon_callkit@2.png"; sourceTree = ""; }; 329DCE181F988F8100468420 /* riot_icon_callkit@3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "riot_icon_callkit@3.png"; sourceTree = ""; }; @@ -1627,7 +1627,7 @@ 32935CB21F628B98006888C8 /* js */ = { isa = PBXGroup; children = ( - 32935CB31F628BCE006888C8 /* IntegrationManager.js */, + 3284A35020A07C210044F922 /* postMessageAPI.js */, ); path = js; sourceTree = ""; @@ -2737,7 +2737,6 @@ 24CBEC4A1F0EAD310093EABB /* Sources */, 24CBEC4B1F0EAD310093EABB /* Frameworks */, 24CBEC4C1F0EAD310093EABB /* Resources */, - 8EA19F5011654D3BD5EDAC33 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -2756,7 +2755,6 @@ 92726A3F1F58737A004AD26F /* Sources */, 92726A401F58737A004AD26F /* Frameworks */, 92726A411F58737A004AD26F /* Resources */, - 807A0ABF153A23C2FC22F977 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -2777,7 +2775,6 @@ F094A9A01B78D8F000B1FBBF /* Resources */, 24CBEC5D1F0EAD310093EABB /* Embed App Extensions */, 7FFD40AA75DB32D83350D225 /* [CP] Embed Pods Frameworks */, - 68D6013FA64A4507DC9DB95B /* [CP] Copy Pods Resources */, 3233F7481F3497E2006ACA81 /* Embed Frameworks */, ); buildRules = ( @@ -3273,7 +3270,6 @@ F083BD961E7009ED00A9B29C /* leave@2x.png in Resources */, F0E05A461EA0F9EB004B83FB /* tab_rooms@2x.png in Resources */, 32AE61F41F0D2183007255F4 /* Vector.strings in Resources */, - 32935CB41F628BCE006888C8 /* IntegrationManager.js in Resources */, F083BD851E7009ED00A9B29C /* favouriteOff@3x.png in Resources */, F083BD541E7009ED00A9B29C /* camera_switch@2x.png in Resources */, F07CB3992029B20100C29C20 /* GroupTableViewCellWithSwitch.xib in Resources */, @@ -3295,6 +3291,7 @@ 327382C41F276AED00356143 /* Vector.strings in Resources */, 32BB89EF204D86DA002F3AEC /* Localizable.strings in Resources */, F083BDE51E7009ED00A9B29C /* voice_call_icon@3x.png in Resources */, + 3284A35120A07C210044F922 /* postMessageAPI.js in Resources */, 322806A11F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.xib in Resources */, F083BE201E7009ED00A9B29C /* RoomParticipantsViewController.xib in Resources */, F083BE471E7009ED00A9B29C /* RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.xib in Resources */, @@ -3373,86 +3370,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 68D6013FA64A4507DC9DB95B /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot-resources.sh", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKAccountDetailsViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKAttachmentsViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKAuthenticationViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKCallViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKContactDetailsViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKContactListViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKCountryPickerViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKGroupListViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKLanguagePickerViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKRecentListViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKRoomMemberDetailsViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKRoomMemberListViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKRoomSettingsViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKRoomViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Controllers/MXKSearchViewController.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/Account/MXKAccountTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/Authentication/MXKAuthInputsEmailCodeBasedView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/Authentication/MXKAuthInputsPasswordBasedView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/Contact/MXKContactTableCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/DeviceView/MXKDeviceView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/EncryptionInfoView/MXKEncryptionInfoView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/Group/MXKGroupTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKCollectionViewCell/MXKMediaCollectionViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKEventDetailsView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKPieChartHUD.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKRoomCreationView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithButton.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelAndButton.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelAndImageView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelAndMXKImageView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelAndSlider.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelAndSubLabel.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelAndSwitch.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelAndTextField.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithLabelTextFieldAndButton.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithPicker.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithSearchBar.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithTextFieldAndButton.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/MXKTableViewCell/MXKTableViewCellWithTextView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/PushRule/MXKPushRuleCreationTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/PushRule/MXKPushRuleTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/ReadReceipts/MXKReadReceiptTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomEmptyBubbleTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentBubbleCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingAttachmentWithoutSenderInfoBubbleCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgBubbleCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomIncomingTextMsgWithoutSenderInfoBubbleCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomIOSOutgoingBubbleTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentBubbleCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingAttachmentWithoutSenderInfoBubbleCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgBubbleCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomBubbleList/MXKRoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarViewWithHPGrowingText.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomInputToolbar/MXKRoomInputToolbarViewWithSimpleTextView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomList/MXKInterleavedRecentTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomList/MXKPublicRoomTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomList/MXKRecentTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomMemberList/MXKRoomMemberTableViewCell.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomTitle/MXKRoomTitleView.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/RoomTitle/MXKRoomTitleViewWithTopic.xib", - "${PODS_ROOT}/MatrixKit/MatrixKit/Views/Search/MXKSearchTableViewCell.xib", - $PODS_CONFIGURATION_BUILD_DIR/MatrixKit/MatrixKit.bundle, - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 6AA0024D4D5FAE30C2E1F311 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -3517,42 +3434,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RiotPods-Riot/Pods-RiotPods-Riot-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 807A0ABF153A23C2FC22F977 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-RiotPods-SiriIntents/Pods-RiotPods-SiriIntents-resources.sh", - "${PODS_ROOT}/MatrixSDK/MatrixSDK/Data/Store/MXCoreDataStore/MXCoreDataStore.xcdatamodeld", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RiotPods-SiriIntents/Pods-RiotPods-SiriIntents-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - 8EA19F5011654D3BD5EDAC33 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-RiotPods-RiotShareExtension/Pods-RiotPods-RiotShareExtension-resources.sh", - "${PODS_ROOT}/MatrixSDK/MatrixSDK/Data/Store/MXCoreDataStore/MXCoreDataStore.xcdatamodeld", - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-RiotPods-RiotShareExtension/Pods-RiotPods-RiotShareExtension-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index 69ae2addd..c13524eac 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -74,6 +74,9 @@ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification; // Current selected room id. nil if no room is presently visible. @property (strong, nonatomic) NSString *visibleRoomId; +// New message sound id. +@property (nonatomic, readonly) SystemSoundID messageSound; + + (AppDelegate*)theDelegate; #pragma mark - Application layout handling diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 2d06f62cb..f0bfa3f95 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -46,6 +46,8 @@ #include +#import "WebViewViewController.h" + @import PiwikTracker; // Calls @@ -210,6 +212,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN @property (strong, nonatomic) UIAlertController *logoutConfirmation; +@property (weak, nonatomic) UIAlertController *gdprConsentNotGivenAlertController; +@property (weak, nonatomic) UIViewController *gdprConsentViewController; + @property (nonatomic, nullable, copy) void (^registrationForRemoteNotificationsCompletion)(NSError *); @@ -343,6 +348,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions { + // Create message sound + NSURL *messageSoundURL = [[NSBundle mainBundle] URLForResource:@"message" withExtension:@"mp3"]; + AudioServicesCreateSystemSoundID((__bridge CFURLRef)messageSoundURL, &_messageSound); + NSLog(@"[AppDelegate] willFinishLaunchingWithOptions: Done"); return YES; @@ -592,6 +601,9 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + // Register to GDPR consent not given notification + [self registerUserConsentNotGivenNotification]; + // Start monitoring reachability [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { @@ -1464,7 +1476,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN { NSString *soundName = action.parameters[@"value"]; if ([soundName isEqualToString:@"default"]) - soundName = UILocalNotificationDefaultSoundName; + soundName = @"message.mp3"; eventNotification.soundName = soundName; } @@ -2196,7 +2208,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN sdkOptions.backgroundModeHandler = [[MXUIKitBackgroundModeHandler alloc] init]; // Get modular widget events in rooms histories - [[MXKAppSettings standardAppSettings] addSupportedEventTypes:@[kWidgetEventTypeString]]; + [[MXKAppSettings standardAppSettings] addSupportedEventTypes:@[kWidgetMatrixEventTypeString, kWidgetModularEventTypeString]]; // Disable long press on event in bubble cells [MXKRoomBubbleTableViewCell disableLongPressGestureOnEvent:YES]; @@ -2255,9 +2267,10 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Each room member will be considered as a potential contact. [MXKContactManager sharedManager].contactManagerMXRoomSource = MXKContactManagerMXRoomSourceAll; - // Send read receipts for modular widgets events too + // Send read receipts for widgets events too NSMutableArray *acknowledgableEventTypes = [NSMutableArray arrayWithArray:mxSession.acknowledgableEventTypes]; - [acknowledgableEventTypes addObject:kWidgetEventTypeString]; + [acknowledgableEventTypes addObject:kWidgetMatrixEventTypeString]; + [acknowledgableEventTypes addObject:kWidgetModularEventTypeString]; mxSession.acknowledgableEventTypes = acknowledgableEventTypes; } else if (mxSession.state == MXSessionStateStoreDataReady) @@ -3056,8 +3069,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN { if ([[ruleAction.parameters valueForKey:@"set_tweak"] isEqualToString:@"sound"]) { - // Play system sound (VoicemailReceived) - AudioServicesPlaySystemSound (1002); + // Play message sound + AudioServicesPlaySystemSound(_messageSound); } } } @@ -3974,4 +3987,79 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } } +#pragma mark - GDPR consent + +// Observe user GDPR consent not given +- (void)registerUserConsentNotGivenNotification +{ + [NSNotificationCenter.defaultCenter addObserverForName:kMXHTTPClientUserConsentNotGivenErrorNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *notification) + { + NSString *consentURI = notification.userInfo[kMXHTTPClientUserConsentNotGivenErrorNotificationConsentURIKey]; + if (consentURI + && self.gdprConsentNotGivenAlertController == nil + && self.gdprConsentViewController == nil) + { + UIViewController *presentingViewController = self.window.rootViewController.presentedViewController ?: self.window.rootViewController; + + __weak typeof(self) weakSelf = self; + + MXSession *mainSession = self.mxSessions.firstObject; + NSString *homeServerName = mainSession.matrixRestClient.credentials.homeServerName; + + NSString *alertMessage = [NSString stringWithFormat:NSLocalizedStringFromTable(@"gdpr_consent_not_given_alert_message", @"Vector", nil), homeServerName]; + + UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_term_conditions", @"Vector", nil) + message:alertMessage + preferredStyle:UIAlertControllerStyleAlert]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"gdpr_consent_not_given_alert_review_now_action", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + typeof(weakSelf) strongSelf = weakSelf; + + if (strongSelf) + { + [strongSelf presentGDPRConsentFromViewController:presentingViewController consentURI:consentURI]; + } + }]]; + + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"later", @"Vector", nil) + style:UIAlertActionStyleCancel + handler:nil]]; + + [presentingViewController presentViewController:alert animated:YES completion:nil]; + + self.gdprConsentNotGivenAlertController = alert; + } + }]; +} + +- (void)presentGDPRConsentFromViewController:(UIViewController*)viewController consentURI:(NSString*)consentURI +{ + WebViewViewController *webViewViewController = [[WebViewViewController alloc] initWithURL:consentURI]; + webViewViewController.title = NSLocalizedStringFromTable(@"settings_term_conditions", @"Vector", nil); + + UIBarButtonItem *closeBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:[NSBundle mxk_localizedStringForKey:@"close"] + style:UIBarButtonItemStylePlain + target:self + action:@selector(dismissGDPRConsent)]; + + webViewViewController.navigationItem.rightBarButtonItem = closeBarButtonItem; + + UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:webViewViewController]; + + [viewController presentViewController:navigationController animated:YES completion:nil]; + + self.gdprConsentViewController = navigationController; +} + +- (void)dismissGDPRConsent +{ + [self.gdprConsentViewController dismissViewControllerAnimated:YES completion:nil]; +} + @end diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index d17a8f452..949c079a6 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -593,3 +593,6 @@ "e2e_room_key_request_share_without_verifying" = "Share without verifying"; "e2e_room_key_request_ignore_request" = "Ignore request"; +// GDPR +"gdpr_consent_not_given_alert_message" = "To continue using the %@ homeserver you must review and agree to the terms and conditions."; +"gdpr_consent_not_given_alert_review_now_action" = "Review now"; diff --git a/Riot/Assets/js/IntegrationManager.js b/Riot/Assets/js/postMessageAPI.js similarity index 77% rename from Riot/Assets/js/IntegrationManager.js rename to Riot/Assets/js/postMessageAPI.js index 5dc414677..2b145debe 100644 --- a/Riot/Assets/js/IntegrationManager.js +++ b/Riot/Assets/js/postMessageAPI.js @@ -28,7 +28,7 @@ window.riotIOS.sendObjectMessageToObjC = function(parameters) { window.riotIOS.events = {}; -// Listen to messages posted by Modular +// Listen to messages posted by the widget window.riotIOS.onMessage = function(event) { // Do not SPAM ObjC with event already managed @@ -40,6 +40,19 @@ window.riotIOS.onMessage = function(event) { event.origin = event.originalEvent.origin; } + // Use an internal "_id" field for matching onMessage events and requests + // _id was originally used by the Modular API. Keep it + if (!event.data._id) { + // The Matrix Widget API v2 spec says: + // "The requestId field should be unique and included in all requests" + event.data._id = event.data.requestId; + } + + // Make sure to have one id + if (!event.data._id) { + event.data._id = Date.now() + "-" + Math.random().toString(36); + } + // Keep this event for future usage riotIOS.events[event.data._id] = event; @@ -50,7 +63,7 @@ window.riotIOS.onMessage = function(event) { window.addEventListener('message', riotIOS.onMessage, false); -// ObjC -> Modular JS bridge +// ObjC -> Widget JS bridge window.riotIOS.sendResponse = function(eventId, res) { // Retrieve the correspong JS event diff --git a/Riot/Info.plist b/Riot/Info.plist index ac347a01c..d00816a35 100644 --- a/Riot/Info.plist +++ b/Riot/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.6.15 + 0.6.16 CFBundleSignature ???? CFBundleVersion - 0.6.15 + 0.6.16 ITSAppUsesNonExemptEncryption ITSEncryptionExportComplianceCode diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index a4a5c6f10..ef1732e49 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -27,11 +27,6 @@ The calendar used to retrieve the today date. */ NSCalendar *calendar; - - /** - The local time zone - */ - NSTimeZone *localTimeZone; } @end @@ -39,10 +34,10 @@ - (NSAttributedString *)attributedStringFromEvent:(MXEvent *)event withRoomState:(MXRoomState *)roomState error:(MXKEventFormatterError *)error { - // Build strings for modular widget events - // TODO: At the moment, we support only jitsi widgets + // Build strings for widget events if (event.eventType == MXEventTypeCustom - && [event.type isEqualToString:kWidgetEventTypeString]) + && ([event.type isEqualToString:kWidgetMatrixEventTypeString] + || [event.type isEqualToString:kWidgetModularEventTypeString])) { NSString *displayText; @@ -71,7 +66,11 @@ // This is a closed widget // Check if it corresponds to a jitsi widget by looking at other state events for // this jitsi widget (widget id = event.stateKey). - for (MXEvent *widgetStateEvent in [roomState stateEventsWithType:kWidgetEventTypeString]) + // Get all widgets state events in the room + NSMutableArray *widgetStateEvents = [NSMutableArray arrayWithArray:[roomState stateEventsWithType:kWidgetMatrixEventTypeString]]; + [widgetStateEvents addObjectsFromArray:[roomState stateEventsWithType:kWidgetModularEventTypeString]]; + + for (MXEvent *widgetStateEvent in widgetStateEvents) { if ([widgetStateEvent.stateKey isEqualToString:widget.widgetId]) { @@ -140,10 +139,6 @@ if (self) { calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian]; - // Note: NSDate object always shows time according to GMT, so the calendar should be in GMT too. - calendar.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; - - localTimeZone = [NSTimeZone localTimeZone]; // Use the secondary bg color to set the background color in the default CSS. NSUInteger bgColor = [MXKTools rgbValueWithColor:kRiotSecondaryBgColor]; @@ -453,12 +448,9 @@ } // Retrieve today date at midnight - NSDateComponents *components = [calendar components:(NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay) fromDate:[NSDate date]]; - NSDate *today = [calendar dateFromComponents:components]; + NSDate *today = [calendar startOfDayForDate:[NSDate date]]; - NSTimeInterval localZoneOffset = [localTimeZone secondsFromGMT]; - - NSTimeInterval interval = -[date timeIntervalSinceDate:today] - localZoneOffset; + NSTimeInterval interval = -[date timeIntervalSinceDate:today]; if (interval > 60*60*24*364) { diff --git a/Riot/Utils/Widgets/Widget.m b/Riot/Utils/Widgets/Widget.m index 6ccddc56d..0ac7714af 100644 --- a/Riot/Utils/Widgets/Widget.m +++ b/Riot/Utils/Widgets/Widget.m @@ -22,9 +22,12 @@ - (instancetype)initWithWidgetEvent:(MXEvent *)widgetEvent inMatrixSession:(MXSession*)mxSession { - if (![widgetEvent.type isEqualToString:kWidgetEventTypeString]) + // TODO - Room widgets need to be moved to 'm.widget' state events + // https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing + if (![widgetEvent.type isEqualToString:kWidgetMatrixEventTypeString] + && ![widgetEvent.type isEqualToString:kWidgetModularEventTypeString]) { - // The Widget class works only with modular, aka "im.vector.modular.widgets", widgets + // The Widget class works only with modular, aka "m.widget" or "im.vector.modular.widgets", widgets return nil; } @@ -67,9 +70,6 @@ widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_display_name" withString:displayName]; widgetUrl = [widgetUrl stringByReplacingOccurrencesOfString:@"$matrix_avatar_url" withString:avatarUrl]; - // And add the user scalar token - widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"&scalar_token=%@", scalarToken]]; - // Integrate widget data into widget url for (NSString *key in _data) { @@ -97,6 +97,14 @@ } } + // Add the user scalar token + widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"%@scalar_token=%@", + [widgetUrl containsString:@"?"] ? @"&" : @"?", + scalarToken]]; + + // Add the widget id + widgetUrl = [widgetUrl stringByAppendingString:[NSString stringWithFormat:@"&widgetId=%@", _widgetId]]; + success(widgetUrl); } diff --git a/Riot/Utils/Widgets/WidgetManager.h b/Riot/Utils/Widgets/WidgetManager.h index 8c2e5d4c5..655a88aba 100644 --- a/Riot/Utils/Widgets/WidgetManager.h +++ b/Riot/Utils/Widgets/WidgetManager.h @@ -21,9 +21,15 @@ #import "Widget.h" /** - The type of matrix event used for modular widgets. + The type of matrix event used for matrix widgets. */ -FOUNDATION_EXPORT NSString *const kWidgetEventTypeString; +FOUNDATION_EXPORT NSString *const kWidgetMatrixEventTypeString; + +/** + The type of matrix event used for modular widgets. + TODO: It should be replaced by kWidgetMatrixEventTypeString. + */ +FOUNDATION_EXPORT NSString *const kWidgetModularEventTypeString; /** Known types widgets. @@ -87,6 +93,14 @@ WidgetManagerErrorCode; */ - (NSArray *)widgetsNotOfTypes:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room; +/** + List all widgets of an account. + + @param mxSession the session of the user account. + @return a list of widgets. + */ +- (NSArray *)userWidgets:(MXSession*)mxSession; + /** Add a modular widget to a room. diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index 3adf7db81..8ea994634 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -20,7 +20,8 @@ #pragma mark - Contants -NSString *const kWidgetEventTypeString = @"im.vector.modular.widgets"; +NSString *const kWidgetMatrixEventTypeString = @"m.widget"; +NSString *const kWidgetModularEventTypeString = @"im.vector.modular.widgets"; NSString *const kWidgetTypeJitsi = @"jitsi"; NSString *const kWidgetManagerDidUpdateWidgetNotification = @"kWidgetManagerDidUpdateWidgetNotification"; @@ -102,10 +103,11 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // Widget id -> widget NSMutableDictionary *widgets = [NSMutableDictionary dictionary]; - // Get all im.vector.modular.widgets state events in the room - NSMutableArray *widgetEvents = [NSMutableArray arrayWithArray:[room.state stateEventsWithType:kWidgetEventTypeString]]; + // Get all widgets state events in the room + NSMutableArray *widgetEvents = [NSMutableArray arrayWithArray:[room.state stateEventsWithType:kWidgetMatrixEventTypeString]]; + [widgetEvents addObjectsFromArray:[room.state stateEventsWithType:kWidgetModularEventTypeString]]; - // There can be several im.vector.modular.widgets state events for a same widget but + // There can be several widgets state events for a same widget but // only the last one must be considered. // Order widgetEvents with the last event first @@ -124,7 +126,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; return result; }]; - // Create each widget from its lastest im.vector.modular.widgets state event + // Create each widget from its lastest widgets state event for (MXEvent *widgetEvent in widgetEvents) { // Filter widget types if required @@ -170,6 +172,39 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; return activeWidgets; } +- (NSArray *)userWidgets:(MXSession*)mxSession +{ + // Disable user widgets (sticker picker) for now + return nil; + + // Get all widgets in the user account data + NSMutableArray *userWidgets = [NSMutableArray array]; + for (NSDictionary *widgetEventContent in [mxSession.accountData accountDataForEventType:@"m.widgets"].allValues) + { + // Patch: Modular uses a malformed key: "stateKey" instead of "state_key" + // TODO: To remove once fixed server side + NSDictionary *widgetEventContentFixed = widgetEventContent; + if (!widgetEventContent[@"state_key"] && widgetEventContent[@"stateKey"]) + { + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:widgetEventContent]; + dict[@"state_key"] = widgetEventContent[@"stateKey"]; + widgetEventContentFixed = dict; + } + + MXEvent *widgetEvent = [MXEvent modelFromJSON:widgetEventContentFixed]; + if (widgetEvent) + { + Widget *widget = [[Widget alloc] initWithWidgetEvent:widgetEvent inMatrixSession:mxSession]; + if (widget) + { + [userWidgets addObject:widget]; + } + } + } + + return userWidgets; +} + - (MXHTTPOperation *)createWidget:(NSString*)widgetId withContent:(NSDictionary*)widgetContent inRoom:(MXRoom*)room @@ -192,7 +227,8 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // Send a state event with the widget data // TODO: This API will be shortly replaced by a pure modular API - return [room sendStateEventOfType:kWidgetEventTypeString + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + return [room sendStateEventOfType:kWidgetModularEventTypeString content:widgetContent stateKey:widgetId success:nil failure:failure]; @@ -247,7 +283,8 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; // Send a state event with an empty content to disable the widget // TODO: This API will be shortly replaced by a pure modular API - return [room sendStateEventOfType:kWidgetEventTypeString + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + return [room sendStateEventOfType:kWidgetModularEventTypeString content:@{} stateKey:widgetId success:^(NSString *eventId) @@ -292,7 +329,7 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; NSString *hash = [NSString stringWithFormat:@"%p", mxSession]; - id listener = [mxSession listenToEventsOfTypes:@[kWidgetEventTypeString] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { + id listener = [mxSession listenToEventsOfTypes:@[kWidgetMatrixEventTypeString, kWidgetModularEventTypeString] onEvent:^(MXEvent *event, MXTimelineDirection direction, id customObject) { typeof(self) self = weakSelf; diff --git a/Riot/ViewController/MediaPickerViewController.m b/Riot/ViewController/MediaPickerViewController.m index 89ff27ec2..b81f5b34f 100644 --- a/Riot/ViewController/MediaPickerViewController.m +++ b/Riot/ViewController/MediaPickerViewController.m @@ -1188,7 +1188,9 @@ static void *RecordingContext = &RecordingContext; } } - self.cameraSwitchButton.hidden = (!frontCamera || !backCamera); + dispatch_async(dispatch_get_main_queue(), ^{ + self.cameraSwitchButton.hidden = (!frontCamera || !backCamera); + }); if (currentCameraInput) { diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 8c1306325..71472f311 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -1257,9 +1257,10 @@ // If the setting is disabled, do not show the icon self.navigationItem.rightBarButtonItems = @[self.navigationItem.rightBarButtonItem]; } - else if (self.widgetsCount) + else if ([self widgetsCount:NO]) { // Show there are widgets by changing the "apps" icon color + // Show it in red only for room widgets, not user's widgets // TODO: Design must be reviewed UIImage *icon = self.navigationItem.rightBarButtonItems[1].image; icon = [MXKTools paintImage:icon withColor:kRiotColorPinkRed]; @@ -3063,7 +3064,7 @@ // Matrix Apps button else if (self.navigationItem.rightBarButtonItems.count == 2 && sender == self.navigationItem.rightBarButtonItems[1]) { - if (self.widgetsCount) + if ([self widgetsCount:YES]) { WidgetPickerViewController *widgetPicker = [[WidgetPickerViewController alloc] initForMXSession:self.roomDataSource.mxSession inRoom:self.roomDataSource.roomId]; @@ -3643,10 +3644,16 @@ [[AppDelegate theDelegate] showErrorAsAlert:error]; } -- (NSUInteger)widgetsCount +- (NSUInteger)widgetsCount:(BOOL)includeUserWidgets { - return [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] - inRoom:self.roomDataSource.room].count; + NSUInteger widgetsCount = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] + inRoom:self.roomDataSource.room].count; + if (includeUserWidgets) + { + widgetsCount = [[WidgetManager sharedManager] userWidgets:self.roomDataSource.room.mxSession].count; + } + + return widgetsCount; } #pragma mark - Unreachable Network Handling diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.h b/Riot/ViewController/Widgets/IntegrationManagerViewController.h index 213147720..3087c57f1 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.h +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.h @@ -14,9 +14,7 @@ limitations under the License. */ -#import "WebViewViewController.h" - -#import +#import "WidgetViewController.h" FOUNDATION_EXPORT NSString *const kIntegrationManagerMainScreen; FOUNDATION_EXPORT NSString *const kIntegrationManagerAddIntegrationScreen; @@ -24,8 +22,10 @@ FOUNDATION_EXPORT NSString *const kIntegrationManagerAddIntegrationScreen; /** `IntegrationManagerViewController` displays the Modular integration manager webapp into a webview. + + It reuses the postMessage API pipe defined in `WidgetViewController`. */ -@interface IntegrationManagerViewController : WebViewViewController +@interface IntegrationManagerViewController : WidgetViewController /** Initialise with params for the Modular interface webapp. diff --git a/Riot/ViewController/Widgets/IntegrationManagerViewController.m b/Riot/ViewController/Widgets/IntegrationManagerViewController.m index 742c522e8..86e118ecb 100644 --- a/Riot/ViewController/Widgets/IntegrationManagerViewController.m +++ b/Riot/ViewController/Widgets/IntegrationManagerViewController.m @@ -17,15 +17,10 @@ #import "IntegrationManagerViewController.h" #import "WidgetManager.h" -#import "AppDelegate.h" - -#import NSString *const kIntegrationManagerMainScreen = nil; NSString *const kIntegrationManagerAddIntegrationScreen = @"add_integ"; -NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', %@);"; - @interface IntegrationManagerViewController () { @@ -63,19 +58,6 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', operation = nil; } -- (void)viewDidLoad -{ - [super viewDidLoad]; - - webView.scalesPageToFit = NO; - webView.scrollView.bounces = NO; - - // Disable opacity so that the webview background uses the current interface theme - webView.opaque = NO; - - webView.delegate = self; -} - - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; @@ -86,7 +68,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [self startActivityIndicator]; - // Make sure we a scalar token + // Make sure we have a scalar token operation = [[WidgetManager sharedManager] getScalarTokenForMXSession:mxSession success:^(NSString *theScalarToken) { typeof(self) self = weakSelf; @@ -147,143 +129,15 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', return url; } -- (void)enableDebug +#pragma mark - Modular postMessage API implementation + +- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData { - // Setup console.log() -> NSLog() route - JSContext *ctx = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; - ctx[@"console"][@"log"] = ^(JSValue * msg) { - NSLog(@"-- JavaScript: %@", msg); - }; - - // Redirect all console.* logging methods to console.log - [webView stringByEvaluatingJavaScriptFromString:@"console.debug = console.log; console.info = console.log; console.warn = console.log; console.error = console.log;"]; -} - -- (void)showErrorAsAlert:(NSError*)error -{ - NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; - NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; - if (!title) - { - if (msg) - { - title = msg; - msg = nil; - } - else - { - title = [NSBundle mxk_localizedStringForKey:@"error"]; - } - } - - __weak __typeof__(self) weakSelf = self; - - UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; - [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] - style:UIAlertActionStyleDefault - handler:^(UIAlertAction * action) { - - typeof(self) self = weakSelf; - - if (self) - { - // Leave this Intergrations Manager VC - [self withdrawViewControllerAnimated:YES completion:nil]; - } - - }]]; - - [self presentViewController:alert animated:YES completion:nil]; -} - -#pragma mark - UIWebViewDelegate - --(void)webViewDidFinishLoad:(UIWebView *)theWebView -{ - [self enableDebug]; - - // Setup js code - NSString *path = [[NSBundle mainBundle] pathForResource:@"IntegrationManager" ofType:@"js"]; - NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; - [webView stringByEvaluatingJavaScriptFromString:js]; - - [self stopActivityIndicator]; - - // Check connectivity - if ([AppDelegate theDelegate].isOffline) - { - // The web page may be in the cache, so its loading will be successful - // but we cannot go further, it often leads to a blank screen. - // So, display an error so that the user can escape. - NSError *error = [NSError errorWithDomain:NSURLErrorDomain - code:NSURLErrorNotConnectedToInternet - userInfo:@{ - NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"network_offline_prompt", @"Vector", nil) - }]; - [self showErrorAsAlert:error]; - } -} - -- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType -{ - NSString *urlString = [[request URL] absoluteString]; - - if ([urlString hasPrefix:@"js:"]) - { - // Listen only to scheme of the JS-UIWebView bridge - NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; - NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; - - NSError *error; - NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers - error:&error]; - if (!error) - { - [self onMessage:parameters]; - } - - return NO; - } - - if (navigationType == UIWebViewNavigationTypeLinkClicked ) - { - // Open links outside the app - [[UIApplication sharedApplication] openURL:[request URL]]; - return NO; - } - - return YES; -} - -- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error -{ - // Filter out the users's scalar token - NSString *errorDescription = error.description; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"scalar_token=\\w*" - options:NSRegularExpressionCaseInsensitive error:nil]; - errorDescription = [regex stringByReplacingMatchesInString:errorDescription - options:0 - range:NSMakeRange(0, errorDescription.length) - withTemplate:@"scalar_token=..."]; - - NSLog(@"[IntegrationManagerVC] didFailLoadWithError: %@", errorDescription); - - [self stopActivityIndicator]; - [self showErrorAsAlert:error]; -} - -#pragma mark - Modular postMessage API - -- (void)onMessage:(NSDictionary*)JSData -{ - NSDictionary *eventData; - MXJSONModelSetDictionary(eventData, JSData[@"event.data"]); - NSString *roomIdInEvent, *userId, *action; - MXJSONModelSetString(roomIdInEvent, eventData[@"room_id"]); - MXJSONModelSetString(userId, eventData[@"user_id"]); - MXJSONModelSetString(action, eventData[@"action"]); + MXJSONModelSetString(roomIdInEvent, requestData[@"room_id"]); + MXJSONModelSetString(userId, requestData[@"user_id"]); + MXJSONModelSetString(action, requestData[@"action"]); if ([action isEqualToString:@"close_scalar"]) { @@ -293,13 +147,13 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', if (!roomIdInEvent) { - [self sendLocalisedError:@"widget_integration_missing_room_id" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_missing_room_id" toRequest:requestId]; return; } if (![roomIdInEvent isEqualToString:roomId]) { - [self sendError:[NSString stringWithFormat:NSLocalizedStringFromTable(@"widget_integration_room_not_visible", @"Vector", nil), roomIdInEvent] toEvent:eventData]; + [self sendError:[NSString stringWithFormat:NSLocalizedStringFromTable(@"widget_integration_room_not_visible", @"Vector", nil), roomIdInEvent] toRequest:requestId]; return; } @@ -307,149 +161,86 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', // These APIs don't require userId if ([@"join_rules_state" isEqualToString:action]) { - [self getJoinRules:eventData]; + [self getJoinRules:requestId data:requestData]; return; } else if ([@"set_plumbing_state" isEqualToString:action]) { - [self setPlumbingState:eventData]; + [self setPlumbingState:requestId data:requestData]; return; } else if ([@"get_membership_count" isEqualToString:action]) { - [self getMembershipCount:eventData]; + [self getMembershipCount:requestId data:requestData]; return; } else if ([@"set_widget" isEqualToString:action]) { - [self setWidget:eventData]; + [self setWidget:requestId data:requestData]; return; } else if ([@"get_widgets" isEqualToString:action]) { - [self getWidgets:eventData]; + [self getWidgets:requestId data:requestData]; return; } else if ([@"can_send_event" isEqualToString:action]) { - [self canSendEvent:eventData]; + [self canSendEvent:requestId data:requestData]; return; } if (!userId) { - [self sendLocalisedError:@"widget_integration_missing_user_id" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_missing_user_id" toRequest:requestId]; return; } if ([@"membership_state" isEqualToString:action]) { - [self getMembershipState:userId eventData:eventData]; + [self getMembershipState:userId request:requestId data:requestData]; } else if ([@"invite" isEqualToString:action]) { - [self inviteUser:userId eventData:eventData]; + [self inviteUser:userId request:requestId data:requestData]; } else if ([@"bot_options" isEqualToString:action]) { - [self getBotOptions:userId eventData:eventData]; + [self getBotOptions:userId request:requestId data:requestData]; } else if ([@"set_bot_options" isEqualToString:action]) { - [self setBotOptions:userId eventData:eventData]; + [self setBotOptions:userId request:requestId data:requestData]; } else if ([@"set_bot_power" isEqualToString:action]) { - [self setBotPower:userId eventData:eventData]; + [self setBotPower:userId request:requestId data:requestData]; } else { - NSLog(@"[IntegrationManagerViewControllerVC] Unhandled postMessage event with action %@: %@", action, JSData); + NSLog(@"[IntegrationManagerViewControllerVC] Unhandled postMessage event with action %@: %@", action, requestData); } } -- (void)sendBoolResponse:(BOOL)response toEvent:(NSDictionary*)eventData -{ - // Convert BOOL to "true" or "false" - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], - response ? @"true" : @"false"]; +#pragma mark - Private methods - [webView stringByEvaluatingJavaScriptFromString:js]; -} - -- (void)sendIntegerResponse:(NSUInteger)response toEvent:(NSDictionary*)eventData -{ - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], - @(response)]; - - [webView stringByEvaluatingJavaScriptFromString:js]; -} - -- (void)sendNSObjectResponse:(NSObject*)response toEvent:(NSDictionary*)eventData -{ - NSString *jsString; - - if (response) - { - // Convert response into a JS object through a JSON string - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response - options:0 - error:0]; - NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; - - jsString = [NSString stringWithFormat:@"JSON.parse('%@')", jsonString]; - } - else - { - jsString = @"null"; - } - - NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToModular, - eventData[@"_id"], - jsString]; - - [webView stringByEvaluatingJavaScriptFromString:js]; -} - -- (void)sendError:(NSString*)message toEvent:(NSDictionary*)eventData -{ - NSLog(@"[IntegrationManagerVC] sendError: Action %@ failed with message: %@", eventData[@"action"], message); - - // TODO: JS has an additional optional parameter: nestedError - [self sendNSObjectResponse:@{ - @"error": @{ - @"message": message - } - } - toEvent:eventData]; -} - -- (void)sendLocalisedError:(NSString*)errorKey toEvent:(NSDictionary*)eventData -{ - [self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toEvent:eventData]; -} - -#pragma mark - Modular postMessage Implementation - -- (MXRoom *)roomCheckWithEvent:(NSDictionary*)eventData +- (MXRoom *)roomCheckForRequest:(NSString*)requestId data:(NSDictionary*)requestData { MXRoom *room = [mxSession roomWithRoomId:roomId]; if (!room) { - [self sendLocalisedError:@"widget_integration_room_not_recognised" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_room_not_recognised" toRequest:requestId]; } return room; } -- (void)inviteUser:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)inviteUser:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to invite %@ into room %@.", userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { @@ -459,7 +250,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } else { @@ -473,7 +264,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -481,18 +272,18 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_need_to_be_able_to_invite" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_need_to_be_able_to_invite" toRequest:requestId]; } }]; } } } -- (void)setWidget:(NSDictionary*)eventData +- (void)setWidget:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to set widget in room %@.", roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { @@ -500,15 +291,15 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', NSString *widgetName; // optional NSDictionary *widgetData ; // optional - MXJSONModelSetString(widget_id, eventData[@"widget_id"]); - MXJSONModelSetString(widgetType, eventData[@"type"]); - MXJSONModelSetString(widgetUrl, eventData[@"url"]); - MXJSONModelSetString(widgetName, eventData[@"name"]); - MXJSONModelSetDictionary(widgetData, eventData[@"data"]); + MXJSONModelSetString(widget_id, requestData[@"widget_id"]); + MXJSONModelSetString(widgetType, requestData[@"type"]); + MXJSONModelSetString(widgetUrl, requestData[@"url"]); + MXJSONModelSetString(widgetName, requestData[@"name"]); + MXJSONModelSetDictionary(widgetData, requestData[@"data"]); if (!widget_id) { - [self sendLocalisedError:@"widget_integration_unable_to_create" toEvent:eventData]; // new Error("Missing required widget fields.")); + [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; // new Error("Missing required widget fields.")); return; } @@ -517,7 +308,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', { if (!widgetType) { - [self sendLocalisedError:@"widget_integration_unable_to_create" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_unable_to_create" toRequest:requestId]; return; } @@ -536,7 +327,8 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', __weak __typeof__(self) weakSelf = self; - [room sendStateEventOfType:kWidgetEventTypeString + // TODO: Move to kWidgetMatrixEventTypeString ("m.widget") type but when? + [room sendStateEventOfType:kWidgetModularEventTypeString content:widgetEventContent stateKey:widget_id success:^(NSString *eventId) { @@ -547,7 +339,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -555,48 +347,52 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; } } -- (void)getWidgets:(NSDictionary*)eventData +- (void)getWidgets:(NSString*)requestId data:(NSDictionary*)requestData { - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; + NSMutableArray *widgetStateEvents = [NSMutableArray array]; if (room) { NSArray *widgets = [[WidgetManager sharedManager] widgetsInRoom:room]; - - NSMutableArray *widgetStateEvents = [NSMutableArray arrayWithCapacity:widgets.count]; - for (Widget *widget in widgets) { [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; } - - [self sendNSObjectResponse:widgetStateEvents toEvent:eventData]; } + + // Add user widgets (not linked to a specific room) + for (Widget *widget in [[WidgetManager sharedManager] userWidgets:mxSession]) + { + [widgetStateEvents addObject:widget.widgetEvent.JSONDictionary]; + } + + [self sendNSObjectResponse:widgetStateEvents toRequest:requestId]; } -- (void)canSendEvent:(NSDictionary*)eventData +- (void)canSendEvent:(NSString*)requestId data:(NSDictionary*)requestData { NSString *eventType; BOOL isState = NO; - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { if (room.state.membership != MXMembershipJoin) { - [self sendLocalisedError:@"widget_integration_must_be_in_room" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_must_be_in_room" toRequest:requestId]; return; } - MXJSONModelSetString(eventType, eventData[@"event_type"]); - MXJSONModelSetBoolean(isState, eventData[@"is_state"]); + MXJSONModelSetString(eventType, requestData[@"event_type"]); + MXJSONModelSetBoolean(isState, requestData[@"is_state"]); MXRoomPowerLevels *powerLevels = room.state.powerLevels; NSInteger userPowerLevel = [powerLevels powerLevelOfUserWithUserID:mxSession.myUser.userId]; @@ -614,48 +410,48 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', if (canSend) { - [self sendBoolResponse:YES toEvent:eventData]; + [self sendBoolResponse:YES toRequest:requestId]; } else { - [self sendLocalisedError:@"widget_integration_no_permission_in_room" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_no_permission_in_room" toRequest:requestId]; } } } -- (void)getMembershipState:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)getMembershipState:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] membership_state of %@ in room %@ requested.", userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { MXRoomMember *member = [room.state memberWithUserId:userId]; - [self sendNSObjectResponse:member.originalEvent.content toEvent:eventData]; + [self sendNSObjectResponse:member.originalEvent.content toRequest:requestId]; } } -- (void)getJoinRules:(NSDictionary*)eventData +- (void)getJoinRules:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] join_rules of %@ requested.", roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { MXEvent *event = [room.state stateEventsWithType:kMXEventTypeStringRoomJoinRules].lastObject; - [self sendNSObjectResponse:event.JSONDictionary toEvent:eventData]; + [self sendNSObjectResponse:event.JSONDictionary toRequest:requestId]; } } -- (void)setPlumbingState:(NSDictionary*)eventData +- (void)setPlumbingState:(NSString*)requestId data:(NSDictionary*)requestData { - NSLog(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", eventData[@"status"], roomId); + NSLog(@"[IntegrationManagerVC] Received request to set plumbing state to status %@ in room %@.", requestData[@"status"], roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSString *status; - MXJSONModelSetString(status, eventData[@"status"]); + MXJSONModelSetString(status, requestData[@"status"]); if (status) { @@ -674,7 +470,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -682,7 +478,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; } @@ -693,11 +489,11 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', } } -- (void)getBotOptions:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)getBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to get options for bot %@ in room %@", userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSString *stateKey = [NSString stringWithFormat:@"_%@", userId]; @@ -717,19 +513,19 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', } } - [self sendNSObjectResponse:botOptionsEvent.JSONDictionary toEvent:eventData]; + [self sendNSObjectResponse:botOptionsEvent.JSONDictionary toRequest:requestId]; } } -- (void)setBotOptions:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)setBotOptions:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { NSLog(@"[IntegrationManagerVC] Received request to set options for bot %@ in room %@", userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSDictionary *content; - MXJSONModelSetDictionary(content, eventData[@"content"]); + MXJSONModelSetDictionary(content, requestData[@"content"]); if (content) { @@ -748,7 +544,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -756,7 +552,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; } @@ -767,15 +563,15 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', } } -- (void)setBotPower:(NSString*)userId eventData:(NSDictionary*)eventData +- (void)setBotPower:(NSString*)userId request:(NSString*)requestId data:(NSDictionary*)requestData { - NSLog(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", eventData[@"level"], userId, roomId); + NSLog(@"[IntegrationManagerVC] Received request to set power level to %@ for bot %@ in room %@.", requestData[@"level"], userId, roomId); - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSInteger level = -1; - MXJSONModelSetInteger(level, eventData[@"level"]); + MXJSONModelSetInteger(level, requestData[@"level"]); if (level >= 0) { @@ -789,7 +585,7 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', [self sendNSObjectResponse:@{ @"success": @(YES) } - toEvent:eventData]; + toRequest:requestId]; } } failure:^(NSError *error) { @@ -797,25 +593,25 @@ NSString *const kJavascriptSendResponseToModular = @"riotIOS.sendResponse('%@', typeof(self) self = weakSelf; if (self) { - [self sendLocalisedError:@"widget_integration_failed_to_send_request" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_failed_to_send_request" toRequest:requestId]; } }]; } else { NSLog(@"[IntegrationManagerVC] setBotPower. Power level must be positive integer."); - [self sendLocalisedError:@"widget_integration_positive_power_level" toEvent:eventData]; + [self sendLocalisedError:@"widget_integration_positive_power_level" toRequest:requestId]; } } } -- (void)getMembershipCount:(NSDictionary*)eventData +- (void)getMembershipCount:(NSString*)requestId data:(NSDictionary*)requestData { - MXRoom *room = [self roomCheckWithEvent:eventData]; + MXRoom *room = [self roomCheckForRequest:requestId data:requestData]; if (room) { NSUInteger membershipCount = room.state.joinedMembers.count; - [self sendIntegerResponse:membershipCount toEvent:eventData]; + [self sendIntegerResponse:membershipCount toRequest:requestId]; } } diff --git a/Riot/ViewController/Widgets/WidgetPickerViewController.m b/Riot/ViewController/Widgets/WidgetPickerViewController.m index 59bf7c339..72b4665ee 100644 --- a/Riot/ViewController/Widgets/WidgetPickerViewController.m +++ b/Riot/ViewController/Widgets/WidgetPickerViewController.m @@ -53,12 +53,19 @@ MXRoom *room = [mxSession roomWithRoomId:roomId]; - NSArray *widgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] + NSArray *roomWidgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] inRoom:room]; + + NSArray *userWidgets = [[WidgetManager sharedManager] userWidgets:room.mxSession]; + + NSMutableArray *widgets = [NSMutableArray array]; + [widgets addObjectsFromArray:roomWidgets]; + [widgets addObjectsFromArray:userWidgets]; + // List widgets for (Widget *widget in widgets) { - alertAction = [UIAlertAction actionWithTitle:widget.name + alertAction = [UIAlertAction actionWithTitle:widget.name ? widget.name : widget.type style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { @@ -68,8 +75,12 @@ // Display the widget [widget widgetUrl:^(NSString * _Nonnull widgetUrl) { - WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget]; - [mxkViewController.navigationController pushViewController:widgetVC animated:YES]; + WidgetViewController *widgetVC = [[WidgetViewController alloc] initWithUrl:widgetUrl forWidget:widget]; + + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession]; + widgetVC.roomDataSource = [roomDataSourceManager roomDataSourceForRoom:roomId create:NO]; + + [mxkViewController.navigationController pushViewController:widgetVC animated:YES]; } failure:^(NSError * _Nonnull error) { diff --git a/Riot/ViewController/Widgets/WidgetViewController.h b/Riot/ViewController/Widgets/WidgetViewController.h index e4bdf9b75..0f9a70685 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.h +++ b/Riot/ViewController/Widgets/WidgetViewController.h @@ -17,12 +17,22 @@ #import "WebViewViewController.h" #import "WidgetManager.h" +#import "MatrixKit/MatrixKit.h" /** `WidgetViewController` displays widget within a webview. + + It also exposes a generic pipe, the postMessage API, to communicate with the + content within the webview, ie the widget (matrix app). */ @interface WidgetViewController : WebViewViewController +/** + The room data source. + Required if the widget needs to post messages. + */ +@property (nonatomic) MXKRoomDataSource *roomDataSource; + /** Init 'WidgetViewController' instance with a widget. @@ -31,4 +41,65 @@ */ - (instancetype)initWithUrl:(NSString*)widgetUrl forWidget:(Widget*)widget; +/** + Display an alert over this controller. + + @param error the error to display. + */ +- (void)showErrorAsAlert:(NSError*)error; + + +#pragma mark - postMessage API + +/** + Callback called when the widget make a postMessage API request. + + This method can be overidden to implement a specific API between the matrix client + and widget. + + @param requestId the id of the widget request. + @param requestData the request data. + */ +- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData; + +/** + Send a boolean response to a request from the widget. + + @param response the response to send. + @param requestId the id of the widget request. + */ +- (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId; + +/** + Send an integer response to a request from the widget. + + @param response the response to send. + @param requestId the id of the widget request. + */ +- (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId; + +/** + Send a serialiable object response to a request the widget. + + @param response the response to send. + @param requestId the id of the widget request. + */ +- (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId; + +/** + Send a message error to a request from the widget. + + @param message the error message. + @param requestId the id of the widget request. + */ +- (void)sendError:(NSString*)message toRequest:(NSString*)requestId; + +/** + Send a localised message error to a request from the widget. + + @param errorKey the string id of the message error. + @param requestId the id of the widget request. + */ +- (void)sendLocalisedError:(NSString*)errorKey toRequest:(NSString*)requestId; + @end diff --git a/Riot/ViewController/Widgets/WidgetViewController.m b/Riot/ViewController/Widgets/WidgetViewController.m index c031f823b..aad876a52 100644 --- a/Riot/ViewController/Widgets/WidgetViewController.m +++ b/Riot/ViewController/Widgets/WidgetViewController.m @@ -16,6 +16,10 @@ #import "WidgetViewController.h" +#import "AppDelegate.h" + +NSString *const kJavascriptSendResponseToPostMessageAPI = @"riotIOS.sendResponse('%@', %@);"; + @interface WidgetViewController () { Widget *widget; @@ -42,7 +46,239 @@ webView.scalesPageToFit = NO; webView.scrollView.bounces = NO; - self.navigationItem.title = widget.name; + // Disable opacity so that the webview background uses the current interface theme + webView.opaque = NO; + + if (widget) + { + self.navigationItem.title = widget.name ? widget.name : widget.type; + } +} + +- (void)showErrorAsAlert:(NSError*)error +{ + NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey]; + NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey]; + if (!title) + { + if (msg) + { + title = msg; + msg = nil; + } + else + { + title = [NSBundle mxk_localizedStringForKey:@"error"]; + } + } + + __weak __typeof__(self) weakSelf = self; + + UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) { + + typeof(self) self = weakSelf; + + if (self) + { + // Leave this widget VC + [self withdrawViewControllerAnimated:YES completion:nil]; + } + + }]]; + + [self presentViewController:alert animated:YES completion:nil]; +} + +#pragma mark - UIWebViewDelegate + +-(void)webViewDidFinishLoad:(UIWebView *)theWebView +{ + [self enableDebug]; + + // Setup js code + NSString *path = [[NSBundle mainBundle] pathForResource:@"postMessageAPI" ofType:@"js"]; + NSString *js = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; + [webView stringByEvaluatingJavaScriptFromString:js]; + + [self stopActivityIndicator]; + + // Check connectivity + if ([AppDelegate theDelegate].isOffline) + { + // The web page may be in the cache, so its loading will be successful + // but we cannot go further, it often leads to a blank screen. + // So, display an error so that the user can escape. + NSError *error = [NSError errorWithDomain:NSURLErrorDomain + code:NSURLErrorNotConnectedToInternet + userInfo:@{ + NSLocalizedDescriptionKey : NSLocalizedStringFromTable(@"network_offline_prompt", @"Vector", nil) + }]; + [self showErrorAsAlert:error]; + } +} + +- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType +{ + NSString *urlString = [[request URL] absoluteString]; + + if ([urlString hasPrefix:@"js:"]) + { + // Listen only to scheme of the JS-UIWebView bridge + NSString *jsonString = [[[urlString componentsSeparatedByString:@"js:"] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; + NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + + NSError *error; + NSDictionary *parameters = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers + error:&error]; + if (!error) + { + // Retrieve the js event payload data + NSDictionary *eventData; + MXJSONModelSetDictionary(eventData, parameters[@"event.data"]); + + NSString *requestId; + MXJSONModelSetString(requestId, eventData[@"_id"]); + + if (requestId) + { + [self onPostMessageRequest:requestId data:eventData]; + } + else + { + NSLog(@"[WidgetVC] shouldStartLoadWithRequest: ERROR: Missing request id in postMessage API %@", parameters); + } + } + + return NO; + } + + if (navigationType == UIWebViewNavigationTypeLinkClicked ) + { + // Open links outside the app + [[UIApplication sharedApplication] openURL:[request URL]]; + return NO; + } + + return YES; +} + +- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error +{ + // Filter out the users's scalar token + NSString *errorDescription = error.description; + NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"scalar_token=\\w*" + options:NSRegularExpressionCaseInsensitive error:nil]; + errorDescription = [regex stringByReplacingMatchesInString:errorDescription + options:0 + range:NSMakeRange(0, errorDescription.length) + withTemplate:@"scalar_token=..."]; + + NSLog(@"[WidgetVC] didFailLoadWithError: %@", errorDescription); + + [self stopActivityIndicator]; + [self showErrorAsAlert:error]; +} + +#pragma mark - postMessage API + +- (void)onPostMessageRequest:(NSString*)requestId data:(NSDictionary*)requestData +{ + NSString *action; + MXJSONModelSetString(action, requestData[@"action"]); + + if ([@"m.sticker" isEqualToString:action]) + { + // Extract the sticker event content and send it as is + + // The key should be "data" according to https://docs.google.com/document/d/1uPF7XWY_dXTKVKV7jZQ2KmsI19wn9-kFRgQ1tFQP7wQ/edit?usp=sharing + // TODO: Fix it once spec is finalised + NSDictionary *widgetData; + NSDictionary *stickerContent; + MXJSONModelSetDictionary(widgetData, requestData[@"widgetData"]); + if (widgetData) + { + MXJSONModelSetDictionary(stickerContent, widgetData[@"content"]); + } + + if (stickerContent) + { + // Let the data source manage the sending cycle + [_roomDataSource sendEventOfType:kMXEventTypeStringSticker content:stickerContent success:nil failure:nil]; + } + else + { + NSLog(@"[WidgetVC] onPostMessageRequest: ERROR: Invalid content for m.sticker: %@", requestData); + } + + // Consider we are done with the sticker picker widget + [self withdrawViewControllerAnimated:YES completion:nil]; + } +} + +- (void)sendBoolResponse:(BOOL)response toRequest:(NSString*)requestId +{ + // Convert BOOL to "true" or "false" + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI, + requestId, + response ? @"true" : @"false"]; + + [webView stringByEvaluatingJavaScriptFromString:js]; +} + +- (void)sendIntegerResponse:(NSUInteger)response toRequest:(NSString*)requestId +{ + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI, + requestId, + @(response)]; + + [webView stringByEvaluatingJavaScriptFromString:js]; +} + +- (void)sendNSObjectResponse:(NSObject*)response toRequest:(NSString*)requestId +{ + NSString *jsString; + + if (response) + { + // Convert response into a JS object through a JSON string + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:response + options:0 + error:0]; + NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; + + jsString = [NSString stringWithFormat:@"JSON.parse('%@')", jsonString]; + } + else + { + jsString = @"null"; + } + + NSString *js = [NSString stringWithFormat:kJavascriptSendResponseToPostMessageAPI, + requestId, + jsString]; + + [webView stringByEvaluatingJavaScriptFromString:js]; +} + +- (void)sendError:(NSString*)message toRequest:(NSString*)requestId +{ + NSLog(@"[WidgetVC] sendError: Action %@ failed with message: %@", requestId, message); + + // TODO: JS has an additional optional parameter: nestedError + [self sendNSObjectResponse:@{ + @"error": @{ + @"message": message + } + } + toRequest:requestId]; +} + +- (void)sendLocalisedError:(NSString*)errorKey toRequest:(NSString*)requestId +{ + [self sendError:NSLocalizedStringFromTable(errorKey, @"Vector", nil) toRequest:requestId]; } @end diff --git a/RiotShareExtension/Info.plist b/RiotShareExtension/Info.plist index adae233be..3a95c6092 100644 --- a/RiotShareExtension/Info.plist +++ b/RiotShareExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.6.15 + 0.6.16 CFBundleVersion 1 NSExtension diff --git a/SiriIntents/Info.plist b/SiriIntents/Info.plist index 25ff2e906..c914d2e10 100644 --- a/SiriIntents/Info.plist +++ b/SiriIntents/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.6.15 + 0.6.16 CFBundleVersion 1 NSExtension