diff --git a/CHANGES.rst b/CHANGES.rst index 3be965fab..8cdeb7417 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,20 @@ +Changes in 0.6.18 (2018-07-03) +=============================================== + +Improvements: + * Upgrade MatrixKit version (v0.7.15). + * RoomVC: Add a re-request keys button on message unable to decrypt (#1879). + * Analytics: Move code from AppDelegate to a dedicated class: Analytics. + * Analytics: Track Matrix SDK stats (time to startup the app). + * Crypto: Add telemetry for events unable to decrypt (UTDs). + * Added the i18n localisation strings to the accessibility labels (#1842), thanks to @einMarco (PR#1906). + * Added titles to sound files ID3 tags. + +Bug fix: + * RoomVC: Read receipts processing dramatically slows down UI (#1899). + * Lag in typing (#1820). + * E2E messages not decrypted in notifs after logging back in (#1914). + Changes in 0.6.17 (2018-06-01) =============================================== @@ -1116,4 +1133,4 @@ Changes in Vector iOS in 0.1.0 (2016-01-29) Changes in Vector iOS in 0.0.1 (2015-11-16) =============================================== - * Creation : The first implementation of Vector application based on Matrix iOS Kit v0.2.7. \ No newline at end of file + * Creation : The first implementation of Vector application based on Matrix iOS Kit v0.2.7. diff --git a/Podfile b/Podfile index 8901c7ffc..d9d427d94 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.14' +$matrixKitVersion = '0.7.15' # The develop branch version #$matrixKitVersion = 'develop' @@ -58,7 +58,7 @@ end abstract_target 'RiotPods' do - pod 'GBDeviceInfo', '~> 5.1.0' + pod 'GBDeviceInfo', '~> 5.2.0' # Piwik for analytics # While https://github.com/matomo-org/matomo-sdk-ios/pull/223 is not released, use the PR branch diff --git a/Podfile.lock b/Podfile.lock index 01b0c1cae..bbcc75a9a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -37,40 +37,40 @@ PODS: - DTFoundation/Core - DTFoundation/UIKit (1.7.13): - DTFoundation/Core - - GBDeviceInfo (5.1.0): - - GBDeviceInfo/Core (= 5.1.0) - - GBDeviceInfo/Core (5.1.0) + - GBDeviceInfo (5.2.0): + - GBDeviceInfo/Core (= 5.2.0) + - GBDeviceInfo/Core (5.2.0) - GZIP (1.2.1) - HPGrowingTextView (1.1) - libPhoneNumber-iOS (0.9.13) - - MatrixKit (0.7.14): + - MatrixKit (0.7.15): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixKit/Core (= 0.7.14) - - MatrixSDK (= 0.10.11) - - MatrixKit/AppExtension (0.7.14): + - MatrixKit/Core (= 0.7.15) + - MatrixSDK (= 0.10.12) + - MatrixKit/AppExtension (0.7.15): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - DTCoreText/Extension - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.10.11) - - MatrixKit/Core (0.7.14): + - MatrixSDK (= 0.10.12) + - MatrixKit/Core (0.7.15): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.21) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.10.11) - - MatrixSDK (0.10.11): - - MatrixSDK/Core (= 0.10.11) - - MatrixSDK/Core (0.10.11): + - MatrixSDK (= 0.10.12) + - MatrixSDK (0.10.12): + - MatrixSDK/Core (= 0.10.12) + - MatrixSDK/Core (0.10.12): - AFNetworking (~> 3.2.0) - GZIP (~> 1.2.1) - OLMKit (~> 2.2.2) - - Realm (~> 3.3.2) - - MatrixSDK/JingleCallStack (0.10.11): + - Realm (~> 3.6.0) + - MatrixSDK/JingleCallStack (0.10.12): - MatrixSDK/Core - WebRTC (= 63.11.20455) - OLMKit (2.2.2): @@ -81,17 +81,17 @@ PODS: - PiwikTracker (4.4.2): - PiwikTracker/Core (= 4.4.2) - PiwikTracker/Core (4.4.2) - - Realm (3.3.2): - - Realm/Headers (= 3.3.2) - - Realm/Headers (3.3.2) + - Realm (3.6.0): + - Realm/Headers (= 3.6.0) + - Realm/Headers (3.6.0) - WebRTC (63.11.20455) DEPENDENCIES: - cmark - DTCoreText - - GBDeviceInfo (~> 5.1.0) - - MatrixKit (= 0.7.14) - - MatrixKit/AppExtension (= 0.7.14) + - GBDeviceInfo (~> 5.2.0) + - MatrixKit (= 0.7.15) + - MatrixKit/AppExtension (= 0.7.15) - MatrixSDK/JingleCallStack - OLMKit - PiwikTracker (from `https://github.com/manuroe/matomo-sdk-ios.git`, branch `feature/CustomVariables`) @@ -127,17 +127,17 @@ SPEC CHECKSUMS: cmark: ec0275215b504780287b6fca360224e384368af8 DTCoreText: e5d688cffc9f6a61eddd1a4f94e2046851230de3 DTFoundation: f03be9fd786f11e505bb8fc44e2a3732bf0917df - GBDeviceInfo: 8e43440ae1264d9a79beea81800363cf192ce322 + GBDeviceInfo: 2c65ceb9404f9079264d4c238f5b81916fdfc5e2 GZIP: 7ee835f989fb3c6ea79005fc90b8fa6af710a70d HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 libPhoneNumber-iOS: e444379ac18bbfbdefad571da735b2cd7e096caa - MatrixKit: ca192403abc03229f5cc5c193bd24a292e302304 - MatrixSDK: abb44d24e0426f3734630362c736d96446a629a3 + MatrixKit: 5668775ddcb36939f22cb5e76dcf617980c52c75 + MatrixSDK: 5d5eae9a9a098ec130022da4a4925332df8df1d9 OLMKit: b9d8c0ffee9ea8c45bc0aaa9afb47f93fba7efbd PiwikTracker: 42862c7b13028065c3dfd36b4dc38db8a5765acf - Realm: d927fbf66df5532cfafc08afb5f7e53ded37b894 + Realm: 08b464b462d4f31bbd4ba5f5a1c8722ef0a700b7 WebRTC: f2a6203584745fe53532633397557876b5d71640 -PODFILE CHECKSUM: f62acabc542b9493f2d704d8c5163a53051b1979 +PODFILE CHECKSUM: 948cd5479b6467245ee83ba82304e73d83c7e4e2 COCOAPODS: 1.5.3 diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index f3a963be3..4521654de 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -59,6 +59,13 @@ 32471CE11F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32471CDF1F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m */; }; 32471CE21F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32471CE01F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.xib */; }; 325E1C151E8D03950018D91E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 325E1C131E8D03950018D91E /* LaunchScreen.storyboard */; }; + 3267EFB220E2A04100FF1CAA /* Analytics.m in Sources */ = {isa = PBXBuildFile; fileRef = 3267EFB120E2A04100FF1CAA /* Analytics.m */; }; + 3267EFB720E379FE00FF1CAA /* CHANGES.rst in Resources */ = {isa = PBXBuildFile; fileRef = 3267EFB320E379FD00FF1CAA /* CHANGES.rst */; }; + 3267EFB820E379FE00FF1CAA /* Podfile in Resources */ = {isa = PBXBuildFile; fileRef = 3267EFB420E379FD00FF1CAA /* Podfile */; }; + 3267EFB920E379FE00FF1CAA /* AUTHORS.rst in Resources */ = {isa = PBXBuildFile; fileRef = 3267EFB520E379FD00FF1CAA /* AUTHORS.rst */; }; + 3267EFBA20E379FE00FF1CAA /* README.rst in Resources */ = {isa = PBXBuildFile; fileRef = 3267EFB620E379FD00FF1CAA /* README.rst */; }; + 3267EFC020E4A3DD00FF1CAA /* DecryptionFailureTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = 3267EFBE20E4A3DD00FF1CAA /* DecryptionFailureTracker.m */; }; + 3267EFC320E5055800FF1CAA /* DecryptionFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 3267EFC220E5055800FF1CAA /* DecryptionFailure.m */; }; 327382B51F276AD200356143 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382A81F276AD200356143 /* InfoPlist.strings */; }; 327382B61F276AD200356143 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382AA1F276AD200356143 /* Localizable.strings */; }; 327382B71F276AD200356143 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382AC1F276AD200356143 /* Vector.strings */; }; @@ -113,6 +120,8 @@ 9D686B069F967C4D4BBC610F /* Pods_RiotPods_SiriIntents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB50EEBE8214352B9EBD6394 /* Pods_RiotPods_SiriIntents.framework */; }; B19A173920B7F94800DF0BB0 /* DeactivateAccountViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B19A173820B7F94800DF0BB0 /* DeactivateAccountViewController.m */; }; B19A173B20B7F96700DF0BB0 /* DeactivateAccountViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B19A173A20B7F96700DF0BB0 /* DeactivateAccountViewController.storyboard */; }; + B1D818C020EA4C7400D5F36D /* RiotSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D818BF20EA4C7400D5F36D /* RiotSettings.swift */; }; + B1D818C120EA794400D5F36D /* RiotSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D818BF20EA4C7400D5F36D /* RiotSettings.swift */; }; DDDE2AB95F865F2292B1D315 /* Pods_RiotPods_RiotShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 23D7292481328A48B8D5D4ED /* Pods_RiotPods_RiotShareExtension.framework */; }; F0131DE51F2200D600CBF707 /* RiotSplitViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0131DE41F2200D600CBF707 /* RiotSplitViewController.m */; }; F0173EB51FCF346800B5F6A3 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = F0173EAF1FCF346800B5F6A3 /* Vector.strings */; }; @@ -725,6 +734,16 @@ 32471CDF1F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RoomMembershipExpandedWithPaginationTitleBubbleCell.m; sourceTree = ""; }; 32471CE01F13AC1500BDF50A /* RoomMembershipExpandedWithPaginationTitleBubbleCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RoomMembershipExpandedWithPaginationTitleBubbleCell.xib; sourceTree = ""; }; 325E1C141E8D03950018D91E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 3267EFB020E2A04100FF1CAA /* Analytics.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Analytics.h; path = Riot/Analytics/Analytics.h; sourceTree = SOURCE_ROOT; }; + 3267EFB120E2A04100FF1CAA /* Analytics.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Analytics.m; path = Riot/Analytics/Analytics.m; sourceTree = SOURCE_ROOT; }; + 3267EFB320E379FD00FF1CAA /* CHANGES.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CHANGES.rst; sourceTree = ""; }; + 3267EFB420E379FD00FF1CAA /* Podfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Podfile; sourceTree = ""; }; + 3267EFB520E379FD00FF1CAA /* AUTHORS.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AUTHORS.rst; sourceTree = ""; }; + 3267EFB620E379FD00FF1CAA /* README.rst */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = README.rst; sourceTree = ""; }; + 3267EFBE20E4A3DD00FF1CAA /* DecryptionFailureTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DecryptionFailureTracker.m; path = Riot/Analytics/DecryptionFailureTracker.m; sourceTree = SOURCE_ROOT; }; + 3267EFBF20E4A3DD00FF1CAA /* DecryptionFailureTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DecryptionFailureTracker.h; path = Riot/Analytics/DecryptionFailureTracker.h; sourceTree = SOURCE_ROOT; }; + 3267EFC120E5055800FF1CAA /* DecryptionFailure.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DecryptionFailure.h; path = Riot/Analytics/DecryptionFailure.h; sourceTree = SOURCE_ROOT; }; + 3267EFC220E5055800FF1CAA /* DecryptionFailure.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = DecryptionFailure.m; path = Riot/Analytics/DecryptionFailure.m; sourceTree = SOURCE_ROOT; }; 327382A91F276AD200356143 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = InfoPlist.strings; sourceTree = ""; }; 327382AB1F276AD200356143 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = Localizable.strings; sourceTree = ""; }; 327382AD1F276AD200356143 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = Vector.strings; sourceTree = ""; }; @@ -804,6 +823,9 @@ B19A173720B7F94800DF0BB0 /* DeactivateAccountViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeactivateAccountViewController.h; sourceTree = ""; }; B19A173820B7F94800DF0BB0 /* DeactivateAccountViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeactivateAccountViewController.m; sourceTree = ""; }; B19A173A20B7F96700DF0BB0 /* DeactivateAccountViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = DeactivateAccountViewController.storyboard; sourceTree = ""; }; + B1D818BC20E66C3300D5F36D /* Riot-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Riot-Bridging-Header.h"; sourceTree = ""; }; + B1D818BF20EA4C7400D5F36D /* RiotSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RiotSettings.swift; sourceTree = ""; }; + B1D818C220EA7DB500D5F36D /* RiotShareExtension-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "RiotShareExtension-Bridging-Header.h"; path = "RiotShareExtension/RiotShareExtension-Bridging-Header.h"; sourceTree = SOURCE_ROOT; }; C195C53961EA28E6900AEB68 /* Pods-Riot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riot.release.xcconfig"; path = "Pods/Target Support Files/Pods-Riot/Pods-Riot.release.xcconfig"; sourceTree = ""; }; C5258DFF261AA3AB228A3F11 /* Pods-RiotPods-RiotShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotPods-RiotShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RiotPods-RiotShareExtension/Pods-RiotPods-RiotShareExtension.debug.xcconfig"; sourceTree = ""; }; DB50EEBE8214352B9EBD6394 /* Pods_RiotPods_SiriIntents.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RiotPods_SiriIntents.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1489,6 +1511,7 @@ isa = PBXGroup; children = ( 2466B7551F2F80B800AE27B0 /* Info.plist */, + B1D818C220EA7DB500D5F36D /* RiotShareExtension-Bridging-Header.h */, 2466B7561F2F80B800AE27B0 /* RiotShareExtension.entitlements */, 24D6B3441F3C8F8A00FC7A71 /* ViewController */, 2439DD5F1F6BBE390090F42D /* Views */, @@ -1597,6 +1620,20 @@ path = "jitsi-meet"; sourceTree = ""; }; + 3267EFAF20E2A00F00FF1CAA /* Analytics */ = { + isa = PBXGroup; + children = ( + 3267EFB020E2A04100FF1CAA /* Analytics.h */, + 3267EFB120E2A04100FF1CAA /* Analytics.m */, + 3267EFBF20E4A3DD00FF1CAA /* DecryptionFailureTracker.h */, + 3267EFBE20E4A3DD00FF1CAA /* DecryptionFailureTracker.m */, + 3267EFC120E5055800FF1CAA /* DecryptionFailure.h */, + 3267EFC220E5055800FF1CAA /* DecryptionFailure.m */, + ); + name = Analytics; + path = "New Group"; + sourceTree = ""; + }; 327382A71F276AD200356143 /* de.lproj */ = { isa = PBXGroup; children = ( @@ -1796,6 +1833,7 @@ F083BB081E7009EC00A9B29C /* Riot */ = { isa = PBXGroup; children = ( + 3267EFAF20E2A00F00FF1CAA /* Analytics */, F083BB091E7009EC00A9B29C /* API */, F083BB0E1E7009EC00A9B29C /* Assets */, F083BBE41E7009EC00A9B29C /* Categories */, @@ -2215,6 +2253,7 @@ F083BC151E7009EC00A9B29C /* Tools.m */, F083BC161E7009EC00A9B29C /* RiotDesignValues.h */, F083BC171E7009EC00A9B29C /* RiotDesignValues.m */, + B1D818BF20EA4C7400D5F36D /* RiotSettings.swift */, ); path = Utils; sourceTree = ""; @@ -2667,6 +2706,7 @@ F083BEA41E700B2800A9B29C /* Supporting Files */ = { isa = PBXGroup; children = ( + B1D818BC20E66C3300D5F36D /* Riot-Bridging-Header.h */, F0DD05CF1F615ECF00CB5292 /* LaunchScreenRiot.png */, F083BBED1E7009EC00A9B29C /* empty.mm */, F083BBEE1E7009EC00A9B29C /* GoogleService-Info.plist */, @@ -2682,6 +2722,10 @@ isa = PBXGroup; children = ( F083BB081E7009EC00A9B29C /* Riot */, + 3267EFB520E379FD00FF1CAA /* AUTHORS.rst */, + 3267EFB320E379FD00FF1CAA /* CHANGES.rst */, + 3267EFB420E379FD00FF1CAA /* Podfile */, + 3267EFB620E379FD00FF1CAA /* README.rst */, F083BB021E7005FD00A9B29C /* RiotTests */, 24CBEC4F1F0EAD310093EABB /* RiotShareExtension */, 92726A441F58737A004AD26F /* SiriIntents */, @@ -2870,6 +2914,7 @@ F094A9A11B78D8F000B1FBBF = { CreatedOnToolsVersion = 6.2; DevelopmentTeam = 7J4U792NQT; + LastSwiftMigration = 0940; ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.ApplicationGroups.iOS = { @@ -2988,6 +3033,7 @@ F083BD641E7009ED00A9B29C /* direct_icon@3x.png in Resources */, F083BE261E7009ED00A9B29C /* SegmentedViewController.xib in Resources */, F083BE161E7009ED00A9B29C /* MediaAlbumContentViewController.xib in Resources */, + 3267EFB720E379FE00FF1CAA /* CHANGES.rst in Resources */, F083BD441E7009ED00A9B29C /* call_speaker_on_icon@3x.png in Resources */, F0614A0F1EDDCCE700F5DC9A /* jump_to_unread@3x.png in Resources */, F083BE511E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgBubbleCell.xib in Resources */, @@ -3040,6 +3086,7 @@ 32BB89F0204D86DA002F3AEC /* InfoPlist.strings in Resources */, F083BD3E1E7009ED00A9B29C /* call_hangup_icon@3x.png in Resources */, 32AE61F21F0D2183007255F4 /* InfoPlist.strings in Resources */, + 3267EFB920E379FE00FF1CAA /* AUTHORS.rst in Resources */, F083BDB91E7009ED00A9B29C /* remove_icon_pink.png in Resources */, F083BE531E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, F083BD7F1E7009ED00A9B29C /* error@3x.png in Resources */, @@ -3070,6 +3117,7 @@ F083BD5F1E7009ED00A9B29C /* details_icon.png in Resources */, F083BE241E7009ED00A9B29C /* RoomViewController.xib in Resources */, F083BE7B1E7009ED00A9B29C /* RoomInputToolbarView.xib in Resources */, + 3267EFB820E379FE00FF1CAA /* Podfile in Resources */, F0E05A3D1EA0F9EB004B83FB /* tab_people_selected@2x.png in Resources */, F0E05A451EA0F9EB004B83FB /* tab_rooms.png in Resources */, F083BE751E7009ED00A9B29C /* RoomOutgoingTextMsgWithoutSenderNameBubbleCell.xib in Resources */, @@ -3319,6 +3367,7 @@ F0E05A3C1EA0F9EB004B83FB /* tab_people_selected.png in Resources */, F083BD221E7009ED00A9B29C /* add_participant.png in Resources */, F083BDC61E7009ED00A9B29C /* search_bg@2x.png in Resources */, + 3267EFBA20E379FE00FF1CAA /* README.rst in Resources */, F083BD421E7009ED00A9B29C /* call_speaker_on_icon.png in Resources */, F04AF26D1F83A4C100D20F4D /* Localizable.strings in Resources */, F083BD9A1E7009ED00A9B29C /* logo@3x.png in Resources */, @@ -3477,6 +3526,7 @@ buildActionMask = 2147483647; files = ( 24EEE5A01F23A08900B3C705 /* RoomTableViewCell.m in Sources */, + B1D818C120EA794400D5F36D /* RiotSettings.swift in Sources */, 24D6B3581F3C90D300FC7A71 /* ShareDataSource.m in Sources */, 245FC3EF1F3DD30800603C6A /* RecentCellData.m in Sources */, 24D6B35C1F3CA03600FC7A71 /* RoomsListViewController.m in Sources */, @@ -3543,6 +3593,7 @@ 321082B21F0E9F40002E0091 /* RoomMembershipCollapsedBubbleCell.m in Sources */, 32C2356F1F7B871800E38FC5 /* WidgetPickerViewController.m in Sources */, F083BE661E7009ED00A9B29C /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */, + 3267EFB220E2A04100FF1CAA /* Analytics.m in Sources */, F083BE141E7009ED00A9B29C /* HomeViewController.m in Sources */, F083BDFB1E7009ED00A9B29C /* RoomSearchDataSource.m in Sources */, 32EF474920B6EE990031695C /* StickerPickerViewController.m in Sources */, @@ -3591,6 +3642,7 @@ F083BE2D1E7009ED00A9B29C /* ForgotPasswordInputsView.m in Sources */, F083BE7E1E7009ED00A9B29C /* InviteRecentTableViewCell.m in Sources */, F083BE3C1E7009ED00A9B29C /* RoomIncomingEncryptedAttachmentWithoutSenderInfoBubbleCell.m in Sources */, + 3267EFC320E5055800FF1CAA /* DecryptionFailure.m in Sources */, F083BE211E7009ED00A9B29C /* RoomSearchViewController.m in Sources */, 32185B311F20FA2B00752141 /* LanguagePickerViewController.m in Sources */, F083BE3E1E7009ED00A9B29C /* RoomIncomingEncryptedAttachmentWithPaginationTitleBubbleCell.m in Sources */, @@ -3632,6 +3684,7 @@ F083BE781E7009ED00A9B29C /* RoomOutgoingTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, F083BE5E1E7009ED00A9B29C /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.m in Sources */, F083BD1E1E7009ED00A9B29C /* AppDelegate.m in Sources */, + B1D818C020EA4C7400D5F36D /* RiotSettings.swift in Sources */, F083BE8A1E7009ED00A9B29C /* ExpandedRoomTitleView.m in Sources */, F083BE0C1E7009ED00A9B29C /* ContactDetailsViewController.m in Sources */, F083BE821E7009ED00A9B29C /* RecentTableViewCell.m in Sources */, @@ -3655,6 +3708,7 @@ F083BE101E7009ED00A9B29C /* CountryPickerViewController.m in Sources */, F083BE581E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m in Sources */, F083BDF61E7009ED00A9B29C /* Contact.m in Sources */, + 3267EFC020E4A3DD00FF1CAA /* DecryptionFailureTracker.m in Sources */, F083BE961E7009ED00A9B29C /* MessagesSearchResultAttachmentBubbleCell.m in Sources */, F083BE391E7009ED00A9B29C /* RoomEncryptedDataBubbleCell.m in Sources */, F083BDF01E7009ED00A9B29C /* UIViewController+RiotSearch.m in Sources */, @@ -3988,12 +4042,19 @@ DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 7J4U792NQT; ENABLE_BITCODE = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "IS_SHARE_EXTENSION=1", + ); INFOPLIST_FILE = RiotShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = im.vector.app.shareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PRODUCT_NAME)/RiotShareExtension-Bridging-Header.h"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -4011,12 +4072,19 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 7J4U792NQT; ENABLE_BITCODE = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "IS_SHARE_EXTENSION=1", + ); INFOPLIST_FILE = RiotShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = im.vector.app.shareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PRODUCT_NAME)/RiotShareExtension-Bridging-Header.h"; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -4082,6 +4150,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + DEFINES_MODULE = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -4099,7 +4168,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -4128,6 +4197,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; + DEFINES_MODULE = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; @@ -4138,7 +4208,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -4150,7 +4220,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = A5030B7C3C0B6EB83A9257BD /* Pods-RiotPods-Riot.debug.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Riot/Riot.entitlements; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = 7J4U792NQT; @@ -4177,6 +4249,9 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = im.vector.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PRODUCT_NAME)/Riot-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -4185,7 +4260,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = F546BCBBB9BBEE67DB28878A /* Pods-RiotPods-Riot.release.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Riot/Riot.entitlements; DEVELOPMENT_TEAM = 7J4U792NQT; ENABLE_BITCODE = NO; @@ -4211,6 +4288,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = im.vector.app; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "$(SRCROOT)/$(PRODUCT_NAME)/Riot-Bridging-Header.h"; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -4218,6 +4297,7 @@ F094A9CC1B78D8F000B1FBBF /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 7J4U792NQT; FRAMEWORK_SEARCH_PATHS = ( @@ -4239,6 +4319,7 @@ F094A9CD1B78D8F000B1FBBF /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 7J4U792NQT; FRAMEWORK_SEARCH_PATHS = ( diff --git a/Riot/API/RageShakeManager.m b/Riot/API/RageShakeManager.m index 5b93e5cee..a402c064c 100644 --- a/Riot/API/RageShakeManager.m +++ b/Riot/API/RageShakeManager.m @@ -24,6 +24,8 @@ #import +#import "Riot-Swift.h" + static RageShakeManager* sharedInstance = nil; @interface RageShakeManager() { @@ -111,7 +113,7 @@ static RageShakeManager* sharedInstance = nil; // Start only if the application is in foreground // And if the rageshake user setting is enabled if ([AppDelegate theDelegate].isAppForeground - && [[NSUserDefaults standardUserDefaults] boolForKey:@"enableRageShake"] + && RiotSettings.shared.enableRageShake && !confirmationAlert) { NSLog(@"[RageShakeManager] Start shaking with [%@]", [responder class]); @@ -163,8 +165,7 @@ static RageShakeManager* sharedInstance = nil; self->confirmationAlert = nil; // Disable rageshake user setting - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"enableRageShake"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + RiotSettings.shared.enableRageShake = NO; } }]]; diff --git a/Riot/Analytics/Analytics.h b/Riot/Analytics/Analytics.h new file mode 100644 index 000000000..ad8466e74 --- /dev/null +++ b/Riot/Analytics/Analytics.h @@ -0,0 +1,63 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import +#import "DecryptionFailureTracker.h" + +/** + `Analytics` sends analytics to an analytics tool. + */ +@interface Analytics : NSObject + +/** + Returns the shared Analytics manager. + + @return the shared Analytics manager. + */ ++ (instancetype)sharedInstance; + +/** + Start doing analytics if the settings `enableCrashReport` is enabled. + */ +- (void)start; + +/** + Stop doing analytics. + */ +- (void)stop; + +/** + Track a screen display. + + @param screenName the name of the displayed screen. + */ +- (void)trackScreen:(NSString*)screenName; + +/** + Flush analytics data. + */ +- (void)dispatch; + +/** + Track how long the launch screen has been displayed to the end user. + + @param seconds the duration in seconds. + */ +- (void)trackLaunchScreenDisplayDuration: (NSTimeInterval)seconds; + +@end diff --git a/Riot/Analytics/Analytics.m b/Riot/Analytics/Analytics.m new file mode 100644 index 000000000..a87b29315 --- /dev/null +++ b/Riot/Analytics/Analytics.m @@ -0,0 +1,189 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "Analytics.h" + +#import "AppDelegate.h" +#import "Riot-Swift.h" + +// All metrics are store under a Piwik category called "Metrics". +// Then, there are 2 Piwik actions: "iOS.startup" and "iOS.stats" (these actions +// are namespaced by plaform to have a nice rendering on the Piwik website). +// Then, we use constants defined by the Matrix SDK as Piwik Names (ex:"mountData") +NSString *const kAnalyticsMetricsCategory = @"Metrics"; +NSString *const kAnalyticsMetricsActionPattern = @"iOS.%@"; + +// E2E telemetry is stored under a Piwik category called "E2E". +NSString *const kAnalyticsE2eCategory = @"E2E"; +NSString *const kAnalyticsE2eDecryptionFailureAction = @"Decryption failure"; + + +@import PiwikTracker; + +@implementation Analytics + ++ (instancetype)sharedInstance +{ + static Analytics *sharedInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + sharedInstance = [[Analytics alloc] init]; + }); + + return sharedInstance; +} + +- (void)start +{ + NSDictionary *piwikConfig = [[NSUserDefaults standardUserDefaults] objectForKey:@"piwik"]; + [PiwikTracker configureSharedInstanceWithSiteID:piwikConfig[@"siteId"] + baseURL:[NSURL URLWithString:piwikConfig[@"url"]] + userAgent:@"iOSPiwikTracker"]; + + // Check whether the user has enabled the sending of crash reports. + if (RiotSettings.shared.enableCrashReport) + { + [PiwikTracker shared].isOptedOut = NO; + + [[PiwikTracker shared] setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"]; + [[PiwikTracker shared] setCustomVariableWithIndex:2 name:@"App Version" value:[AppDelegate theDelegate].appVersion]; + + // The language is either the one selected by the user within the app + // or, else, the one configured by the OS + NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0]; + [[PiwikTracker shared] setCustomVariableWithIndex:4 name:@"Chosen Language" value:language]; + + MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; + if (account) + { + [[PiwikTracker shared] setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer]; + [[PiwikTracker shared] setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL]; + } + + // TODO: We should also track device and os version + // But that needs to be decided for all platforms + + // Catch and log crashes + [MXLogger logCrashes:YES]; + [MXLogger setBuildVersion:[AppDelegate theDelegate].build]; + +#ifdef DEBUG + // Disable analytics in debug as it pollutes stats + [PiwikTracker shared].isOptedOut = YES; +#endif + } + else + { + NSLog(@"[AppDelegate] The user decided to not send analytics"); + [PiwikTracker shared].isOptedOut = YES; + [MXLogger logCrashes:NO]; + } +} + +- (void)stop +{ + [PiwikTracker shared].isOptedOut = YES; + [MXLogger logCrashes:NO]; +} + +- (void)trackScreen:(NSString *)screenName +{ + // Use the same pattern as Android + NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; + NSString *appVersion = [AppDelegate theDelegate].appVersion; + + [[PiwikTracker shared] trackWithView:@[@"ios", appName, appVersion, screenName] + url:nil]; +} + +- (void)dispatch +{ + [[PiwikTracker shared] dispatch]; +} + +- (void)trackLaunchScreenDisplayDuration:(NSTimeInterval)seconds +{ + NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + action:action + name:kMXAnalyticsStartupLaunchScreen + number:@(seconds * 1000) + url:nil]; +} + +#pragma mark - MXAnalyticsDelegate + +- (void)trackStartupStorePreloadDuration: (NSTimeInterval)seconds +{ + NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + action:action + name:kMXAnalyticsStartupStorePreload + number:@(seconds * 1000) + url:nil]; +} + +- (void)trackStartupMountDataDuration: (NSTimeInterval)seconds +{ + NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + action:action + name:kMXAnalyticsStartupMountData + number:@(seconds * 1000) + url:nil]; +} + +- (void)trackStartupSyncDuration: (NSTimeInterval)seconds isInitial: (BOOL)isInitial +{ + NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStartupCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + action:action + name:isInitial ? kMXAnalyticsStartupInititialSync : kMXAnalyticsStartupIncrementalSync + number:@(seconds * 1000) + url:nil]; +} + +- (void)trackRoomCount: (NSUInteger)roomCount +{ + NSString *action = [NSString stringWithFormat:kAnalyticsMetricsActionPattern, kMXAnalyticsStatsCategory]; + + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsMetricsCategory + action:action + name:kMXAnalyticsStatsRooms + number:@(roomCount) + url:nil]; +} + +#pragma mark - MXDecryptionFailureDelegate + +- (void)trackFailures:(NSDictionary *)failuresCounts +{ + for (NSString *reason in failuresCounts) + { + [[PiwikTracker shared] trackWithEventWithCategory:kAnalyticsE2eCategory + action:kAnalyticsE2eDecryptionFailureAction + name:reason + number:failuresCounts[reason] + url:nil]; + } +} + +@end diff --git a/Riot/Analytics/DecryptionFailure.h b/Riot/Analytics/DecryptionFailure.h new file mode 100644 index 000000000..7ea43962c --- /dev/null +++ b/Riot/Analytics/DecryptionFailure.h @@ -0,0 +1,51 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +/** + Failure reasons as defined in https://docs.google.com/document/d/1es7cTCeJEXXfRCTRgZerAM2Wg5ZerHjvlpfTW-gsOfI. + */ +struct DecryptionFailureReasonStruct +{ + __unsafe_unretained NSString * const unspecified; + __unsafe_unretained NSString * const olmKeysNotSent; + __unsafe_unretained NSString * const olmIndexError; + __unsafe_unretained NSString * const unexpected; +}; +extern const struct DecryptionFailureReasonStruct DecryptionFailureReason; + +/** + `DecryptionFailure` represents a decryption failure. + */ +@interface DecryptionFailure : NSObject + +/** + The id of the event that was unabled to decrypt. + */ +@property (nonatomic) NSString *failedEventId; + +/** + The time the failure has been reported. + */ +@property (nonatomic, readonly) NSTimeInterval ts; + +/** + Decryption failure reason. + */ +@property (nonatomic) NSString *reason; + +@end diff --git a/Riot/Analytics/DecryptionFailure.m b/Riot/Analytics/DecryptionFailure.m new file mode 100644 index 000000000..d43b0ec9f --- /dev/null +++ b/Riot/Analytics/DecryptionFailure.m @@ -0,0 +1,38 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "DecryptionFailure.h" + +const struct DecryptionFailureReasonStruct DecryptionFailureReason = { + .unspecified = @"unspecified_error", + .olmKeysNotSent = @"olm_keys_not_sent_error", + .olmIndexError = @"olm_index_error", + .unexpected = @"unexpected_error" +}; + +@implementation DecryptionFailure + +- (instancetype)init +{ + self = [super init]; + if (self) + { + _ts = [NSDate date].timeIntervalSince1970; + } + return self; +} + +@end diff --git a/Riot/Analytics/DecryptionFailureTracker.h b/Riot/Analytics/DecryptionFailureTracker.h new file mode 100644 index 000000000..45a8e1cee --- /dev/null +++ b/Riot/Analytics/DecryptionFailureTracker.h @@ -0,0 +1,71 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import + +#import "DecryptionFailure.h" + +@import MatrixSDK; + +@protocol MXDecryptionFailureDelegate; + +@interface DecryptionFailureTracker : NSObject + +/** + Returns the shared tracker. + + @return the shared tracker. + */ ++ (instancetype)sharedInstance; + +/** + The delegate object to receive analytics events. + */ +@property (nonatomic) id delegate; + +/** + Report an event unable to decrypt. + + This error can be momentary. The DecryptionFailureTracker will check if it gets + fixed. Else, it will generate a failure (@see `trackFailures`). + + @param event the event. + @param roomState the room state when the event was received. + @param userId my user id. + */ +- (void)reportUnableToDecryptErrorForEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState myUser:(NSString*)userId; + +/** + Flush current data. + */ +- (void)dispatch; + +@end + +/** + The `MXDecryptionFailureDelegate` protocol receives some stats computed by + `DecryptionFailureTracker`. + */ +@protocol MXDecryptionFailureDelegate + +/** + Stats for decryption failures. + + @param failuresCounts the number of errors per failure reason. + */ +- (void)trackFailures:(NSDictionary *)failuresCounts; + +@end diff --git a/Riot/Analytics/DecryptionFailureTracker.m b/Riot/Analytics/DecryptionFailureTracker.m new file mode 100644 index 000000000..ed87dd952 --- /dev/null +++ b/Riot/Analytics/DecryptionFailureTracker.m @@ -0,0 +1,167 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#import "DecryptionFailureTracker.h" + + +// Call `checkFailures` every `CHECK_INTERVAL` +#define CHECK_INTERVAL 5 + +// Give events a chance to be decrypted by waiting `GRACE_PERIOD` before counting +// and reporting them as failures +#define GRACE_PERIOD 60 + + +@interface DecryptionFailureTracker() +{ + // Reported failures + // Every `CHECK_INTERVAL`, this list is checked for failures that happened + // more than`GRACE_PERIOD` ago. Those that did are reported to the delegate. + NSMutableDictionary *reportedFailures; + + // Event ids of failures that were tracked previously + NSMutableSet *trackedEvents; + + // Timer for periodic check + NSTimer *checkFailuresTimer; +} +@end + +@implementation DecryptionFailureTracker + ++ (instancetype)sharedInstance +{ + static DecryptionFailureTracker *sharedInstance = nil; + static dispatch_once_t onceToken; + + dispatch_once(&onceToken, ^{ + sharedInstance = [[DecryptionFailureTracker alloc] init]; + }); + + return sharedInstance; +} + +- (instancetype)init +{ + self = [super init]; + if (self) + { + reportedFailures = [NSMutableDictionary dictionary]; + trackedEvents = [NSMutableSet set]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(eventDidDecrypt:) name:kMXEventDidDecryptNotification object:nil]; + + checkFailuresTimer = [NSTimer scheduledTimerWithTimeInterval:CHECK_INTERVAL + target:self + selector:@selector(checkFailures) + userInfo:nil + repeats:YES]; + } + return self; +} + +- (void)reportUnableToDecryptErrorForEvent:(MXEvent *)event withRoomState:(MXRoomState *)roomState myUser:(NSString *)userId +{ + if (reportedFailures[event.eventId] || [trackedEvents containsObject:event.eventId]) + { + return; + } + + // Filter out "expected" UTDs + // We cannot decrypt messages sent before the user joined the room + MXRoomMember *myUser = [roomState memberWithUserId:userId]; + if (!myUser || myUser.membership != MXMembershipJoin) + { + return; + } + + DecryptionFailure *decryptionFailure = [[DecryptionFailure alloc] init]; + decryptionFailure.failedEventId = event.eventId; + + // Categorise the error + switch (event.decryptionError.code) + { + case MXDecryptingErrorUnknownInboundSessionIdCode: + decryptionFailure.reason = DecryptionFailureReason.olmKeysNotSent; + break; + + case MXDecryptingErrorOlmCode: + decryptionFailure.reason = DecryptionFailureReason.olmIndexError; + break; + + case MXDecryptingErrorEncryptionNotEnabledCode: + case MXDecryptingErrorUnableToDecryptCode: + decryptionFailure.reason = DecryptionFailureReason.unexpected; + break; + + default: + decryptionFailure.reason = DecryptionFailureReason.unspecified; + break; + } + + reportedFailures[event.eventId] = decryptionFailure; +} + +- (void)dispatch +{ + [self checkFailures]; +} + +#pragma mark - Private methods + +/** + Mark reported failures that occured before tsNow - GRACE_PERIOD as failures that should be + tracked. + */ +- (void)checkFailures +{ + NSTimeInterval tsNow = [NSDate date].timeIntervalSince1970; + + NSMutableArray *failuresToTrack = [NSMutableArray array]; + + for (DecryptionFailure *reportedFailure in reportedFailures.allValues) + { + if (reportedFailure.ts < tsNow - GRACE_PERIOD) + { + [failuresToTrack addObject:reportedFailure]; + [reportedFailures removeObjectForKey:reportedFailure.failedEventId]; + [trackedEvents addObject:reportedFailure.failedEventId]; + } + } + + if (_delegate && failuresToTrack.count) + { + // Sort failures by error reason + NSMutableDictionary *failuresCounts = [NSMutableDictionary dictionary]; + for (DecryptionFailure *failure in failuresToTrack) + { + failuresCounts[failure.reason] = @(failuresCounts[failure.reason].unsignedIntegerValue + 1); + } + + NSLog(@"[DecryptionFailureTracker] trackFailures: %@", failuresCounts); + + [_delegate trackFailures:failuresCounts]; + } +} + +- (void)eventDidDecrypt:(NSNotification *)notif +{ + // Could be an event in the reportedFailures, remove it + MXEvent *event = notif.object; + [reportedFailures removeObjectForKey:event.eventId]; +} + +@end diff --git a/Riot/AppDelegate.h b/Riot/AppDelegate.h index ede68dacd..305650eab 100644 --- a/Riot/AppDelegate.h +++ b/Riot/AppDelegate.h @@ -22,6 +22,7 @@ #import "JitsiViewController.h" #import "RageShakeManager.h" +#import "Analytics.h" #import "RiotDesignValues.h" @@ -128,12 +129,6 @@ extern NSString *const kAppDelegateNetworkStatusDidChangeNotification; - (void)selectMatrixAccount:(void (^)(MXKAccount *selectedAccount))onSelection; -#pragma mark - Analytics - -- (void)startAnalytics; -- (void)stopAnalytics; -- (void)trackScreen:(NSString*)screenName; - #pragma mark - Push notifications - (void)registerUserNotificationSettings; diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 5ced25b79..e8a17510b 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -48,14 +48,14 @@ #import "WebViewViewController.h" -@import PiwikTracker; - // Calls #import "CallViewController.h" #import "MXSession+Riot.h" #import "MXRoom+Riot.h" +#import "Riot-Swift.h" + //#define MX_CALL_STACK_OPENWEBRTC #ifdef MX_CALL_STACK_OPENWEBRTC #import @@ -235,10 +235,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN MXSDKOptions *sdkOptions = [MXSDKOptions sharedInstance]; sdkOptions.applicationGroupIdentifier = @"group.im.vector"; - // Track SDK performance on Google analytics - // TODO: needs the same tool - //sdkOptions.analyticsDelegate = [[MXGoogleAnalytics alloc] init]; - // Redirect NSLogs to files only if we are not debugging if (!isatty(STDERR_FILENO)) { @@ -461,15 +457,12 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN _isAppForeground = NO; - // Retrieve custom configuration - NSString* userDefaults = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UserDefaults"]; - NSString *defaultsPathFromApp = [[NSBundle mainBundle] pathForResource:userDefaults ofType:@"plist"]; - NSDictionary *defaults = [NSDictionary dictionaryWithContentsOfFile:defaultsPathFromApp]; - [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; - [[NSUserDefaults standardUserDefaults] synchronize]; + [self setupUserDefaults]; - // Configure Google Analytics here if the option is enabled - [self startAnalytics]; + // Configure our analytics. It will indeed start if the option is enabled + [MXSDKOptions sharedInstance].analyticsDelegate = [Analytics sharedInstance]; + [DecryptionFailureTracker sharedInstance].delegate = [Analytics sharedInstance]; + [[Analytics sharedInstance] start]; // Prepare Pushkit handling _incomingPushEventIds = [NSMutableDictionary dictionary]; @@ -563,7 +556,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN _isAppForeground = NO; // Analytics: Force to send the pending actions - [[PiwikTracker shared] dispatch]; + [[DecryptionFailureTracker sharedInstance] dispatch]; + [[Analytics sharedInstance] dispatch]; } - (void)applicationWillEnterForeground:(UIApplication *)application @@ -594,7 +588,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSLog(@"[AppDelegate] applicationDidBecomeActive"); // Check if there is crash log to send - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]) + if (RiotSettings.shared.enableCrashReport) { [self checkExceptionToReport]; } @@ -1026,70 +1020,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN } } -#pragma mark - Analytics - -- (void)startAnalytics -{ - NSDictionary *piwikConfig = [[NSUserDefaults standardUserDefaults] objectForKey:@"piwik"]; - [PiwikTracker configureSharedInstanceWithSiteID:piwikConfig[@"siteId"] - baseURL:[NSURL URLWithString:piwikConfig[@"url"]] - userAgent:@"iOSPiwikTracker"]; - - // Check whether the user has enabled the sending of crash reports. - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]) - { - [PiwikTracker shared].isOptedOut = NO; - - [[PiwikTracker shared] setCustomVariableWithIndex:1 name:@"App Platform" value:@"iOS Platform"]; - [[PiwikTracker shared] setCustomVariableWithIndex:2 name:@"App Version" value:[self appVersion]]; - - // The language is either the one selected by the user within the app - // or, else, the one configured by the OS - NSString *language = [NSBundle mxk_language] ? [NSBundle mxk_language] : [[NSBundle mainBundle] preferredLocalizations][0]; - [[PiwikTracker shared] setCustomVariableWithIndex:4 name:@"Chosen Language" value:language]; - - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - if (account) - { - [[PiwikTracker shared] setCustomVariableWithIndex:7 name:@"Homeserver URL" value:account.mxCredentials.homeServer]; - [[PiwikTracker shared] setCustomVariableWithIndex:8 name:@"Identity Server URL" value:account.identityServerURL]; - } - - // TODO: We should also track device and os version - // But that needs to be decided for all platforms - - // Catch and log crashes - [MXLogger logCrashes:YES]; - [MXLogger setBuildVersion:[AppDelegate theDelegate].build]; - -#ifdef DEBUG - // Disable analytics in debug as it pollutes stats - [PiwikTracker shared].isOptedOut = YES; -#endif - } - else if ([[NSUserDefaults standardUserDefaults] objectForKey:@"enableCrashReport"]) - { - NSLog(@"[AppDelegate] The user decided to not "); - [PiwikTracker shared].isOptedOut = YES; - [MXLogger logCrashes:NO]; - } -} - -- (void)stopAnalytics -{ - [PiwikTracker shared].isOptedOut = YES; - [MXLogger logCrashes:NO]; -} - -- (void)trackScreen:(NSString *)screenName -{ - // Use the same pattern as Android - NSString *appName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; - NSString *appVersion = [self appVersion]; - - [[PiwikTracker shared] trackWithView:@[@"ios", appName, appVersion, screenName] - url:nil]; -} +#pragma mark - Crash handling // Check if there is a crash log to send to server - (void)checkExceptionToReport @@ -1471,7 +1402,8 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN @"user_id": account.mxCredentials.userId }; - BOOL isNotificationContentShown = !event.isEncrypted || account.showDecryptedContentInNotifications; + BOOL isNotificationContentShown = !event.isEncrypted || RiotSettings.shared.showDecryptedContentInNotifications; + if ((event.eventType == MXEventTypeRoomMessage || event.eventType == MXEventTypeRoomEncrypted) && isNotificationContentShown) { eventNotification.category = @"QUICK_REPLY"; @@ -1559,7 +1491,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSString *msgType = event.content[@"msgtype"]; NSString *content = event.content[@"body"]; - if (event.isEncrypted && !account.showDecryptedContentInNotifications) + if (event.isEncrypted && !RiotSettings.shared.showDecryptedContentInNotifications) { // Hide the content msgType = nil; @@ -2914,8 +2846,11 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN if (launchAnimationContainerView) { - NSTimeInterval durationMs = [[NSDate date] timeIntervalSinceDate:launchAnimationStart] * 1000; - NSLog(@"[AppDelegate] LaunchAnimation was shown for %.3fms", durationMs); + NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:launchAnimationStart]; + NSLog(@"[AppDelegate] LaunchAnimation was shown for %.3fms", duration * 1000); + + // Track it on our analytics + [[Analytics sharedInstance] trackLaunchScreenDisplayDuration:duration]; // TODO: Send durationMs to Piwik // Such information should be the same on all platforms @@ -4086,4 +4021,24 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [self.gdprConsentViewController dismissViewControllerAnimated:YES completion:nil]; } +#pragma mark - Settings + +- (void)setupUserDefaults +{ + // Register "Riot-Defaults.plist" default values + NSString* userDefaults = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UserDefaults"]; + NSString *defaultsPathFromApp = [[NSBundle mainBundle] pathForResource:userDefaults ofType:@"plist"]; + NSDictionary *defaults = [NSDictionary dictionaryWithContentsOfFile:defaultsPathFromApp]; + [[NSUserDefaults standardUserDefaults] registerDefaults:defaults]; + + // Now use RiotSettings and NSUserDefaults to store `showDecryptedContentInNotifications` setting option + // Migrate this information from main MXKAccount to RiotSettings, if value is not in UserDefaults + + if (!RiotSettings.shared.isShowDecryptedContentInNotificationsHasBeenSetOnce) + { + MXKAccount *currentAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; + RiotSettings.shared.showDecryptedContentInNotifications = currentAccount.showDecryptedContentInNotifications; + } +} + @end diff --git a/Riot/Assets/Sounds/busy.mp3 b/Riot/Assets/Sounds/busy.mp3 index fec27ba4c..0c1e4dedb 100644 Binary files a/Riot/Assets/Sounds/busy.mp3 and b/Riot/Assets/Sounds/busy.mp3 differ diff --git a/Riot/Assets/Sounds/message.mp3 b/Riot/Assets/Sounds/message.mp3 index b87eeda7c..c857ee7b1 100644 Binary files a/Riot/Assets/Sounds/message.mp3 and b/Riot/Assets/Sounds/message.mp3 differ diff --git a/Riot/Assets/Sounds/ring.mp3 b/Riot/Assets/Sounds/ring.mp3 index 3c3cdde3f..c02e24625 100644 Binary files a/Riot/Assets/Sounds/ring.mp3 and b/Riot/Assets/Sounds/ring.mp3 differ diff --git a/Riot/Assets/Sounds/ringback.mp3 b/Riot/Assets/Sounds/ringback.mp3 index 6ee34bf39..c56252c1f 100644 Binary files a/Riot/Assets/Sounds/ringback.mp3 and b/Riot/Assets/Sounds/ringback.mp3 differ diff --git a/Riot/Assets/bg.lproj/Vector.strings b/Riot/Assets/bg.lproj/Vector.strings index 7ae92bd2c..7143cae6f 100644 --- a/Riot/Assets/bg.lproj/Vector.strings +++ b/Riot/Assets/bg.lproj/Vector.strings @@ -537,3 +537,8 @@ "deactivate_account_validate_action" = "Деактивирай акаунта"; "deactivate_account_password_alert_title" = "Деактивиране на акаунт"; "deactivate_account_password_alert_message" = "За да продължите, моля въведете паролата си"; +"event_formatter_rerequest_keys_part1_link" = "Изисквай повторно ключове за шифроване"; +"event_formatter_rerequest_keys_part2" = " от другите ми устройства."; +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Заявката е изпратена"; +"rerequest_keys_alert_message" = "Моля стартирайте Riot на друго устройство можещо да разшифрова съобщението, за да може то да изпрати ключовете до това устройство."; diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 2da915898..f6bd2be7b 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -318,7 +318,7 @@ "settings_add_phone_number" = "Telefonnummer hinzufügen"; "settings_fail_to_update_profile" = "Profilaktualisierung fehlgeschlagen"; "settings_global_settings_info" = "Globale Benachrichtungseinstellungen sind verfügbar auf dem %@ Web Client"; -"settings_pin_rooms_with_missed_notif" = "Pinnen von Räumen mit vepassten Benachrichtigungen"; +"settings_pin_rooms_with_missed_notif" = "Pinnen von Räumen mit verpassten Benachrichtigungen"; "settings_pin_rooms_with_unread" = "Pinnen von Räumen mit ungelesenen Nachrichten"; "settings_on_denied_notification" = "Benachrichtigungen verboten für %@, bitte in den Geräte-Einstellungen erlauben"; "settings_contacts_discover_matrix_users" = "Entdecke andere Benutzer mittels E-Mail-Adressen oder Telefonnummern"; @@ -538,3 +538,8 @@ "deactivate_account_validate_action" = "Account deaktiveren"; "deactivate_account_password_alert_title" = "Account deaktivieren"; "deactivate_account_password_alert_message" = "Zum Fortfahren, Password eingeben"; +"event_formatter_rerequest_keys_part1_link" = "Verschlüsselungsschlüssel erneut"; +"event_formatter_rerequest_keys_part2" = " von deinen anderen Geräten anfragen."; +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Anfrage gesendet"; +"rerequest_keys_alert_message" = "Öffne Riot bitte auf einem anderen Gerät, dass die Nachricht entschlüsseln kann, damit es die Schlüssel an dieses Gerät senden kann."; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 05f7ed41f..2920c6ec5 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -518,6 +518,8 @@ "event_formatter_widget_removed" = "%@ widget removed by %@"; "event_formatter_jitsi_widget_added" = "VoIP conference added by %@"; "event_formatter_jitsi_widget_removed" = "VoIP conference removed by %@"; +"event_formatter_rerequest_keys_part1_link" = "Re-request encryption keys"; +"event_formatter_rerequest_keys_part2" = " from your other devices."; // Others "or" = "or"; @@ -622,3 +624,7 @@ "deactivate_account_password_alert_title" = "Deactivate Account"; "deactivate_account_password_alert_message" = "To continue, please enter your password"; + +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Request Sent"; +"rerequest_keys_alert_message" = "Please launch Riot on another device that can decrypt the message so it can send the keys to this device."; diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index 49dc621a3..202ce233a 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -522,3 +522,26 @@ // GDPR "gdpr_consent_not_given_alert_message" = "%@ hasiera-zerbitzaria erabiltzen jarraitzeko erabilera baldintzak irakurri eta onartu behar dituzu."; "gdpr_consent_not_given_alert_review_now_action" = "Irakurri orain"; +"room_action_send_photo_or_video" = "Bidali argazki edo bideoa"; +"room_action_send_sticker" = "Bidali eranskailua"; +"settings_deactivate_account" = "DESAKTIBATU KONTUA"; +"settings_deactivate_my_account" = "Desaktibatu nire kontua"; +"widget_sticker_picker_no_stickerpacks_alert" = "Ez duzu orain eranskailu multzo aktiborik."; +"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Baten bat gehitu orain?"; +"deactivate_account_title" = "Desaktibatu kontua"; +"deactivate_account_informations_part1" = "Honek kontua behin betirako erabilgaitza bihurtuko du. Ezin izango duzu saioa hasi, eta ezin izango du beste inork ID hori erabili. Kontua dagoen gela guztietatik aterako da, eta kontuaren xehetasunak identitate-zerbitzaritik ezabatuko dira. "; +"deactivate_account_informations_part2_emphasize" = "Ekintza hau ezin da desegin."; +"deactivate_account_informations_part3" = "\n\nZure kontua desaktibatzen "; +"deactivate_account_informations_part4_emphasize" = "ez du lehenetsita guk zuk bidalitako mezuak ahaztea eragingo "; +"deactivate_account_informations_part5" = "Guk zure mezuak ahaztea nahi baduzu markatu beheko kutxa.\n\nMatrix-eko mezuen ikusgaitasuna e-mail sistemaren antekoa da. Guk zure mezuak ahaztean ez dizkiogu erabiltzaile berriei edo izena eman ez dutenei erakutsiko, baina jada zure mezuak jaso dituzten erregistratutako erabiltzaileen bere kopia izaten jarraituko dute."; +"deactivate_account_forget_messages_information_part1" = "Ahaztu bidali ditudan mezu guztiak kontua desaktibatzean ("; +"deactivate_account_forget_messages_information_part2_emphasize" = "Abisua"; +"deactivate_account_forget_messages_information_part3" = ": Honekin etorkizuneko erabiltzaileek elkarrizketaren bertsio ez oso bat ikusiko dute)"; +"deactivate_account_validate_action" = "Desaktibatu kontua"; +"deactivate_account_password_alert_title" = "Desaktibatu kontua"; +"deactivate_account_password_alert_message" = "Jarraitzeko sartu zure pasahitza"; +"event_formatter_rerequest_keys_part1_link" = "Berreskatu zifratze gakoak"; +"event_formatter_rerequest_keys_part2" = " zure beste gailuetatik."; +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Eskaria bidalita"; +"rerequest_keys_alert_message" = "Abiatu Riot mezua deszifratu dezakeen beste gailuren batean handik hona gakoak bidali ahal izateko."; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 7f10f1756..15df5a9ee 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -538,3 +538,8 @@ "deactivate_account_validate_action" = "Désactiver le compte"; "deactivate_account_password_alert_title" = "Désactiver le compte"; "deactivate_account_password_alert_message" = "Pour continuer, veuillez renseigner votre mot de passe"; +"event_formatter_rerequest_keys_part1_link" = "Redemander les clés de chiffrement"; +"event_formatter_rerequest_keys_part2" = " depuis vos autres appareils."; +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Demande envoyée"; +"rerequest_keys_alert_message" = "Veuillez lancer Riot sur un autre appareil qui peut déchiffrer le message pour qu'il puisse envoyer les clés sur cet appareil."; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index af7887b1d..8368d71c6 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -79,7 +79,7 @@ "auth_password_dont_match" = "De wachtwoorden komen niet overeen"; "auth_username_in_use" = "De gebruikersnaam is al in gebruik"; "auth_forgot_password" = "Wachtwoord vergeten?"; -"auth_use_server_options" = "Gebruik aangepaste server options (geavanceerd)"; +"auth_use_server_options" = "Gebruik alternatieve serverinstellingen (geavanceerd)"; "auth_email_validation_message" = "Bekijk je e-mail om verder te gaan met de registratie"; "auth_msisdn_validation_title" = "Verificatie Aanhangig"; "auth_msisdn_validation_message" = "We hebben een SMS met een activatie code gestuurd. Vul deze code hieronder in."; @@ -227,7 +227,7 @@ "room_event_action_cancel_upload" = "Uploaden annuleren"; "room_event_action_cancel_download" = "Downloaden annuleren"; "room_event_action_view_encryption" = "Versleutelingsinformatie"; -"room_warning_about_encryption" = "Eind-tot-eind versleuteling is nog in beta en kan onbetrouwbaar zijn.\n\nHet is beter om het nog niet met gevoelige gegevens te vertrouwen.\n\nApparaten kunnen nog niet de geschiedenis van voordat ze de ruimte betraden kunnen ontsleutelen.\n\nVersleutelde berichten zullen nog niet zichtbaar zijn op applicaties die geen versleuteling ondersteunen."; +"room_warning_about_encryption" = "End-to-endbeveiliging is in bèta en kan onbetrouwbaar zijn.\n\nHet is beter om het nog niet met gevoelige gegevens te vertrouwen.\n\nApparaten kunnen de geschiedenis van voordat ze de ruimte betraden nog niet ontsleutelen.\n\nVersleutelde berichten zullen nog niet zichtbaar zijn op programma's die geen versleuteling ondersteunen."; // Unknown devices "unknown_devices_alert_title" = "Ruimte bevat onbekende apparaten"; "unknown_devices_alert" = "Deze ruimte bevat onbekende apparaten die niet geen geverifieerd.\nDit betekent dat er geen garantie is dat de apparaten bij de gebruikers horen waar het beweert dat het bij hoort.\nWe raden je aan om bij elk apparaat door het verificatieprocces heen te gaan voordat je doorgaat, maar je kan het bericht opnieuw versturen zonder te verifiëren als je dat prefereert."; @@ -302,7 +302,7 @@ "settings_unignore_user" = "Alle berichten van %@ laten zien?"; "settings_contacts_discover_matrix_users" = "Gebruik e-mailadressen en telefoonnummers om gebruikers te vinden"; "settings_contacts_phonebook_country" = "Telefoonboek land"; -"settings_labs_e2e_encryption" = "Eind-tot-Eind versleuteling"; +"settings_labs_e2e_encryption" = "End-to-endbeveiliging"; "settings_labs_e2e_encryption_prompt_message" = "Om het opzetten van de versleuteling af te ronden moet je opnieuw inloggen."; "settings_version" = "Versie %@"; "settings_olm_version" = "Olm Versie %@"; @@ -365,8 +365,8 @@ "room_details_advanced_e2e_encryption_enabled" = "Versleuteling staat aan in deze ruimte"; "room_details_advanced_e2e_encryption_disabled" = "Versleuteling staat niet aan in deze ruimte."; "room_details_advanced_e2e_encryption_blacklist_unverified_devices" = "Alleen naar geverifieerde apparaten versleutelen"; -"room_details_advanced_e2e_encryption_prompt_message" = "Eind-tot-eind versleuteling is experimenteel en kan onbetrouwbaar zijn.\n\nHet is beter om het nog niet met gevoelige gegevens te vertrouwen.\n\nApparaten kunnen nog niet de geschiedenis van voordat ze de ruimte betraden kunnen ontsleutelen.\n\nZodra de versleuteling aan staat kan het niet meer worden uitgezet (voor nu).\n\nVersleutelde berichten zullen nog niet zichtbaar zijn op applicaties die geen versleuteling ondersteunen."; -"room_details_fail_to_update_avatar" = "Ruimte foto vernieuwen mislukt"; +"room_details_advanced_e2e_encryption_prompt_message" = "End-to-endbeveiliging is experimenteel en kan onbetrouwbaar zijn.\n\nHet is beter om het nog niet met gevoelige gegevens te vertrouwen.\n\nApparaten kunnen de geschiedenis van voordat ze de ruimte betraden nog niet ontsleutelen.\n\nZodra de versleuteling aan staat kan het (voorlopig) niet worden uitgezet.\n\nVersleutelde berichten zullen nog niet zichtbaar zijn op programma's die geen versleuteling ondersteunen."; +"room_details_fail_to_update_avatar" = "Ruimte-foto vernieuwen mislukt"; "room_details_fail_to_update_room_name" = "Ruimtenaam vernieuwen mislukt"; "room_details_fail_to_update_topic" = "Ruimteonderwerp vernieuwen mislukt"; "room_details_fail_to_update_room_guest_access" = "Ruimte's gast toegang vernieuwen mislukt"; @@ -420,7 +420,7 @@ "google_analytics_use_prompt" = "Wil je helpen met het verbeteren van %@ bij het automatisch rapporteren van crash rapporten en data van gebruik?"; // Crypto "e2e_enabling_on_app_update" = "Riot ondersteunt nu eind-tot-eind sleuteling maar je moet opnieuw inloggen om het aan te zetten.\n\nJe kan het nu of later doen vanaf de applicatie instellingen."; -"e2e_need_log_in_again" = "Je moet opnieuw inloggen om eind-tot-eind versleuteling sleutels te genereren voor dit apparaat en om de publieke sleutel naar de thuisserver te sturen.\nDit is eenmalig; excuses voor het ongemak."; +"e2e_need_log_in_again" = "Je moet opnieuw inloggen om end-to-endbeveligingssleutels te genereren voor dit apparaat en om de publieke sleutel naar de thuisserver te sturen.\nDit is eenmalig; excuses voor het ongemak."; // Bug report "bug_report_title" = "Foutmelding"; "bug_report_description" = "Beschrijf de foutmelding. Wat heb je gedaan? Wat verwachte je dat er zou gebeuren? Wat gebeurde er werkelijk?"; @@ -546,3 +546,26 @@ // GDPR "gdpr_consent_not_given_alert_message" = "Om de %@ thuisserver te blijven gebruiken moet je de algemeen voorwaarden lezen en daarmee akkoord gaan."; "gdpr_consent_not_given_alert_review_now_action" = "Nu doorlezen"; +"room_action_send_photo_or_video" = "Foto of video versturen"; +"room_action_send_sticker" = "Sticker versturen"; +"settings_deactivate_account" = "DEACTIVEER ACCOUNT"; +"deactivate_account_forget_messages_information_part2_emphasize" = "Waarschuwing"; +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Aanvraag verstuurd"; +"rerequest_keys_alert_message" = "Start Riot alstublieft op een ander apparaat dat het bericht kan ontsleutelen zodat deze de sleutels kan sturen naar dit apparaat."; +"settings_deactivate_my_account" = "Account deactiveren"; +"event_formatter_rerequest_keys_part1_link" = "Vraag beveiligingssleutels opnieuw aan"; +"event_formatter_rerequest_keys_part2" = " van je andere apparaten."; +"widget_sticker_picker_no_stickerpacks_alert" = "Je hebt momenteel geen stickerpakketten aan staan."; +"widget_sticker_picker_no_stickerpacks_alert_add_now" = "Wil je er nu een paar toevoegen?"; +"deactivate_account_title" = "Account deactiveren"; +"deactivate_account_informations_part1" = "Dit zal je account voorgoed onbruikbaar maken. Je zal niet meer in kunnen loggen en niemand anders zal met dezelfde gebruikers ID kunnen registreren. Dit zal er voor zorgen dat je account alle ruimtes verlaat waar het momenteel onderdeel van is en het verwijderd de accountgegevens van de identiteitsserver. "; +"deactivate_account_informations_part2_emphasize" = "Deze actie is onomkeerbaar."; +"deactivate_account_informations_part3" = "\n\nHet deactiveren van je account "; +"deactivate_account_informations_part4_emphasize" = "zal er niet standaard voor zorgen dat de berichten die je verzonden hebt vergeten worden. "; +"deactivate_account_informations_part5" = "Als je wilt dat wij de berichten vergeten, klikt alsjeblieft op het vakje hieronder.\n\nDe zichtbaarheid van berichten in Matrix is hetzelfde als in e-mail. Het vergeten van je berichten betekent dat berichten die je hebt verstuurd niet meer gedeeld worden met nieuwe of ongeregistreerde gebruikers, maar geregistreerde gebruikers die al toegang hebben tot deze berichten zullen alsnog toegang hebben tot hun eigen kopie van het bericht."; +"deactivate_account_forget_messages_information_part1" = "Vergeet alle berichten die ik heb verstuurd wanneer mijn account gedeactiveerd is ("; +"deactivate_account_forget_messages_information_part3" = ": dit zal er voor zorgen dat toekomstige gebruikers een incompleet beeld krijgen van gesprekken)"; +"deactivate_account_validate_action" = "Account deactiveren"; +"deactivate_account_password_alert_title" = "Account deactiveren"; +"deactivate_account_password_alert_message" = "Voer je wachtwoord in om verder te gaan"; diff --git a/Riot/Assets/pt_BR.lproj/InfoPlist.strings b/Riot/Assets/pt_BR.lproj/InfoPlist.strings new file mode 100644 index 000000000..da30fcefa --- /dev/null +++ b/Riot/Assets/pt_BR.lproj/InfoPlist.strings @@ -0,0 +1,5 @@ +// Permissions usage explanations +"NSCameraUsageDescription" = "A câmera será usada para tomar fotos e vídeos, e também para a realização de chamadas de vídeo."; +"NSPhotoLibraryUsageDescription" = "A galeria de fotos é usada para o envio de fotos e vídeos."; +"NSMicrophoneUsageDescription" = "O microfone é usado para gravar vídeos e fazer chamadas, tanto de áudio como de vídeo."; +"NSContactsUsageDescription" = "O cadernos de contatos é usado para fazer busca de usuárias/os no Riot a partir do e-mail ou do número de telefone."; diff --git a/Riot/Assets/pt_BR.lproj/Localizable.strings b/Riot/Assets/pt_BR.lproj/Localizable.strings new file mode 100644 index 000000000..767bb6cc3 --- /dev/null +++ b/Riot/Assets/pt_BR.lproj/Localizable.strings @@ -0,0 +1,52 @@ +/* New message from a specific person, not referencing a room */ +"MSG_FROM_USER" = "Mensagem de %@"; +/* New message from a specific person in a named room */ +"MSG_FROM_USER_IN_ROOM" = "%@ enviou mensagem em %@"; +/* New message from a specific person, not referencing a room. Content included. */ +"MSG_FROM_USER_WITH_CONTENT" = "%@: %@"; +/* New message from a specific person in a named room. Content included. */ +"MSG_FROM_USER_IN_ROOM_WITH_CONTENT" = "%@ em %@: %@"; +/* New action message from a specific person, not referencing a room. */ +"ACTION_FROM_USER" = "* %@ %@"; +/* New action message from a specific person in a named room. */ +"ACTION_FROM_USER_IN_ROOM" = "%@ : * %@ %@"; +/* New action message from a specific person, not referencing a room. */ +"IMAGE_FROM_USER" = "%@ enviou a você uma imagem %@"; +/* New action message from a specific person in a named room. */ +"IMAGE_FROM_USER_IN_ROOM" = "%@ enviou uma imagem %@ na sala %@"; +/* A single unread message in a room */ +"SINGLE_UNREAD_IN_ROOM" = "Você recebeu uma mensagem na sala %@"; +/* A single unread message */ +"SINGLE_UNREAD" = "Você recebeu uma mensagem"; +/* Multiple unread messages in a room */ +"UNREAD_IN_ROOM" = "%@ novas mensagens na sala %@"; +/* Multiple unread messages from a specific person, not referencing a room */ +"MSGS_FROM_USER" = "%@ novas mensagens de %@"; +/* Multiple unread messages from two people */ +"MSGS_FROM_TWO_USERS" = "%@ novas mensagens de %@ e %@"; +/* Multiple unread messages from three people */ +"MSGS_FROM_THREE_USERS" = "%@ novas mensagens de %@, %@ e %@"; +/* Multiple unread messages from two plus people (ie. for 4+ people: 'others' replaces the third person) */ +"MSGS_FROM_TWO_PLUS_USERS" = "%@ novas mensagens de %@, %@ e outras/os"; +/* Multiple messages in two rooms */ +"MSGS_IN_TWO_ROOMS" = "%@ novas mensagens nas salas %@ e %@"; +/* Look, stuff's happened, alright? Just open the app. */ +"MSGS_IN_TWO_PLUS_ROOMS" = "%@ novas mensagens nas salas %@ e %@, dentre outras"; +/* A user has invited you to a chat */ +"USER_INVITE_TO_CHAT" = "%@ convidou você para conversar"; +/* A user has invited you to an (unamed) group chat */ +"USER_INVITE_TO_CHAT_GROUP_CHAT" = "%@ convidou você para uma conversa em grupo"; +/* A user has invited you to a named room */ +"USER_INVITE_TO_NAMED_ROOM" = "%@ convidou você para a sala %@"; +/* Incoming one-to-one voice call */ +"VOICE_CALL_FROM_USER" = "Chamada de %@"; +/* Incoming one-to-one video call */ +"VIDEO_CALL_FROM_USER" = "Chamada de vídeo de %@"; +/* Incoming unnamed voice conference invite from a specific person */ +"VOICE_CONF_FROM_USER" = "Chamada coletiva de %@"; +/* Incoming unnamed video conference invite from a specific person */ +"VIDEO_CONF_FROM_USER" = "Vídeo-chamada coletiva de %@"; +/* Incoming named voice conference invite from a specific person */ +"VOICE_CONF_NAMED_FROM_USER" = "Vídeo chamada coletiva de %@: '%@'"; +/* Incoming named video conference invite from a specific person */ +"VIDEO_CONF_NAMED_FROM_USER" = "Vídeo-chamada coletiva de %@: '%@'"; diff --git a/Riot/Assets/pt_BR.lproj/Vector.strings b/Riot/Assets/pt_BR.lproj/Vector.strings new file mode 100644 index 000000000..d45422cc9 --- /dev/null +++ b/Riot/Assets/pt_BR.lproj/Vector.strings @@ -0,0 +1,156 @@ +// Titles +"title_home" = "Início"; +"title_favourites" = "Favoritos"; +"title_people" = "Pessoas"; +"title_rooms" = "Salas"; +"title_groups" = "Comunidades"; +"warning" = "Atenção"; +// Actions +"view" = "Ver"; +"next" = "Próximo"; +"back" = "Voltar"; +"continue" = "Continuar"; +"create" = "Criar"; +"start" = "Iniciar"; +"leave" = "Sair"; +"remove" = "Remover"; +"invite" = "Convidar"; +"retry" = "Tentar novamente"; +"on" = "Ativado"; +"off" = "Desativado"; +"cancel" = "Cancelar"; +"save" = "Salvar"; +"join" = "Entrar"; +"decline" = "Recusar"; +"accept" = "Aceitar"; +"preview" = "Pré-visualização"; +"camera" = "Câmera"; +"voice" = "Voz"; +"video" = "Vídeo"; +"active_call" = "Chamada em andamento"; +"active_call_details" = "Chamada em andamento (%@)"; +"later" = "Mais tarde"; +"rename" = "Renomear"; +"collapse" = "reduzir"; +"send_to" = "Enviar para %@"; +"sending" = "Enviando"; +// Authentication +"auth_login" = "Fazer login"; +"auth_register" = "Criar nova conta"; +"auth_submit" = "Enviar"; +"auth_skip" = "Ignorar"; +"auth_send_reset_email" = "Enviar e-mail de redefinição de senha"; +"auth_return_to_login" = "Voltar à tela de login"; +"auth_user_id_placeholder" = "E-mail ou nome de usuária/o"; +"auth_password_placeholder" = "Senha"; +"auth_new_password_placeholder" = "Nova senha"; +"auth_user_name_placeholder" = "Nome de usuária/o"; +"auth_optional_email_placeholder" = "Endereço de e-mail (opcional)"; +"auth_email_placeholder" = "Endereço de e-mail"; +"auth_optional_phone_placeholder" = "Número de telefone (opcional)"; +"auth_phone_placeholder" = "Número de telefone"; +"auth_repeat_password_placeholder" = "Repita a senha"; +"auth_repeat_new_password_placeholder" = "Confirme sua nova senha"; +"auth_home_server_placeholder" = "URL (p.ex. https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (p.ex. https://matrix.org)"; +"auth_invalid_login_param" = "Nome de usuária/o e/ou senha incorretos"; +"auth_invalid_user_name" = "Nomes de usuária/o podem apenas conter letras, números, pontos, hífen e linha de sublinhar"; +"auth_invalid_password" = "Senha muito curta (mín. 6 letras)"; +"auth_invalid_email" = "O endereço de e-mail não parece válido"; +"auth_invalid_phone" = "O número de telefone não parece válido"; +"auth_missing_password" = "Falta a senha"; +"auth_add_email_message" = "Adicione um endereço de e-mail para a sua conta para permitir que outras pessoas possam te achar, e também para que você possa redefinir sua senha."; +"auth_add_phone_message" = "Adicione um número de telefone à sua conta para permitir que outras pessoas te encontrem."; +"auth_add_email_phone_message" = "Adicione um endereço de e-mail e/ou um telefone à sua conta para permitir que outras pessoas encontrem você. O endereço de e-mail também permite que você redefina a sua senha."; +"auth_add_email_and_phone_message" = "Adicione um endereço de e-mail e um telefone à sua conta para permitir que outras pessoas encontrem você. O endereço de e-mail também permite que você redefina a sua senha."; +"auth_missing_email" = "Falta o endereço de e-mail"; +"auth_missing_phone" = "Falta o número de telefone"; +"auth_missing_email_or_phone" = "Falta o endereço de e-mail ou o número de telefone"; +"auth_email_in_use" = "Este endereço de e-mail já está sendo usado por outra pessoa"; +"auth_phone_in_use" = "Este número de telefone já está sendo usado por outra pessoa"; +"auth_untrusted_id_server" = "Este servidor de identidades não é confiável"; +"auth_password_dont_match" = "As senhas não são iguais"; +"auth_username_in_use" = "Este nome de usuária/o já está sendo usado por outra pessoa"; +"auth_forgot_password" = "Esqueceu sua senha?"; +"auth_email_not_found" = "Não consegui enviar e-mail: este endereço de e-mail não foi encontrado"; +"auth_use_server_options" = "Usar opções personalizadas de servidor (avançado)"; +"auth_email_validation_message" = "Por favor, verifique o seu e-mail para continuar seu cadastro"; +"auth_msisdn_validation_title" = "Faltando verificação"; +"auth_msisdn_validation_message" = "Nós enviamos um SMS com um código de ativação. Por favor, coloque abaixo este código."; +"auth_msisdn_validation_error" = "Não foi possível verificar o número de telefone."; +"auth_recaptcha_message" = "Este Servidor de Base (Home Server) quer ter certeza que você não é um robô"; +"auth_reset_password_message" = "Para redefinir sua senha, coloque o endereço de e-mail relacionado à sua conta:"; +"auth_reset_password_missing_email" = "O endereço de e-mail relacionado à sua conta tem que ser inserido."; +"auth_reset_password_missing_password" = "Uma nova senha tem que ser inserida."; +"auth_reset_password_email_validation_message" = "Um e-mail foi enviado para %@. Quando você clicar no link do e-mail, clique abaixo."; +"auth_reset_password_next_step_button" = "Eu verifiquei meu endereço de e-mail"; +"auth_reset_password_error_unauthorized" = "Não foi possível verificar o endereço de e-mail: confirme que realmente clicou no link contido na mensagem de e-mail"; +"auth_reset_password_error_not_found" = "O seu endereço de e-mail não parece estar associado a um ID Matrix neste Servidor de Base (Home Server)."; +"auth_reset_password_success_message" = "Sua senha foi redefinida.\n\nVocê foi deslogado de todos os dispositivos e não receberá notificações. Para reativar as notificações, faça login novamente em cada um dos dispositivos."; +"auth_add_email_and_phone_warning" = "Registro com email e número de telefone ainda não é suportado enquanto existir a API. Apenas o número de telefone será associado a sua conta. Você pode adicionar seu email ao seu perfil nas configurações."; +"room_creation_appearance" = "Aparência"; +"room_creation_appearance_name" = "Nome"; +"room_creation_appearance_picture" = "Imagem da Sala (opcional)"; +"room_creation_privacy" = "Privacidade"; +"room_creation_private_room" = "Esta conversa é privada"; +"room_creation_public_room" = "Esta conversa é pública"; +"room_creation_make_public" = "Tornar público"; +"room_creation_make_public_prompt_title" = "Tornar esta conversa pública?"; +"room_creation_make_public_prompt_msg" = "Você tem certeza que deseja tornar esta conversa pública? Qualquer um poderá ler suas mensagens e entrar na conversa."; +"room_creation_keep_private" = "Manter privada"; +"room_creation_make_private" = "Manter privada"; +"room_creation_wait_for_creation" = "A sala já está sendo criada. Por favor aguarde."; +"room_creation_invite_another_user" = "Procurar / convidar por ID da usuária/o, Nome ou email"; +"room_recents_directory_section_network" = "Rede"; +"room_recents_favourites_section" = "FAVORITOS"; +"room_recents_people_section" = "PESSOAS"; +"room_recents_conversations_section" = "SALAS"; +"room_recents_no_conversation" = "Nenhuma sala"; +"room_recents_low_priority_section" = "BAIXA PRIORIDADE"; +"room_recents_invites_section" = "CONVITES"; +"room_recents_start_chat_with" = "Iniciar conversa"; +"room_recents_create_empty_room" = "Criar sala"; +"room_recents_join_room" = "Entrar na sala"; +"room_recents_join_room_title" = "Entrar na sala"; +"room_recents_join_room_prompt" = "Digite o id da sala ou o nome da sala"; +// People tab +"people_invites_section" = "CONVITES"; +"people_conversation_section" = "CONVERSAS"; +"people_no_conversation" = "Nenhuma conversa"; +// Rooms tab +"room_directory_no_public_room" = "Não existem salas públicas disponíveis"; +// Groups tab +"group_invite_section" = "CONVITES"; +"group_section" = "Comunidades"; +// Search +"search_rooms" = "Salas"; +"search_messages" = "Mensagens"; +"search_people" = "Pessoas"; +"search_files" = "Arquivos"; +"search_default_placeholder" = "Procurar"; +"search_people_placeholder" = "Procurar por ID da/o usuária/o, Nome ou email"; +"search_no_result" = "Nenhum resultado"; +"search_in_progress" = "Procurando…"; +// Directory +"directory_cell_title" = "Procurar no diretório"; +"directory_cell_description" = "%tu salas"; +"directory_search_results" = "%tu resultados encontrados para %@"; +"directory_search_results_more_than" = ">%tu resultados encontrados para %@"; +"directory_searching_title" = "Procurando no diretório…"; +// Contacts +"contacts_address_book_section" = "CONTATOS LOCAIS"; +"contacts_address_book_matrix_users_toggle" = "Apenas usuários Matrix"; +"contacts_address_book_no_contact" = "Nenhum contato local"; +"contacts_address_book_permission_required" = "É preciso autorização para acessar contatos locais"; +"contacts_address_book_permission_denied" = "Você não permitiu o Riot acessar seus contatos locais"; +"contacts_user_directory_section" = "DIRETÓRIO DE USUÁRIOS"; +"contacts_user_directory_offline_section" = "DIRETÓRIO DE USUÁRIOS (sem conexão)"; +// Chat participants +"room_participants_title" = "Participantes"; +"room_participants_add_participant" = "Adicionar participantes"; +"room_participants_one_participant" = "1 participante"; +"room_participants_multi_participants" = "%d participantes"; +"room_participants_leave_prompt_title" = "Sair da sala"; +"room_participants_leave_prompt_msg" = "Você tem certeza que deseja sair da sala?"; +"room_participants_remove_prompt_title" = "Confirmação"; +"room_participants_remove_prompt_msg" = "Você tem certeza que deseja remover %@ desta conversa?"; diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index 28ca66b2a..1901456f0 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -539,3 +539,8 @@ "deactivate_account_password_alert_title" = "Деактивировать аккаунт"; "deactivate_account_password_alert_message" = "Чтобы продолжить, введите пароль"; "widget_sticker_picker_no_stickerpacks_alert" = "У вас пока нет включенных пакетов стикеров."; +"event_formatter_rerequest_keys_part1_link" = "Повторно запросить ключи шифрования"; +"event_formatter_rerequest_keys_part2" = " с других устройств."; +// Re-request confirmation dialog +"rerequest_keys_alert_title" = "Запрос отправлен"; +"rerequest_keys_alert_message" = "Запустите Riot на другом устройстве, которое может расшифровать сообщение, чтобы оно могло отправить ключи этому устройству."; diff --git a/Riot/Base.lproj/Main.storyboard b/Riot/Base.lproj/Main.storyboard index b53f424ad..383630d7a 100644 --- a/Riot/Base.lproj/Main.storyboard +++ b/Riot/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -51,7 +51,7 @@ - + @@ -247,7 +247,7 @@ - + @@ -608,7 +608,7 @@ - + diff --git a/Riot/Info.plist b/Riot/Info.plist index cbd7ee6ea..8377d7276 100644 --- a/Riot/Info.plist +++ b/Riot/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.6.17 + 0.6.18 CFBundleSignature ???? CFBundleVersion - 0.6.17 + 0.6.18 ITSAppUsesNonExemptEncryption ITSEncryptionExportComplianceCode diff --git a/Riot/Model/Room/RoomBubbleCellData.h b/Riot/Model/Room/RoomBubbleCellData.h index 5e4a876b9..7ce9162fc 100644 --- a/Riot/Model/Room/RoomBubbleCellData.h +++ b/Riot/Model/Room/RoomBubbleCellData.h @@ -34,10 +34,6 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag) */ @property(nonatomic) BOOL containsLastMessage; -/** - A Boolean value that determines whether some read receipts are currently displayed in this bubble. - */ -@property(nonatomic) BOOL hasReadReceipts; /** The event id of the current selected event inside the bubble. Default is nil. diff --git a/Riot/Model/Room/RoomBubbleCellData.m b/Riot/Model/Room/RoomBubbleCellData.m index bfea7fc3f..9671f88bf 100644 --- a/Riot/Model/Room/RoomBubbleCellData.m +++ b/Riot/Model/Room/RoomBubbleCellData.m @@ -48,12 +48,11 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; // Increase maximum number of components self.maxComponentCount = 20; - // Initialize receipts flag - _hasReadReceipts = NO; - - // Force the update of the text message to take into account the potential read receipts in the bubble display. - // Note: we don't update this attributed string here because the RoomBubbleCellData instances are created on a processing - // thread different from the UI thread. + // Initialize read receipts + self.readReceipts = [NSMutableDictionary dictionary]; + self.readReceipts[event.eventId] = [roomDataSource.room getEventReceipts:event.eventId sorted:YES]; + + // Reset attributedTextMessage to force reset MXKRoomCellData parameters self.attributedTextMessage = nil; } @@ -79,7 +78,6 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; [self refreshBubbleComponentsPosition]; } - shouldUpdateComponentsPosition = NO; } } @@ -163,9 +161,6 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; NSMutableAttributedString *currentAttributedTextMsg; - // Refresh the receipt flag during this process - _hasReadReceipts = NO; - NSInteger selectedComponentIndex = self.selectedComponentIndex; NSInteger lastMessageIndex = self.containsLastMessage ? self.mostRecentComponentIndex : NSNotFound; @@ -203,11 +198,10 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; // Init attributed string with the first text component currentAttributedTextMsg = [[NSMutableAttributedString alloc] initWithAttributedString:componentString]; } - - // Vertical whitespace is added in case of read receipts - if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) + + if (self.readReceipts[component.event.eventId].count) { - _hasReadReceipts = YES; + // Add vertical whitespace in case of read receipts [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; } @@ -246,10 +240,9 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; // Append attributed text [currentAttributedTextMsg appendAttributedString:componentString]; - // Add vertical whitespace in case of read receipts - if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) + if (self.readReceipts[component.event.eventId].count) { - _hasReadReceipts = YES; + // Add vertical whitespace in case of read receipts [currentAttributedTextMsg appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; } } @@ -262,14 +255,13 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; { // CAUTION: This method must be called on the main thread. - // Refresh the receipt flag during this process. - _hasReadReceipts = NO; - @synchronized(bubbleComponents) { // Check whether there is at least one component. if (bubbleComponents.count) { + BOOL hasReadReceipts = NO; + // Set position of the first component CGFloat positionY = (self.attachment == nil || self.attachment.type == MXKAttachmentTypeFile || self.attachment.type == MXKAttachmentTypeAudio) ? MXKROOMBUBBLECELLDATA_TEXTVIEW_DEFAULT_VERTICAL_INSET : 0; MXKRoomBubbleComponent *component; @@ -283,7 +275,7 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; if (component.attributedTextMessage) { - _hasReadReceipts = ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO] != nil); + hasReadReceipts = (self.readReceipts[component.event.eventId].count > 0); break; } } @@ -308,7 +300,7 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; } // Vertical whitespace is added in case of read receipts - if (_hasReadReceipts) + if (hasReadReceipts) { [attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; } @@ -348,9 +340,8 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; component.position = CGPointMake(0, positionY); // Add vertical whitespace in case of read receipts. - if ([roomDataSource.room getEventReceipts:component.event.eventId sorted:NO]) + if (self.readReceipts[component.event.eventId].count) { - _hasReadReceipts = YES; [attributedString appendAttributedString:[RoomBubbleCellData readReceiptVerticalWhitespace]]; } @@ -379,19 +370,6 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; } } -- (void)setHasReadReceipts:(BOOL)hasReadReceipts -{ - // Check whether there is something to do - if (_hasReadReceipts || hasReadReceipts) - { - // Update flag - _hasReadReceipts = hasReadReceipts; - - // Recompute the text message layout - self.attributedTextMessage = nil; - } -} - - (void)setSelectedEventId:(NSString *)selectedEventId { // Check whether there is something to do @@ -517,6 +495,9 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; return NO; } + // Update read receipts for this bubble + self.readReceipts[event.eventId] = [roomDataSource.room getEventReceipts:event.eventId sorted:YES]; + return [super addEvent:event andRoomState:roomState]; } diff --git a/Riot/Model/Room/RoomDataSource.m b/Riot/Model/Room/RoomDataSource.m index e6401e54d..436405d0d 100644 --- a/Riot/Model/Room/RoomDataSource.m +++ b/Riot/Model/Room/RoomDataSource.m @@ -96,24 +96,102 @@ - (void)didReceiveReceiptEvent:(MXEvent *)receiptEvent roomState:(MXRoomState *)roomState { - // Override this callback to force rendering of each cell with read receipts information. - @synchronized(bubbles) - { - for (RoomBubbleCellData *cellData in bubbles) + // Do the processing on the same processing queue as MXKRoomDataSource + dispatch_async(MXKRoomDataSource.processingQueue, ^{ + + // Remove the previous displayed read receipt for each user who sent a + // new read receipt. + // To implement it, we need to find the sender id of each new read receipt + // among the read receipts array of all events in all bubbles. + NSMutableArray *readReceiptSenders = [receiptEvent.readReceiptSenders mutableCopy]; + + @synchronized(bubbles) { - cellData.hasReadReceipts = NO; + NSMutableDictionary *> *updatedCellDataReadReceipts = [NSMutableDictionary dictionary]; + for (RoomBubbleCellData *cellData in bubbles) + { + for (NSString *eventId in cellData.readReceipts) + { + for (MXReceiptData *receiptData in cellData.readReceipts[eventId]) + { + NSMutableArray *foundSenders = [NSMutableArray array]; + for (NSString *senderId in readReceiptSenders) + { + if ([receiptData.userId isEqualToString:senderId]) + { + // We find an existing displayed receipt, remove it + [foundSenders addObject:senderId]; + + if (!updatedCellDataReadReceipts[eventId]) + { + updatedCellDataReadReceipts[eventId] = cellData.readReceipts[eventId]; + } + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"userId!=%@", receiptData.userId]; + updatedCellDataReadReceipts[eventId] = [updatedCellDataReadReceipts[eventId] filteredArrayUsingPredicate:predicate]; + break; + } + } + + // As there is one (the last) read receipt displayed per user, + // we do not need to search for other read receipts of found users. + [readReceiptSenders removeObjectsInArray:foundSenders]; + if (!readReceiptSenders.count) + { + // All senders have been found + break; + } + } + } + + // Flush found changed to the cell data + for (NSString *eventId in updatedCellDataReadReceipts) + { + if (updatedCellDataReadReceipts[eventId].count) + { + cellData.readReceipts[eventId] = updatedCellDataReadReceipts[eventId]; + } + else + { + cellData.readReceipts[eventId] = nil; + } + } + + if (!readReceiptSenders.count) + { + // All senders have been found + break; + } + } } - } - - NSArray *readEventIds = receiptEvent.readReceiptEventIds; - for (NSString* eventId in readEventIds) - { - RoomBubbleCellData *cellData = [self cellDataOfEventWithEventId:eventId]; - // Ignore the read receipts on the events without an actual display. - cellData.hasReadReceipts = !cellData.hasNoDisplay; - } - - [super didReceiveReceiptEvent:receiptEvent roomState:roomState]; + + // Update cell data we have received a read receipt for + NSArray *readEventIds = receiptEvent.readReceiptEventIds; + for (NSString* eventId in readEventIds) + { + RoomBubbleCellData *cellData = [self cellDataOfEventWithEventId:eventId]; + if (cellData) + { + @synchronized(bubbles) + { + if (!cellData.hasNoDisplay) + { + cellData.readReceipts[eventId] = [self.room getEventReceipts:eventId sorted:YES]; + } + else + { + // Ignore the read receipts on the events without an actual display. + cellData.readReceipts[eventId] = nil; + } + } + } + } + + dispatch_async(dispatch_get_main_queue(), ^{ + // TODO: Be smarter and update only updated cells + [super didReceiveReceiptEvent:receiptEvent roomState:roomState]; + }); + }); } #pragma mark - @@ -181,7 +259,7 @@ // Handle read receipts and read marker display. // Ignore the read receipts on the bubble without actual display. // Ignore the read receipts on collapsed bubbles - if ((self.showBubbleReceipts && cellData.hasReadReceipts && !isCollapsableCellCollapsed) || self.showReadMarker) + if ((self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed) || self.showReadMarker) { // Read receipts container are inserted here on the right side into the content view. // Some vertical whitespaces are added in message text view (see RoomBubbleCellData class) to insert correctly multiple receipts. @@ -194,10 +272,10 @@ if (component.event.sentState != MXEventSentStateFailed) { // Handle read receipts (if any) - if (self.showBubbleReceipts && cellData.hasReadReceipts && !isCollapsableCellCollapsed) + if (self.showBubbleReceipts && cellData.readReceipts.count && !isCollapsableCellCollapsed) { // Get the events receipts by ignoring the current user receipt. - NSArray* receipts = [self.room getEventReceipts:component.event.eventId sorted:YES]; + NSArray* receipts = cellData.readReceipts[component.event.eventId]; NSMutableArray *roomMembers; NSMutableArray *placeholders; diff --git a/Riot/Model/RoomList/RecentsDataSource.m b/Riot/Model/RoomList/RecentsDataSource.m index 83c725b59..86de39869 100644 --- a/Riot/Model/RoomList/RecentsDataSource.m +++ b/Riot/Model/RoomList/RecentsDataSource.m @@ -25,6 +25,8 @@ #import "AppDelegate.h" +#import "Riot-Swift.h" + #define RECENTSDATASOURCE_SECTION_DIRECTORY 0x01 #define RECENTSDATASOURCE_SECTION_INVITES 0x02 #define RECENTSDATASOURCE_SECTION_FAVORITES 0x04 @@ -1193,8 +1195,8 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou if (_recentsDataSourceMode == RecentsDataSourceModeHome) { - BOOL pinMissedNotif = [[NSUserDefaults standardUserDefaults] boolForKey:@"pinRoomsWithMissedNotif"]; - BOOL pinUnread = [[NSUserDefaults standardUserDefaults] boolForKey:@"pinRoomsWithUnread"]; + BOOL pinMissedNotif = RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome; + BOOL pinUnread = RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome; NSComparator comparator = nil; if (pinMissedNotif) diff --git a/Riot/Riot-Bridging-Header.h b/Riot/Riot-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/Riot/Riot-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/Riot/Utils/EventFormatter.h b/Riot/Utils/EventFormatter.h index ba5d6a60c..d55c03257 100644 --- a/Riot/Utils/EventFormatter.h +++ b/Riot/Utils/EventFormatter.h @@ -16,6 +16,16 @@ #import +/** + Link string used in attributed strings to mark a keys re-request action. + */ +FOUNDATION_EXPORT NSString *const kEventFormatterOnReRequestKeysLinkAction; + +/** + Parameters separator in the link string. + */ +FOUNDATION_EXPORT NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator; + /** `EventFormatter` class inherits from `MXKEventFormatter` to define Vector formatting */ diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index ef1732e49..bb35137ba 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -21,6 +21,14 @@ #import "WidgetManager.h" +#import "MXDecryptionResult.h" +#import "DecryptionFailureTracker.h" + +#pragma mark - Constants definitions + +NSString *const kEventFormatterOnReRequestKeysLinkAction = @"kEventFormatterOnReRequestKeysLinkAction"; +NSString *const kEventFormatterOnReRequestKeysLinkActionSeparator = @"/"; + @interface EventFormatter () { /** @@ -107,7 +115,47 @@ } } - return [super attributedStringFromEvent:event withRoomState:roomState error:error]; + NSAttributedString *attributedString = [super attributedStringFromEvent:event withRoomState:roomState error:error]; + + if (event.sentState == MXEventSentStateSent + && [event.decryptionError.domain isEqualToString:MXDecryptingErrorDomain]) + { + // Track e2e failures + dispatch_async(dispatch_get_main_queue(), ^{ + [[DecryptionFailureTracker sharedInstance] reportUnableToDecryptErrorForEvent:event withRoomState:roomState myUser:mxSession.myUser.userId]; + }); + + if (event.decryptionError.code == MXDecryptingErrorUnknownInboundSessionIdCode) + { + // Append to the displayed error an attibuted string with a tappable link + // so that the user can try to fix the UTD + NSMutableAttributedString *attributedStringWithRerequestMessage = [attributedString mutableCopy]; + [attributedStringWithRerequestMessage appendAttributedString:[[NSAttributedString alloc] initWithString:@"\n"]]; + + NSString *linkActionString = [NSString stringWithFormat:@"%@%@%@", kEventFormatterOnReRequestKeysLinkAction, + kEventFormatterOnReRequestKeysLinkActionSeparator, + event.eventId]; + + [attributedStringWithRerequestMessage appendAttributedString: + [[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"event_formatter_rerequest_keys_part1_link", @"Vector", nil) + attributes:@{ + NSLinkAttributeName: linkActionString, + NSForegroundColorAttributeName: self.sendingTextColor, + NSFontAttributeName: self.encryptedMessagesTextFont + }]]; + + [attributedStringWithRerequestMessage appendAttributedString: + [[NSAttributedString alloc] initWithString:NSLocalizedStringFromTable(@"event_formatter_rerequest_keys_part2", @"Vector", nil) + attributes:@{ + NSForegroundColorAttributeName: self.sendingTextColor, + NSFontAttributeName: self.encryptedMessagesTextFont + }]]; + + attributedString = attributedStringWithRerequestMessage; + } + } + + return attributedString; } - (NSAttributedString*)attributedStringFromEvents:(NSArray*)events withRoomState:(MXRoomState*)roomState error:(MXKEventFormatterError*)error diff --git a/Riot/Utils/RiotDesignValues.m b/Riot/Utils/RiotDesignValues.m index 1f062ceaf..1c381a36e 100644 --- a/Riot/Utils/RiotDesignValues.m +++ b/Riot/Utils/RiotDesignValues.m @@ -17,6 +17,13 @@ #import "RiotDesignValues.h" +#ifdef IS_SHARE_EXTENSION +#import "RiotShareExtension-Swift.h" +#else +#import "Riot-Swift.h" +#endif + + NSString *const kRiotDesignValuesDidChangeThemeNotification = @"kRiotDesignValuesDidChangeThemeNotification"; UIColor *kRiotPrimaryBgColor; @@ -134,7 +141,7 @@ UIKeyboardAppearance kRiotKeyboard; - (void)accessibilityInvertColorsStatusDidChange { // Refresh the theme only for "auto" - NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:@"userInterfaceTheme"]; + NSString *theme = RiotSettings.shared.userInterfaceTheme; if (!theme || [theme isEqualToString:@"auto"]) { [self userInterfaceThemeDidChange]; @@ -144,7 +151,7 @@ UIKeyboardAppearance kRiotKeyboard; - (void)userInterfaceThemeDidChange { // Retrieve the current selected theme ("light" if none. "auto" is used as default from iOS 11). - NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:@"userInterfaceTheme"]; + NSString *theme = RiotSettings.shared.userInterfaceTheme; if (!theme || [theme isEqualToString:@"auto"]) { diff --git a/Riot/Utils/RiotSettings.swift b/Riot/Utils/RiotSettings.swift new file mode 100644 index 000000000..e91b0ab15 --- /dev/null +++ b/Riot/Utils/RiotSettings.swift @@ -0,0 +1,115 @@ +/* + Copyright 2018 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +/// Store Riot specific app settings. +@objcMembers +final class RiotSettings: NSObject { + + // MARK: - Constants + + private enum UserDefaultsKeys { + static let enableCrashReport = "enableCrashReport" + static let enableRageShake = "enableRageShake" + static let createConferenceCallsWithJitsi = "createConferenceCallsWithJitsi" + static let userInterfaceTheme = "userInterfaceTheme" + static let notificationsShowDecryptedContent = "showDecryptedContent" + static let pinRoomsWithMissedNotifications = "pinRoomsWithMissedNotif" + static let pinRoomsWithUnreadMessages = "pinRoomsWithUnread" + } + + static let shared = RiotSettings() + + // MARK: - Public + + // MARK: Notifications + + /// Indicate if `showDecryptedContentInNotifications` settings has been set once. + var isShowDecryptedContentInNotificationsHasBeenSetOnce: Bool { + return UserDefaults.standard.object(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) != nil + } + + /// Indicate if encrypted messages content should be displayed in notifications. + var showDecryptedContentInNotifications: Bool { + get { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.notificationsShowDecryptedContent) + } set { + UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.notificationsShowDecryptedContent) + } + } + + /// Indicate if rooms with missed notifications should be displayed first on home screen. + var pinRoomsWithMissedNotificationsOnHome: Bool { + get { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.pinRoomsWithMissedNotifications) + } set { + UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.pinRoomsWithMissedNotifications) + } + } + + /// Indicate if rooms with unread messages should be displayed first on home screen. + var pinRoomsWithUnreadMessagesOnHome: Bool { + get { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.pinRoomsWithUnreadMessages) + } set { + UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.pinRoomsWithUnreadMessages) + } + } + + // MARK: User interface + + var userInterfaceTheme: String? { + get { + return UserDefaults.standard.string(forKey: UserDefaultsKeys.userInterfaceTheme) + } set { + UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.userInterfaceTheme) + } + } + + // MARK: Other + + /// Indicate if `enableCrashReport` settings has been set once. + var isEnableCrashReportHasBeenSetOnce: Bool { + return UserDefaults.standard.object(forKey: UserDefaultsKeys.enableCrashReport) != nil + } + + var enableCrashReport: Bool { + get { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableCrashReport) + } set { + UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableCrashReport) + } + } + + var enableRageShake: Bool { + get { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.enableRageShake) + } set { + UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.enableRageShake) + } + } + + // MARK: Labs + + var createConferenceCallsWithJitsi: Bool { + get { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.createConferenceCallsWithJitsi) + } set { + UserDefaults.standard.set(newValue, forKey: UserDefaultsKeys.createConferenceCallsWithJitsi) + } + } +} diff --git a/Riot/ViewController/AttachmentsViewController.m b/Riot/ViewController/AttachmentsViewController.m index 77a3afdb9..72f2dd5b0 100644 --- a/Riot/ViewController/AttachmentsViewController.m +++ b/Riot/ViewController/AttachmentsViewController.m @@ -73,7 +73,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"AttachmentsViewer"]; + [[Analytics sharedInstance] trackScreen:@"AttachmentsViewer"]; } - (void)destroy diff --git a/Riot/ViewController/AuthenticationViewController.m b/Riot/ViewController/AuthenticationViewController.m index 889a922df..47d6ffd64 100644 --- a/Riot/ViewController/AuthenticationViewController.m +++ b/Riot/ViewController/AuthenticationViewController.m @@ -193,7 +193,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"Authentication"]; + [[Analytics sharedInstance] trackScreen:@"Authentication"]; } - (void)viewDidAppear:(BOOL)animated diff --git a/Riot/ViewController/Communities/GroupDetailsViewController.m b/Riot/ViewController/Communities/GroupDetailsViewController.m index 95c2635ac..08bf19492 100644 --- a/Riot/ViewController/Communities/GroupDetailsViewController.m +++ b/Riot/ViewController/Communities/GroupDetailsViewController.m @@ -135,7 +135,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"GroupDetails"]; + [[Analytics sharedInstance] trackScreen:@"GroupDetails"]; } - (void)viewWillDisappear:(BOOL)animated diff --git a/Riot/ViewController/Communities/GroupHomeViewController.m b/Riot/ViewController/Communities/GroupHomeViewController.m index 983710c56..88ab6533b 100644 --- a/Riot/ViewController/Communities/GroupHomeViewController.m +++ b/Riot/ViewController/Communities/GroupHomeViewController.m @@ -182,7 +182,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"GroupDetailsHome"]; + [[Analytics sharedInstance] trackScreen:@"GroupDetailsHome"]; // Release the potential pushed view controller [self releasePushedViewController]; diff --git a/Riot/ViewController/Communities/GroupParticipantsViewController.m b/Riot/ViewController/Communities/GroupParticipantsViewController.m index 8b20b8369..ef7ba2360 100644 --- a/Riot/ViewController/Communities/GroupParticipantsViewController.m +++ b/Riot/ViewController/Communities/GroupParticipantsViewController.m @@ -217,7 +217,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"GroupDetailsPeople"]; + [[Analytics sharedInstance] trackScreen:@"GroupDetailsPeople"]; // Release the potential pushed view controller [self releasePushedViewController]; diff --git a/Riot/ViewController/Communities/GroupRoomsViewController.m b/Riot/ViewController/Communities/GroupRoomsViewController.m index 0a11ef080..9d2e0bfe1 100644 --- a/Riot/ViewController/Communities/GroupRoomsViewController.m +++ b/Riot/ViewController/Communities/GroupRoomsViewController.m @@ -181,7 +181,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"GroupDetailsRooms"]; + [[Analytics sharedInstance] trackScreen:@"GroupDetailsRooms"]; // Release the potential pushed view controller [self releasePushedViewController]; diff --git a/Riot/ViewController/Communities/GroupsViewController.m b/Riot/ViewController/Communities/GroupsViewController.m index cde207edb..a96ead33f 100644 --- a/Riot/ViewController/Communities/GroupsViewController.m +++ b/Riot/ViewController/Communities/GroupsViewController.m @@ -186,7 +186,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"Groups"]; + [[Analytics sharedInstance] trackScreen:@"Groups"]; // Deselect the current selected row, it will be restored on viewDidAppear (if any) NSIndexPath *indexPath = [self.groupsTableView indexPathForSelectedRow]; diff --git a/Riot/ViewController/ContactDetailsViewController.m b/Riot/ViewController/ContactDetailsViewController.m index 0bf37eeed..ed477c040 100644 --- a/Riot/ViewController/ContactDetailsViewController.m +++ b/Riot/ViewController/ContactDetailsViewController.m @@ -263,7 +263,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"ContactDetails"]; + [[Analytics sharedInstance] trackScreen:@"ContactDetails"]; // Hide the bottom border of the navigation bar to display the expander header [self hideNavigationBarBorder:YES]; diff --git a/Riot/ViewController/ContactsTableViewController.h b/Riot/ViewController/ContactsTableViewController.h index 521f8be81..a196bd788 100644 --- a/Riot/ViewController/ContactsTableViewController.h +++ b/Riot/ViewController/ContactsTableViewController.h @@ -73,7 +73,7 @@ @property (nonatomic) BOOL shouldScrollToTopOnRefresh; /** - The Google Analytics Instance screen name (Default is "ContactsTable"). + The analytics instance screen name (Default is "ContactsTable"). */ @property (nonatomic) NSString *screenName; diff --git a/Riot/ViewController/ContactsTableViewController.m b/Riot/ViewController/ContactsTableViewController.m index 842a208e1..c5f199fd5 100644 --- a/Riot/ViewController/ContactsTableViewController.m +++ b/Riot/ViewController/ContactsTableViewController.m @@ -144,7 +144,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:_screenName]; + [[Analytics sharedInstance] trackScreen:_screenName]; // Check whether the access to the local contacts has not been already asked. if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined) diff --git a/Riot/ViewController/CountryPickerViewController.m b/Riot/ViewController/CountryPickerViewController.m index 881295893..1c0a2fa40 100644 --- a/Riot/ViewController/CountryPickerViewController.m +++ b/Riot/ViewController/CountryPickerViewController.m @@ -99,7 +99,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"CountryPicker"]; + [[Analytics sharedInstance] trackScreen:@"CountryPicker"]; } - (void)destroy diff --git a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m index a8fe4b6ca..dfbd4f8ca 100644 --- a/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m +++ b/Riot/ViewController/DeactivateAccount/DeactivateAccountViewController.m @@ -96,7 +96,7 @@ static CGFloat const kTextFontSize = 15.0; [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"DeactivateAccount"]; + [[Analytics sharedInstance] trackScreen:@"DeactivateAccount"]; } - (void)viewDidLayoutSubviews diff --git a/Riot/ViewController/DirectoryServerPickerViewController.m b/Riot/ViewController/DirectoryServerPickerViewController.m index 22f33a7f2..a7450b622 100644 --- a/Riot/ViewController/DirectoryServerPickerViewController.m +++ b/Riot/ViewController/DirectoryServerPickerViewController.m @@ -143,7 +143,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"DirectoryServerPicker"]; + [[Analytics sharedInstance] trackScreen:@"DirectoryServerPicker"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { diff --git a/Riot/ViewController/DirectoryViewController.m b/Riot/ViewController/DirectoryViewController.m index 5bd12e113..90bb1bfd5 100644 --- a/Riot/ViewController/DirectoryViewController.m +++ b/Riot/ViewController/DirectoryViewController.m @@ -105,7 +105,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"Directory"]; + [[Analytics sharedInstance] trackScreen:@"Directory"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { diff --git a/Riot/ViewController/HomeFilesSearchViewController.m b/Riot/ViewController/HomeFilesSearchViewController.m index 83235f5bb..6cf51a715 100644 --- a/Riot/ViewController/HomeFilesSearchViewController.m +++ b/Riot/ViewController/HomeFilesSearchViewController.m @@ -107,7 +107,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"FilesGlobalSearch"]; + [[Analytics sharedInstance] trackScreen:@"FilesGlobalSearch"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionDidLeaveRoomNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionNewRoomNotification object:nil]; diff --git a/Riot/ViewController/HomeMessagesSearchViewController.m b/Riot/ViewController/HomeMessagesSearchViewController.m index ac1544db3..d239acbc6 100644 --- a/Riot/ViewController/HomeMessagesSearchViewController.m +++ b/Riot/ViewController/HomeMessagesSearchViewController.m @@ -114,7 +114,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"MessagesGlobalSearch"]; + [[Analytics sharedInstance] trackScreen:@"MessagesGlobalSearch"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionDidLeaveRoomNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshSearchResult:) name:kMXSessionNewRoomNotification object:nil]; diff --git a/Riot/ViewController/LanguagePickerViewController.m b/Riot/ViewController/LanguagePickerViewController.m index 0a64838e7..ef3ac56d8 100644 --- a/Riot/ViewController/LanguagePickerViewController.m +++ b/Riot/ViewController/LanguagePickerViewController.m @@ -109,7 +109,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"CountryPicker"]; + [[Analytics sharedInstance] trackScreen:@"CountryPicker"]; } - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath; diff --git a/Riot/ViewController/MasterTabBarController.m b/Riot/ViewController/MasterTabBarController.m index 6a1a6c667..25da2d2ab 100644 --- a/Riot/ViewController/MasterTabBarController.m +++ b/Riot/ViewController/MasterTabBarController.m @@ -26,6 +26,8 @@ #import "MXRoom+Riot.h" #import "MXSession+Riot.h" +#import "Riot-Swift.h" + @interface MasterTabBarController () { // Array of `MXSession` instances. @@ -77,6 +79,15 @@ _roomsViewController = [self.viewControllers objectAtIndex:TABBAR_ROOMS_INDEX]; _groupsViewController = [self.viewControllers objectAtIndex:TABBAR_GROUPS_INDEX]; + // Set the accessibility labels for all buttons #1842 + [_settingsBarButtonItem setAccessibilityLabel:NSLocalizedStringFromTable(@"settings_title", @"Vector", nil)]; + [_searchBarButtonIem setAccessibilityLabel:NSLocalizedStringFromTable(@"search_default_placeholder", @"Vector", nil)]; + [_homeViewController setAccessibilityLabel:NSLocalizedStringFromTable(@"title_home", @"Vector", nil)]; + [_favouritesViewController setAccessibilityLabel:NSLocalizedStringFromTable(@"title_favourites", @"Vector", nil)]; + [_peopleViewController setAccessibilityLabel:NSLocalizedStringFromTable(@"title_people", @"Vector", nil)]; + [_roomsViewController setAccessibilityLabel:NSLocalizedStringFromTable(@"title_rooms", @"Vector", nil)]; + [_groupsViewController setAccessibilityLabel:NSLocalizedStringFromTable(@"title_groups", @"Vector", nil)]; + // Sanity check NSAssert(_homeViewController && _favouritesViewController && _peopleViewController && _roomsViewController && _groupsViewController, @"Something wrong in Main.storyboard"); @@ -136,10 +147,10 @@ else { // Check whether the user has been already prompted to send crash reports. - // (Check whether 'enableCrashReport' flag has been set once) - if (![[NSUserDefaults standardUserDefaults] objectForKey:@"enableCrashReport"]) + // (Check whether 'enableCrashReport' flag has been set once) + if (!RiotSettings.shared.isEnableCrashReportHasBeenSetOnce) { - [self promptUserBeforeUsingGoogleAnalytics]; + [self promptUserBeforeUsingAnalytics]; } [self refreshTabBarBadges]; @@ -762,7 +773,7 @@ #pragma mark - -- (void)promptUserBeforeUsingGoogleAnalytics +- (void)promptUserBeforeUsingAnalytics { NSLog(@"[MasterTabBarController]: Invite the user to send crash reports"); @@ -778,8 +789,7 @@ style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"enableCrashReport"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + RiotSettings.shared.enableCrashReport = NO; if (weakSelf) { @@ -792,21 +802,20 @@ [currentAlert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) { - - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"enableCrashReport"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + + RiotSettings.shared.enableCrashReport = YES; if (weakSelf) { typeof(self) self = weakSelf; self->currentAlert = nil; } - - [[AppDelegate theDelegate] startAnalytics]; + + [[Analytics sharedInstance] start]; }]]; - [currentAlert mxk_setAccessibilityIdentifier: @"HomeVCUseGoogleAnalyticsAlert"]; + [currentAlert mxk_setAccessibilityIdentifier: @"HomeVCUseAnalyticsAlert"]; [self presentViewController:currentAlert animated:YES completion:nil]; } diff --git a/Riot/ViewController/MediaAlbumContentViewController.m b/Riot/ViewController/MediaAlbumContentViewController.m index 025a2e54f..4ed3f011f 100644 --- a/Riot/ViewController/MediaAlbumContentViewController.m +++ b/Riot/ViewController/MediaAlbumContentViewController.m @@ -157,7 +157,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"MediaAlbumContent"]; + [[Analytics sharedInstance] trackScreen:@"MediaAlbumContent"]; self.navigationItem.title = _assetsCollection.localizedTitle; diff --git a/Riot/ViewController/MediaPickerViewController.m b/Riot/ViewController/MediaPickerViewController.m index b81f5b34f..f0f30b847 100644 --- a/Riot/ViewController/MediaPickerViewController.m +++ b/Riot/ViewController/MediaPickerViewController.m @@ -236,7 +236,7 @@ static void *RecordingContext = &RecordingContext; [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"MediaPicker"]; + [[Analytics sharedInstance] trackScreen:@"MediaPicker"]; if (!userAlbumsQueue) { diff --git a/Riot/ViewController/RecentsViewController.h b/Riot/ViewController/RecentsViewController.h index 09117f31d..6fefeed34 100644 --- a/Riot/ViewController/RecentsViewController.h +++ b/Riot/ViewController/RecentsViewController.h @@ -79,7 +79,7 @@ @property (nonatomic) CGFloat stickyHeaderHeight; /** - The Google Analytics Instance screen name (Default is "RecentsScreen"). + The analytics instance screen name (Default is "RecentsScreen"). */ @property (nonatomic) NSString *screenName; diff --git a/Riot/ViewController/RecentsViewController.m b/Riot/ViewController/RecentsViewController.m index 24b8d97db..8bb715eaa 100644 --- a/Riot/ViewController/RecentsViewController.m +++ b/Riot/ViewController/RecentsViewController.m @@ -231,7 +231,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:_screenName]; + [[Analytics sharedInstance] trackScreen:_screenName]; // Deselect the current selected row, it will be restored on viewDidAppear (if any) NSIndexPath *indexPath = [self.recentsTableView indexPathForSelectedRow]; diff --git a/Riot/ViewController/RoomFilesSearchViewController.m b/Riot/ViewController/RoomFilesSearchViewController.m index 6973cde1c..a710dc8cb 100644 --- a/Riot/ViewController/RoomFilesSearchViewController.m +++ b/Riot/ViewController/RoomFilesSearchViewController.m @@ -108,7 +108,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomFilesSearch"]; + [[Analytics sharedInstance] trackScreen:@"RoomFilesSearch"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { diff --git a/Riot/ViewController/RoomMemberDetailsViewController.m b/Riot/ViewController/RoomMemberDetailsViewController.m index d7b9ecf94..f0c78e61f 100644 --- a/Riot/ViewController/RoomMemberDetailsViewController.m +++ b/Riot/ViewController/RoomMemberDetailsViewController.m @@ -251,7 +251,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomMemberDetails"]; + [[Analytics sharedInstance] trackScreen:@"RoomMemberDetails"]; // Hide the bottom border of the navigation bar to display the expander header [self hideNavigationBarBorder:YES]; diff --git a/Riot/ViewController/RoomMessagesSearchViewController.m b/Riot/ViewController/RoomMessagesSearchViewController.m index 7ac1b8031..f152865d4 100644 --- a/Riot/ViewController/RoomMessagesSearchViewController.m +++ b/Riot/ViewController/RoomMessagesSearchViewController.m @@ -109,7 +109,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomMessagesSearch"]; + [[Analytics sharedInstance] trackScreen:@"RoomMessagesSearch"]; // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { diff --git a/Riot/ViewController/RoomParticipantsViewController.m b/Riot/ViewController/RoomParticipantsViewController.m index 4b83342aa..c6d76ffeb 100644 --- a/Riot/ViewController/RoomParticipantsViewController.m +++ b/Riot/ViewController/RoomParticipantsViewController.m @@ -245,7 +245,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomParticipants"]; + [[Analytics sharedInstance] trackScreen:@"RoomParticipants"]; if (memberDetailsViewController) { diff --git a/Riot/ViewController/RoomSearchViewController.m b/Riot/ViewController/RoomSearchViewController.m index ba5367be1..19d50a700 100644 --- a/Riot/ViewController/RoomSearchViewController.m +++ b/Riot/ViewController/RoomSearchViewController.m @@ -101,7 +101,7 @@ } // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomsSearch"]; + [[Analytics sharedInstance] trackScreen:@"RoomsSearch"]; // Enable the search field by default at the screen opening if (self.searchBarHidden) diff --git a/Riot/ViewController/RoomSettingsViewController.m b/Riot/ViewController/RoomSettingsViewController.m index 279b6edd3..bfb2c105f 100644 --- a/Riot/ViewController/RoomSettingsViewController.m +++ b/Riot/ViewController/RoomSettingsViewController.m @@ -289,7 +289,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"RoomSettings"]; + [[Analytics sharedInstance] trackScreen:@"RoomSettings"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUpdateRules:) name:kMXNotificationCenterDidUpdateRules object:nil]; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 093f89e17..9b821132e 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -117,6 +117,10 @@ #import "WidgetPickerViewController.h" #import "StickerPickerViewController.h" +#import "EventFormatter.h" + +#import "Riot-Swift.h" + @interface RoomViewController () { // The expanded header @@ -181,6 +185,9 @@ // Observer kMXRoomSummaryDidChangeNotification to keep updated the missed discussion count id mxRoomSummaryDidChangeObserver; + + // Observer for removing the re-request explanation/waiting dialog + id mxEventDidDecryptNotificationObserver; // The table view cell in which the read marker is displayed (nil by default). MXKRoomBubbleTableViewCell *readMarkerTableViewCell; @@ -445,7 +452,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"ChatRoom"]; + [[Analytics sharedInstance] trackScreen:@"ChatRoom"]; // Refresh the room title view [self refreshRoomTitle]; @@ -592,6 +599,12 @@ [[NSNotificationCenter defaultCenter] removeObserver:mxRoomSummaryDidChangeObserver]; mxRoomSummaryDidChangeObserver = nil; } + + if (mxEventDidDecryptNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:mxEventDidDecryptNotificationObserver]; + mxEventDidDecryptNotificationObserver = nil; + } } - (void)viewDidLayoutSubviews @@ -1103,6 +1116,11 @@ [[NSNotificationCenter defaultCenter] removeObserver:mxRoomSummaryDidChangeObserver]; mxRoomSummaryDidChangeObserver = nil; } + if (mxEventDidDecryptNotificationObserver) + { + [[NSNotificationCenter defaultCenter] removeObserver:mxEventDidDecryptNotificationObserver]; + mxEventDidDecryptNotificationObserver = nil; + } [self removeCallNotificationsListeners]; [self removeWidgetNotificationsListeners]; @@ -2680,6 +2698,20 @@ NSString *fragment = [NSString stringWithFormat:@"/group/%@", [absoluteURLString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; [[AppDelegate theDelegate] handleUniversalLinkFragment:fragment]; } + else if ([absoluteURLString hasPrefix:kEventFormatterOnReRequestKeysLinkAction]) + { + NSArray *arguments = [absoluteURLString componentsSeparatedByString:kEventFormatterOnReRequestKeysLinkActionSeparator]; + if (arguments.count > 1) + { + NSString *eventId = arguments[1]; + MXEvent *event = [self.roomDataSource eventWithEventId:eventId]; + + if (event) + { + [self reRequestKeysAndShowExplanationAlert:event]; + } + } + } } return shouldDoAction; @@ -2982,7 +3014,7 @@ } // If enabled, create the conf using jitsi widget and open it directly - else if ([[NSUserDefaults standardUserDefaults] boolForKey:@"createConferenceCallsWithJitsi"] + else if (RiotSettings.shared.createConferenceCallsWithJitsi && self.roomDataSource.room.state.joinedMembers.count > 2) { [self startActivityIndicator]; @@ -4582,5 +4614,57 @@ [self presentViewController:currentAlert animated:YES completion:nil]; } +#pragma mark - Re-request encryption keys + +- (void)reRequestKeysAndShowExplanationAlert:(MXEvent*)event +{ + MXWeakify(self); + __block UIAlertController *alert; + + // Make the re-request + [self.mainSession.crypto reRequestRoomKeyForEvent:event]; + + // Observe kMXEventDidDecryptNotification to remove automatically the dialog + // if the user has shared the keys from another device + mxEventDidDecryptNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + MXStrongifyAndReturnIfNil(self); + + MXEvent *decryptedEvent = notif.object; + + if ([decryptedEvent.eventId isEqualToString:event.eventId]) + { + [[NSNotificationCenter defaultCenter] removeObserver:self->mxEventDidDecryptNotificationObserver]; + self->mxEventDidDecryptNotificationObserver = nil; + + if (self->currentAlert == alert) + { + [self->currentAlert dismissViewControllerAnimated:YES completion:nil]; + self->currentAlert = nil; + } + } + }]; + + // Show the explanation dialog + alert = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"rerequest_keys_alert_title", @"Vector", nil) + message:NSLocalizedStringFromTable(@"rerequest_keys_alert_message", @"Vector", nil) + preferredStyle:UIAlertControllerStyleAlert]; + currentAlert = alert; + + + [alert addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * action) + { + MXStrongifyAndReturnIfNil(self); + + [[NSNotificationCenter defaultCenter] removeObserver:self->mxEventDidDecryptNotificationObserver]; + self->mxEventDidDecryptNotificationObserver = nil; + + self->currentAlert = nil; + }]]; + + [self presentViewController:currentAlert animated:YES completion:nil]; +} + @end diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index 5c7e2d5d5..b94074471 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -46,6 +46,8 @@ #import "GBDeviceInfo_iOS.h" +#import "Riot-Swift.h" + NSString* const kSettingsViewControllerPhoneBookCountryCellId = @"kSettingsViewControllerPhoneBookCountryCellId"; enum @@ -423,7 +425,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"Settings"]; + [[Analytics sharedInstance] trackScreen:@"Settings"]; // Release the potential pushed view controller [self releasePushedViewController]; @@ -1681,7 +1683,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_show_decrypted_content", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = account.showDecryptedContentInNotifications; + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.showDecryptedContentInNotifications; labelAndSwitchCell.mxkSwitch.enabled = account.isPushKitNotificationActive; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleShowDecodedContent:) forControlEvents:UIControlEventTouchUpInside]; @@ -1705,7 +1707,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_pin_rooms_with_missed_notif", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"pinRoomsWithMissedNotif"]; + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePinRoomsWithMissedNotif:) forControlEvents:UIControlEventTouchUpInside]; @@ -1716,7 +1718,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_pin_rooms_with_unread", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"pinRoomsWithUnread"]; + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome; labelAndSwitchCell.mxkSwitch.enabled = YES; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(togglePinRoomsWithUnread:) forControlEvents:UIControlEventTouchUpInside]; @@ -1782,7 +1784,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; } - NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:@"userInterfaceTheme"]; + NSString *theme = RiotSettings.shared.userInterfaceTheme; + if (!theme) { if (@available(iOS 11.0, *)) @@ -1942,7 +1945,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); MXKTableViewCellWithLabelAndSwitch* sendCrashReportCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; sendCrashReportCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_send_crash_report", @"Vector", nil); - sendCrashReportCell.mxkSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]; + sendCrashReportCell.mxkSwitch.on = RiotSettings.shared.enableCrashReport; sendCrashReportCell.mxkSwitch.enabled = YES; [sendCrashReportCell.mxkSwitch addTarget:self action:@selector(toggleSendCrashReport:) forControlEvents:UIControlEventTouchUpInside]; @@ -1953,7 +1956,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); MXKTableViewCellWithLabelAndSwitch* enableRageShakeCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; enableRageShakeCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_enable_rageshake", @"Vector", nil); - enableRageShakeCell.mxkSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableRageShake"]; + enableRageShakeCell.mxkSwitch.on = RiotSettings.shared.enableRageShake; enableRageShakeCell.mxkSwitch.enabled = YES; [enableRageShakeCell.mxkSwitch addTarget:self action:@selector(toggleEnableRageShake:) forControlEvents:UIControlEventTouchUpInside]; @@ -2042,7 +2045,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; labelAndSwitchCell.mxkLabel.text = NSLocalizedStringFromTable(@"settings_labs_create_conference_with_jitsi", @"Vector", nil); - labelAndSwitchCell.mxkSwitch.on = [[NSUserDefaults standardUserDefaults] boolForKey:@"createConferenceCallsWithJitsi"]; + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.createConferenceCallsWithJitsi; [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleJitsiForConference:) forControlEvents:UIControlEventTouchUpInside]; @@ -2820,8 +2823,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); - (void)toggleShowDecodedContent:(id)sender { - MXKAccount* account = [MXKAccountManager sharedManager].activeAccounts.firstObject; - account.showDecryptedContentInNotifications = !account.showDecryptedContentInNotifications; + UISwitch *switchButton = (UISwitch*)sender; + RiotSettings.shared.showDecryptedContentInNotifications = switchButton.isOn; } - (void)toggleLocalContactsSync:(id)sender @@ -2847,25 +2850,25 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); - (void)toggleSendCrashReport:(id)sender { - BOOL enable = [[NSUserDefaults standardUserDefaults] boolForKey:@"enableCrashReport"]; + BOOL enable = RiotSettings.shared.enableCrashReport; if (enable) { - NSLog(@"[SettingsViewController] disable automatic crash report sending"); - [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"enableCrashReport"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + NSLog(@"[SettingsViewController] disable automatic crash report and analytics sending"); - [[AppDelegate theDelegate] stopAnalytics]; + RiotSettings.shared.enableCrashReport = NO; + + [[Analytics sharedInstance] stop]; // Remove potential crash file. [MXLogger deleteCrashLog]; } else { - NSLog(@"[SettingsViewController] enable automatic crash report sending"); - [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"enableCrashReport"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + NSLog(@"[SettingsViewController] enable automatic crash report and analytics sending"); - [[AppDelegate theDelegate] startAnalytics]; + RiotSettings.shared.enableCrashReport = YES; + + [[Analytics sharedInstance] start]; } } @@ -2875,8 +2878,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { UISwitch *switchButton = (UISwitch*)sender; - [[NSUserDefaults standardUserDefaults] setBool:switchButton.isOn forKey:@"enableRageShake"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + RiotSettings.shared.enableRageShake = switchButton.isOn; [self.tableView reloadData]; } @@ -2887,9 +2889,8 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); if (sender && [sender isKindOfClass:UISwitch.class]) { UISwitch *switchButton = (UISwitch*)sender; - - [[NSUserDefaults standardUserDefaults] setBool:switchButton.isOn forKey:@"createConferenceCallsWithJitsi"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + + RiotSettings.shared.createConferenceCallsWithJitsi = switchButton.isOn; [self.tableView reloadData]; } @@ -3012,16 +3013,14 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); { UISwitch *switchButton = (UISwitch*)sender; - [[NSUserDefaults standardUserDefaults] setBool:switchButton.on forKey:@"pinRoomsWithMissedNotif"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + RiotSettings.shared.pinRoomsWithMissedNotificationsOnHome = switchButton.on; } - (void)togglePinRoomsWithUnread:(id)sender { UISwitch *switchButton = (UISwitch*)sender; - - [[NSUserDefaults standardUserDefaults] setBool:switchButton.on forKey:@"pinRoomsWithUnread"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + + RiotSettings.shared.pinRoomsWithUnreadMessagesOnHome = switchButton.on; } - (void)toggleCommunityFlair:(id)sender @@ -3661,15 +3660,14 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); newTheme = @"black"; } - NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:@"userInterfaceTheme"]; + NSString *theme = RiotSettings.shared.userInterfaceTheme; if (newTheme && ![newTheme isEqualToString:theme]) { // Clear fake Riot Avatars based on the previous theme. [AvatarGenerator clear]; // The user wants to select this theme - [[NSUserDefaults standardUserDefaults] setObject:newTheme forKey:@"userInterfaceTheme"]; - [[NSUserDefaults standardUserDefaults] synchronize]; + RiotSettings.shared.userInterfaceTheme = newTheme; [self.tableView reloadData]; } diff --git a/Riot/ViewController/UnifiedSearchViewController.m b/Riot/ViewController/UnifiedSearchViewController.m index 79149cb4a..3d6145a67 100644 --- a/Riot/ViewController/UnifiedSearchViewController.m +++ b/Riot/ViewController/UnifiedSearchViewController.m @@ -137,7 +137,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"UnifiedSearch"]; + [[Analytics sharedInstance] trackScreen:@"UnifiedSearch"]; // Let's child display the loading not the home view controller if (self.activityIndicator) diff --git a/Riot/ViewController/UsersDevicesViewController.m b/Riot/ViewController/UsersDevicesViewController.m index 4241c60c4..b2847f6ac 100644 --- a/Riot/ViewController/UsersDevicesViewController.m +++ b/Riot/ViewController/UsersDevicesViewController.m @@ -116,7 +116,7 @@ [super viewWillAppear:animated]; // Screen tracking - [[AppDelegate theDelegate] trackScreen:@"UnknowDevices"]; + [[Analytics sharedInstance] trackScreen:@"UnknowDevices"]; [self.tableView reloadData]; } diff --git a/Riot/Views/RoomList/RecentTableViewCell.m b/Riot/Views/RoomList/RecentTableViewCell.m index b0bf69a75..48c15da08 100644 --- a/Riot/Views/RoomList/RecentTableViewCell.m +++ b/Riot/Views/RoomList/RecentTableViewCell.m @@ -25,6 +25,11 @@ #import "MXRoomSummary+Riot.h" +#pragma mark - Defines & Constants + +static const CGFloat kDirectRoomBorderColorAlpha = 0.75; +static const CGFloat kDirectRoomBorderWidth = 3.0; + @implementation RecentTableViewCell #pragma mark - Class methods @@ -48,10 +53,14 @@ self.missedNotifAndUnreadBadgeLabel.textColor = kRiotPrimaryBgColor; // Prepare direct room border + CGColorRef directRoomBorderColor = CGColorCreateCopyWithAlpha(kRiotColorGreen.CGColor, kDirectRoomBorderColorAlpha); + [self.directRoomBorderView.layer setCornerRadius:self.directRoomBorderView.frame.size.width / 2]; self.directRoomBorderView.clipsToBounds = YES; - self.directRoomBorderView.layer.borderColor = CGColorCreateCopyWithAlpha(kRiotColorGreen.CGColor, 0.75); - self.directRoomBorderView.layer.borderWidth = 3; + self.directRoomBorderView.layer.borderColor = directRoomBorderColor; + self.directRoomBorderView.layer.borderWidth = kDirectRoomBorderWidth; + + CFRelease(directRoomBorderColor); self.roomAvatar.defaultBackgroundColor = [UIColor clearColor]; } diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index e5492ac38..d1844a06b 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -24,6 +24,11 @@ #import "MXTools.h" +#pragma mark - Defines & Constants + +static const CGFloat kDirectRoomBorderColorAlpha = 0.75; +static const CGFloat kDirectRoomBorderWidth = 3.0; + @implementation RoomCollectionViewCell #pragma mark - Class methods @@ -66,10 +71,14 @@ self.missedNotifAndUnreadBadgeLabel.textColor = kRiotPrimaryBgColor; // Prepare direct room border + CGColorRef directRoomBorderColor = CGColorCreateCopyWithAlpha(kRiotColorGreen.CGColor, kDirectRoomBorderColorAlpha); + [self.directRoomBorderView.layer setCornerRadius:self.directRoomBorderView.frame.size.width / 2]; self.directRoomBorderView.clipsToBounds = YES; - self.directRoomBorderView.layer.borderColor = CGColorCreateCopyWithAlpha(kRiotColorGreen.CGColor, 0.75); - self.directRoomBorderView.layer.borderWidth = 3; + self.directRoomBorderView.layer.borderColor = directRoomBorderColor; + self.directRoomBorderView.layer.borderWidth = kDirectRoomBorderWidth; + + CFRelease(directRoomBorderColor); self.editionArrowView.backgroundColor = kRiotSecondaryBgColor; diff --git a/Riot/Views/RoomList/RoomTableViewCell.m b/Riot/Views/RoomList/RoomTableViewCell.m index 4f33d2821..b4d53ca5b 100644 --- a/Riot/Views/RoomList/RoomTableViewCell.m +++ b/Riot/Views/RoomList/RoomTableViewCell.m @@ -20,6 +20,11 @@ #import "RiotDesignValues.h" #import "MXRoomSummary+Riot.h" +#pragma mark - Defines & Constants + +static const CGFloat kDirectRoomBorderColorAlpha = 0.75; +static const CGFloat kDirectRoomBorderWidth = 3.0; + @implementation RoomTableViewCell #pragma mark - Class methods @@ -31,10 +36,14 @@ self.titleLabel.textColor = kRiotPrimaryTextColor; // Prepare direct room border + CGColorRef directRoomBorderColor = CGColorCreateCopyWithAlpha(kRiotColorGreen.CGColor, kDirectRoomBorderColorAlpha); + [self.directRoomBorderView.layer setCornerRadius:self.directRoomBorderView.frame.size.width / 2]; self.directRoomBorderView.clipsToBounds = YES; - self.directRoomBorderView.layer.borderColor = CGColorCreateCopyWithAlpha(kRiotColorGreen.CGColor, 0.75); - self.directRoomBorderView.layer.borderWidth = 3; + self.directRoomBorderView.layer.borderColor = directRoomBorderColor; + self.directRoomBorderView.layer.borderWidth = kDirectRoomBorderWidth; + + CFRelease(directRoomBorderColor); self.avatarImageView.defaultBackgroundColor = [UIColor clearColor]; } diff --git a/Riot/third_party_licenses.html b/Riot/third_party_licenses.html index 13163b0d5..b1f0e155b 100644 --- a/Riot/third_party_licenses.html +++ b/Riot/third_party_licenses.html @@ -114,19 +114,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-
  • - GoogleAnalytics (https://www.google.com/analytics) -

    Measure your app performance. -

    Copyright (c) 2011-2016 Google Inc. All rights reserved. -

    Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License at: -

    https://www.apache.org/licenses/LICENSE-2.0 -

    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -

    Google Analytics Terms of Service: https://www.google.com/analytics/terms -

    -
  • GZIP (https://github.com/nicklockwood/GZIP) diff --git a/RiotShareExtension/Info.plist b/RiotShareExtension/Info.plist index 733c119fa..fd320e7e7 100644 --- a/RiotShareExtension/Info.plist +++ b/RiotShareExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.6.17 + 0.6.18 CFBundleVersion 1 NSExtension diff --git a/RiotShareExtension/RiotShareExtension-Bridging-Header.h b/RiotShareExtension/RiotShareExtension-Bridging-Header.h new file mode 100644 index 000000000..1b2cb5d6d --- /dev/null +++ b/RiotShareExtension/RiotShareExtension-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/SiriIntents/Info.plist b/SiriIntents/Info.plist index b9785df49..ab1b6d34b 100644 --- a/SiriIntents/Info.plist +++ b/SiriIntents/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.6.17 + 0.6.18 CFBundleVersion 1 NSExtension