diff --git a/AUTHORS.rst b/AUTHORS.rst index 5a02bcaf8..e3dfda228 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -26,4 +26,7 @@ Denis Morozov Aram Sargsyan * PR #1341: Read receipts details + +Vivian Lim + * PR #1513 Return key on hardware keyboards now sends messages \ No newline at end of file diff --git a/Podfile b/Podfile index 39eeec830..d40551828 100644 --- a/Podfile +++ b/Podfile @@ -22,7 +22,7 @@ pod 'MatrixKit', '0.6.2' #pod 'MatrixKit', :path => '../matrix-ios-kit/MatrixKit.podspec' #pod 'MatrixSDK', :path => '../matrix-ios-sdk/MatrixSDK.podspec' -pod 'GBDeviceInfo', '~> 4.3.0' +pod 'GBDeviceInfo', '~> 4.4.0' pod 'GoogleAnalytics' @@ -32,7 +32,7 @@ pod 'WebRTC', '58.17.16937' # OLMKit for crypto pod 'OLMKit' #pod 'OLMKit', :path => '../olm/OLMKit.podspec' -pod 'Realm', '~> 2.8.1' +pod 'Realm', '~> 2.10.2' # Remove warnings from "bad" pods pod 'OLMKit', :inhibit_warnings => true @@ -50,7 +50,7 @@ pod 'WebRTC', '58.17.16937' # OLMKit for crypto pod 'OLMKit' #pod 'OLMKit', :path => '../olm/OLMKit.podspec' -pod 'Realm', '~> 2.8.1' +pod 'Realm', '~> 2.10.2' # The tagged version on which this version of Riot share extension has been built pod 'MatrixKit/AppExtension', '0.6.2' diff --git a/Podfile.lock b/Podfile.lock index 7264c3ab8..971069601 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -15,18 +15,18 @@ PODS: - AFNetworking/UIKit (3.1.0): - AFNetworking/NSURLSession - cmark (0.24.1) - - DTCoreText (1.6.20): - - DTCoreText/Core (= 1.6.20) + - DTCoreText (1.6.21): + - DTCoreText/Core (= 1.6.21) - DTFoundation/Core (~> 1.7.5) - DTFoundation/DTAnimatedGIF (~> 1.7.5) - DTFoundation/DTHTMLParser (~> 1.7.5) - DTFoundation/UIKit (~> 1.7.5) - - DTCoreText/Core (1.6.20): + - DTCoreText/Core (1.6.21): - DTFoundation/Core (~> 1.7.5) - DTFoundation/DTAnimatedGIF (~> 1.7.5) - DTFoundation/DTHTMLParser (~> 1.7.5) - DTFoundation/UIKit (~> 1.7.5) - - DTCoreText/Extension (1.6.20): + - DTCoreText/Extension (1.6.21): - DTFoundation/Core (~> 1.7.5) - DTFoundation/DTAnimatedGIF (~> 1.7.5) - DTFoundation/DTHTMLParser (~> 1.7.5) @@ -91,7 +91,7 @@ DEPENDENCIES: SPEC CHECKSUMS: AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 cmark: ec0275215b504780287b6fca360224e384368af8 - DTCoreText: 51904f2374af443e0d270d6fdc76035ab6f9ef8a + DTCoreText: e5d688cffc9f6a61eddd1a4f94e2046851230de3 DTFoundation: 26a164ef510877387906fb92bff524a792db4bf8 GBDeviceInfo: caae36532afcc209b51ac62bba547aadab9e88f2 GoogleAnalytics: f42cc53a87a51fe94334821868d9c8481ff47a7b diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 8efb31570..9c42d7209 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -10,13 +10,15 @@ 05D592A32FF1D1877B89F73C /* libPods-Riot.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD9D0BDE9232898950554DD5 /* libPods-Riot.a */; }; 2435179C1F375B9400D0683E /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 2466B7551F2F80B800AE27B0 /* Info.plist */; }; 2435179F1F375C0F00D0683E /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 327382C01F276AED00356143 /* Vector.strings */; }; + 2439DD621F6BBE760090F42D /* RecentRoomTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 2439DD611F6BBE760090F42D /* RecentRoomTableViewCell.m */; }; + 2439DD641F6BBEA50090F42D /* RecentRoomTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2439DD631F6BBEA50090F42D /* RecentRoomTableViewCell.xib */; }; 245FC3ED1F3D079A00603C6A /* ShareExtensionManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 245FC3EB1F3CAF9800603C6A /* ShareExtensionManager.m */; }; 245FC3EF1F3DD30800603C6A /* RecentCellData.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC071E7009EC00A9B29C /* RecentCellData.m */; }; 2466B73E1F2DFAC100AE27B0 /* animatedLogo-4.png in Resources */ = {isa = PBXBuildFile; fileRef = F083BB201E7009EC00A9B29C /* animatedLogo-4.png */; }; 24B5103E1EFA7083004C6AD2 /* ReadReceiptsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 24B5103D1EFA7083004C6AD2 /* ReadReceiptsViewController.m */; }; 24B510401EFA88CC004C6AD2 /* ReadReceiptsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 24B5103F1EFA88CC004C6AD2 /* ReadReceiptsViewController.xib */; }; 24CBEC591F0EAD310093EABB /* RiotShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 24CBEC4E1F0EAD310093EABB /* RiotShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 24D6B3581F3C90D300FC7A71 /* ShareRecentsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 24D6B3571F3C90D300FC7A71 /* ShareRecentsDataSource.m */; }; + 24D6B3581F3C90D300FC7A71 /* ShareDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 24D6B3571F3C90D300FC7A71 /* ShareDataSource.m */; }; 24D6B3591F3CA02900FC7A71 /* SharePresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 24D6B34B1F3C8F8A00FC7A71 /* SharePresentingViewController.m */; }; 24D6B35A1F3CA02C00FC7A71 /* ShareViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 24D6B34D1F3C8F8A00FC7A71 /* ShareViewController.m */; }; 24D6B35B1F3CA03300FC7A71 /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 24D6B34E1F3C8F8A00FC7A71 /* ShareViewController.xib */; }; @@ -27,7 +29,7 @@ 24EEE5A11F23A09A00B3C705 /* RiotDesignValues.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC171E7009EC00A9B29C /* RiotDesignValues.m */; }; 24EEE5A21F23A8B400B3C705 /* MXRoom+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BBE81E7009EC00A9B29C /* MXRoom+Riot.m */; }; 24EEE5A31F23A8C300B3C705 /* AvatarGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BC111E7009EC00A9B29C /* AvatarGenerator.m */; }; - 24EEE5A41F24C06E00B3C705 /* (null) in Resources */ = {isa = PBXBuildFile; }; + 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */ = {isa = PBXBuildFile; }; 24EEE5A81F25529600B3C705 /* cancel@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A121EDEE65000F5DC9A /* cancel@3x.png */; }; 24EEE5A91F25529900B3C705 /* cancel@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A111EDEE65000F5DC9A /* cancel@2x.png */; }; 24EEE5AA1F25529C00B3C705 /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = F0614A101EDEE65000F5DC9A /* cancel.png */; }; @@ -73,6 +75,7 @@ 32AE61F21F0D2183007255F4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32AE61EC1F0D2183007255F4 /* InfoPlist.strings */; }; 32AE61F31F0D2183007255F4 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32AE61EE1F0D2183007255F4 /* Localizable.strings */; }; 32AE61F41F0D2183007255F4 /* Vector.strings in Resources */ = {isa = PBXBuildFile; fileRef = 32AE61F01F0D2183007255F4 /* Vector.strings */; }; + 32C2356F1F7B871800E38FC5 /* WidgetPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32C2356E1F7B871800E38FC5 /* WidgetPickerViewController.m */; }; 32D392181EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 32D392161EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.m */; }; 32D392191EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32D392171EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.xib */; }; 32E84FA11F6BD32700CA0B89 /* apps-icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 32E84F9E1F6BD32700CA0B89 /* apps-icon.png */; }; @@ -81,6 +84,7 @@ 32F3AE1A1F6FF4E600F0F004 /* WidgetViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32F3AE191F6FF4E600F0F004 /* WidgetViewController.m */; }; 32FD0A3D1EB0CD9B0072B066 /* BugReportViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FD0A3B1EB0CD9B0072B066 /* BugReportViewController.m */; }; 32FD0A3E1EB0CD9B0072B066 /* BugReportViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 32FD0A3C1EB0CD9B0072B066 /* BugReportViewController.xib */; }; + 83711A7C1F6F8E7D008F0D4D /* KeyboardGrowingTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 83711A7B1F6F8E7D008F0D4D /* KeyboardGrowingTextView.m */; }; 92324BE31F4F66D3009DE194 /* IncomingCallView.m in Sources */ = {isa = PBXBuildFile; fileRef = 92324BE21F4F66D3009DE194 /* IncomingCallView.m */; }; 92324BE61F4F6A60009DE194 /* CircleButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 92324BE51F4F6A60009DE194 /* CircleButton.m */; }; A27ECCE3FC4971745D2CB78D /* libPods-RiotShareExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7246451C668D6782166E22EC /* libPods-RiotShareExtension.a */; }; @@ -490,6 +494,10 @@ F0A4A1671EF7CB66003630DB /* members_list_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = F0A4A1641EF7CB66003630DB /* members_list_icon.png */; }; F0A4A1681EF7CB66003630DB /* members_list_icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0A4A1651EF7CB66003630DB /* members_list_icon@2x.png */; }; F0A4A1691EF7CB66003630DB /* members_list_icon@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0A4A1661EF7CB66003630DB /* members_list_icon@3x.png */; }; + F0A8955F1F7D1FEA00BD6C2A /* MXRoomSummary+Riot.m in Sources */ = {isa = PBXBuildFile; fileRef = F0D2ADA01F6AA5FD00A7097D /* MXRoomSummary+Riot.m */; }; + F0A895601F7D404B00BD6C2A /* e2e_verified.png in Resources */ = {isa = PBXBuildFile; fileRef = F083BB681E7009EC00A9B29C /* e2e_verified.png */; }; + F0A895611F7D404E00BD6C2A /* e2e_verified@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F083BB691E7009EC00A9B29C /* e2e_verified@2x.png */; }; + F0A895621F7D405000BD6C2A /* e2e_verified@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F083BB6A1E7009EC00A9B29C /* e2e_verified@3x.png */; }; F0B4CBA51F418D0B008E99C5 /* WebViewViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0B4CBA41F418D0B008E99C5 /* WebViewViewController.m */; }; F0B4CBA71F41CA44008E99C5 /* DeviceView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F0B4CBA61F41CA44008E99C5 /* DeviceView.xib */; }; F0B4CBAA1F41E71A008E99C5 /* RiotNavigationController.m in Sources */ = {isa = PBXBuildFile; fileRef = F0B4CBA91F41E71A008E99C5 /* RiotNavigationController.m */; }; @@ -578,6 +586,9 @@ /* Begin PBXFileReference section */ 12AA0005C8B3D8D8162584C5 /* Pods-RiotShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RiotShareExtension/Pods-RiotShareExtension.debug.xcconfig"; sourceTree = ""; }; + 2439DD601F6BBE760090F42D /* RecentRoomTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RecentRoomTableViewCell.h; sourceTree = ""; }; + 2439DD611F6BBE760090F42D /* RecentRoomTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RecentRoomTableViewCell.m; sourceTree = ""; }; + 2439DD631F6BBEA50090F42D /* RecentRoomTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RecentRoomTableViewCell.xib; sourceTree = ""; }; 245FC3EA1F3CAF9800603C6A /* ShareExtensionManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShareExtensionManager.h; sourceTree = ""; }; 245FC3EB1F3CAF9800603C6A /* ShareExtensionManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareExtensionManager.m; sourceTree = ""; }; 2466B7551F2F80B800AE27B0 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RiotShareExtension/Info.plist; sourceTree = SOURCE_ROOT; }; @@ -596,8 +607,8 @@ 24D6B34C1F3C8F8A00FC7A71 /* ShareViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShareViewController.h; sourceTree = ""; }; 24D6B34D1F3C8F8A00FC7A71 /* ShareViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareViewController.m; sourceTree = ""; }; 24D6B34E1F3C8F8A00FC7A71 /* ShareViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareViewController.xib; sourceTree = ""; }; - 24D6B3561F3C90D300FC7A71 /* ShareRecentsDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShareRecentsDataSource.h; sourceTree = ""; }; - 24D6B3571F3C90D300FC7A71 /* ShareRecentsDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareRecentsDataSource.m; sourceTree = ""; }; + 24D6B3561F3C90D300FC7A71 /* ShareDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShareDataSource.h; sourceTree = ""; }; + 24D6B3571F3C90D300FC7A71 /* ShareDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareDataSource.m; sourceTree = ""; }; 3205ED7B1E976C8A003D65FA /* DirectoryServerPickerViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryServerPickerViewController.h; sourceTree = ""; }; 3205ED7C1E976C8A003D65FA /* DirectoryServerPickerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DirectoryServerPickerViewController.m; sourceTree = ""; }; 3205ED811E97725E003D65FA /* DirectoryServerTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryServerTableViewCell.h; sourceTree = ""; }; @@ -649,6 +660,8 @@ 32AE61ED1F0D2183007255F4 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = InfoPlist.strings; sourceTree = ""; }; 32AE61EF1F0D2183007255F4 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = Localizable.strings; sourceTree = ""; }; 32AE61F11F0D2183007255F4 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = Vector.strings; sourceTree = ""; }; + 32C2356D1F7B871800E38FC5 /* WidgetPickerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WidgetPickerViewController.h; sourceTree = ""; }; + 32C2356E1F7B871800E38FC5 /* WidgetPickerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WidgetPickerViewController.m; sourceTree = ""; }; 32D392151EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DirectoryServerDetailTableViewCell.h; sourceTree = ""; }; 32D392161EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DirectoryServerDetailTableViewCell.m; sourceTree = ""; }; 32D392171EB9B7AB009A2BAF /* DirectoryServerDetailTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DirectoryServerDetailTableViewCell.xib; sourceTree = ""; }; @@ -662,6 +675,7 @@ 32FD0A3C1EB0CD9B0072B066 /* BugReportViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = BugReportViewController.xib; sourceTree = ""; }; 7246451C668D6782166E22EC /* libPods-RiotShareExtension.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RiotShareExtension.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 765F5104DB3EC39713DEB3A4 /* Pods-RiotShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RiotShareExtension.release.xcconfig"; path = "Pods/Target Support Files/Pods-RiotShareExtension/Pods-RiotShareExtension.release.xcconfig"; sourceTree = ""; }; + 83711A7B1F6F8E7D008F0D4D /* KeyboardGrowingTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = KeyboardGrowingTextView.m; sourceTree = ""; }; 839BB91240D350D5607D55BA /* Pods-Riot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Riot.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Riot/Pods-Riot.debug.xcconfig"; sourceTree = ""; }; 92324BE11F4F66D3009DE194 /* IncomingCallView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IncomingCallView.h; sourceTree = ""; }; 92324BE21F4F66D3009DE194 /* IncomingCallView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = IncomingCallView.m; sourceTree = ""; }; @@ -1275,12 +1289,24 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2439DD5F1F6BBE390090F42D /* Views */ = { + isa = PBXGroup; + children = ( + 2439DD601F6BBE760090F42D /* RecentRoomTableViewCell.h */, + 2439DD611F6BBE760090F42D /* RecentRoomTableViewCell.m */, + 2439DD631F6BBEA50090F42D /* RecentRoomTableViewCell.xib */, + ); + name = Views; + path = RiotShareExtension/Views; + sourceTree = SOURCE_ROOT; + }; 24CBEC4F1F0EAD310093EABB /* RiotShareExtension */ = { isa = PBXGroup; children = ( 2466B7551F2F80B800AE27B0 /* Info.plist */, 2466B7561F2F80B800AE27B0 /* RiotShareExtension.entitlements */, 24D6B3441F3C8F8A00FC7A71 /* ViewController */, + 2439DD5F1F6BBE390090F42D /* Views */, 24D6B3551F3C8FCC00FC7A71 /* Model */, ); name = RiotShareExtension; @@ -1311,8 +1337,8 @@ children = ( 245FC3EA1F3CAF9800603C6A /* ShareExtensionManager.h */, 245FC3EB1F3CAF9800603C6A /* ShareExtensionManager.m */, - 24D6B3561F3C90D300FC7A71 /* ShareRecentsDataSource.h */, - 24D6B3571F3C90D300FC7A71 /* ShareRecentsDataSource.m */, + 24D6B3561F3C90D300FC7A71 /* ShareDataSource.h */, + 24D6B3571F3C90D300FC7A71 /* ShareDataSource.m */, ); name = Model; path = RiotShareExtension/Model; @@ -1349,6 +1375,8 @@ 3233F72E1F31F4BF006ACA81 /* JitsiViewController.h */, 3233F72F1F31F4BF006ACA81 /* JitsiViewController.m */, 3233F7301F31F4BF006ACA81 /* JitsiViewController.xib */, + 32C2356D1F7B871800E38FC5 /* WidgetPickerViewController.h */, + 32C2356E1F7B871800E38FC5 /* WidgetPickerViewController.m */, ); path = Widgets; sourceTree = ""; @@ -2201,6 +2229,7 @@ F083BCD91E7009EC00A9B29C /* RoomInputToolbarView.h */, F083BCDA1E7009EC00A9B29C /* RoomInputToolbarView.m */, F083BCDB1E7009EC00A9B29C /* RoomInputToolbarView.xib */, + 83711A7B1F6F8E7D008F0D4D /* KeyboardGrowingTextView.m */, ); path = RoomInputToolbar; sourceTree = ""; @@ -2487,14 +2516,19 @@ 24EEE5B51F2607C500B3C705 /* SegmentedViewController.xib in Resources */, 24EEE5A91F25529900B3C705 /* cancel@2x.png in Resources */, F0B7A8B11F475783006E27D2 /* RoomsListViewController.xib in Resources */, + F0A895621F7D405000BD6C2A /* e2e_verified@3x.png in Resources */, 2435179C1F375B9400D0683E /* Info.plist in Resources */, 24EEE5A81F25529600B3C705 /* cancel@3x.png in Resources */, 2466B73E1F2DFAC100AE27B0 /* animatedLogo-4.png in Resources */, 2435179F1F375C0F00D0683E /* Vector.strings in Resources */, 24EEE5AF1F25F0F500B3C705 /* Images.xcassets in Resources */, + F0A895611F7D404E00BD6C2A /* e2e_verified@2x.png in Resources */, 24EEE5AA1F25529C00B3C705 /* cancel.png in Resources */, + F0A895601F7D404B00BD6C2A /* e2e_verified.png in Resources */, 24D6B35E1F3CA03E00FC7A71 /* FallbackViewController.xib in Resources */, - 24EEE5A41F24C06E00B3C705 /* (null) in Resources */, + 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */, + 2439DD641F6BBEA50090F42D /* RecentRoomTableViewCell.xib in Resources */, + 24EEE5A41F24C06E00B3C705 /* BuildFile in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3025,7 +3059,7 @@ buildActionMask = 2147483647; files = ( 24EEE5A01F23A08900B3C705 /* RoomTableViewCell.m in Sources */, - 24D6B3581F3C90D300FC7A71 /* ShareRecentsDataSource.m in Sources */, + 24D6B3581F3C90D300FC7A71 /* ShareDataSource.m in Sources */, 245FC3EF1F3DD30800603C6A /* RecentCellData.m in Sources */, 24D6B35C1F3CA03600FC7A71 /* RoomsListViewController.m in Sources */, 24D6B35A1F3CA02C00FC7A71 /* ShareViewController.m in Sources */, @@ -3034,6 +3068,8 @@ 24D6B35D1F3CA03A00FC7A71 /* FallbackViewController.m in Sources */, 24EEE5A11F23A09A00B3C705 /* RiotDesignValues.m in Sources */, 24EEE5A21F23A8B400B3C705 /* MXRoom+Riot.m in Sources */, + F0A8955F1F7D1FEA00BD6C2A /* MXRoomSummary+Riot.m in Sources */, + 2439DD621F6BBE760090F42D /* RecentRoomTableViewCell.m in Sources */, 245FC3ED1F3D079A00603C6A /* ShareExtensionManager.m in Sources */, 24EEE5B41F2607C000B3C705 /* SegmentedViewController.m in Sources */, ); @@ -3075,6 +3111,7 @@ 32471CDC1F1373A100BDF50A /* RoomMembershipCollapsedWithPaginationTitleBubbleCell.m in Sources */, F083BE2B1E7009ED00A9B29C /* AuthInputsView.m in Sources */, 321082B21F0E9F40002E0091 /* RoomMembershipCollapsedBubbleCell.m in Sources */, + 32C2356F1F7B871800E38FC5 /* WidgetPickerViewController.m in Sources */, F083BE661E7009ED00A9B29C /* RoomIncomingTextMsgWithPaginationTitleBubbleCell.m in Sources */, F083BE141E7009ED00A9B29C /* HomeViewController.m in Sources */, F083BDFB1E7009ED00A9B29C /* RoomSearchDataSource.m in Sources */, @@ -3101,6 +3138,7 @@ 3205ED7D1E976C8A003D65FA /* DirectoryServerPickerViewController.m in Sources */, F083BE7C1E7009ED00A9B29C /* DirectoryRecentTableViewCell.m in Sources */, F0E05A031E963103004B83FB /* RoomsViewController.m in Sources */, + 83711A7C1F6F8E7D008F0D4D /* KeyboardGrowingTextView.m in Sources */, F083BE801E7009ED00A9B29C /* PublicRoomTableViewCell.m in Sources */, 322806A01F0F64C4008C53D7 /* RoomMembershipExpandedBubbleCell.m in Sources */, F083BE031E7009ED00A9B29C /* EventFormatter.m in Sources */, diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 54f72cec1..8654776c4 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -516,6 +516,12 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN [self refreshLocalContacts]; _isAppForeground = YES; + + if (@available(iOS 11.0, *)) + { + // Riot has its own dark theme. Prevent iOS from applying its one + [[UIApplication sharedApplication] keyWindow].accessibilityIgnoresInvertColors = YES; + } [self handleLaunchAnimation]; } diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index 10240ccd3..6b8df2779 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -416,9 +416,18 @@ "auth_email_not_found" = "Fehler beim Senden der E-Mail: Die E-Mail-Adresse wurde nicht gefunden"; "settings_user_interface" = "BENUTZEROBERFLÄCHE"; "settings_ui_language" = "Sprache"; -"settings_ui_light_theme" = "Helles Design"; -"settings_ui_dark_theme" = "Dunkles Design"; // Events formatter "event_formatter_member_updates" = "%tu Änderungen der Mitgliedschaft"; "contacts_user_directory_section" = "NUTZER VERZEICHNIS"; "contacts_user_directory_offline_section" = "NUTZER VERZEICHNIS (offline)"; +"auth_home_server_placeholder" = "URL (z.B. https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (z.B. https://matrix.org)"; +"room_ongoing_conference_call_close" = "Schließen"; +"room_conference_call_no_power" = "Du brauchst die Berechtigung Konferenzgespräche in diesem Raum zu verwalten"; +"settings_labs_create_conference_with_jitsi" = "Erstelle Konferenzgespräche mit Jitsi"; +"call_already_displayed" = "Es existiert bereits ein Gespräch."; +"call_jitsi_error" = "Konferenzgespräch konnte nicht betreten werden."; +// Widget +"widget_no_power_to_manage" = "Du brauchst die Berechtigung um Widgets in diesem Raum zu verwalten"; +"widget_creation_failure" = "Widget-Erstellung fehlgeschlagen"; +"room_ongoing_conference_call_with_close" = "Laufendes Konferenzgespräch. Trete mit %@ oder %@ bei. %@ es."; diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 0691ddaa0..0ad6de8fe 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -106,7 +106,6 @@ "auth_reset_password_error_not_found" = "Your email address does not appear to be associated with a Matrix ID on this Homeserver."; "auth_reset_password_success_message" = "Your password has been reset.\n\nYou have been logged out of all devices and will no longer receive push notifications. To re-enable notifications, re-log in on each device."; "auth_add_email_and_phone_warning" = "Registration with email and phone number at once is not supported yet until the api exists. Only the phone number will be taken into account. You may add your email to your profile in settings."; -"auth_share_extension_prompt" = "Login in the main app to share content"; // Chat creation "room_creation_title" = "New Chat"; @@ -340,8 +339,12 @@ //"settings_call_invitations" = "Call invitations"; "settings_ui_language" = "Language"; -"settings_ui_light_theme" = "Light theme"; -"settings_ui_dark_theme" = "Dark theme"; +"settings_ui_theme" = "Theme"; +"settings_ui_theme_auto" = "Auto"; +"settings_ui_theme_light" = "Light"; +"settings_ui_theme_dark" = "Dark"; +"settings_ui_theme_picker_title" = "Select a theme"; +"settings_ui_theme_picker_message" = "\"Auto\" uses your device \"Invert Colours\" settings"; "settings_unignore_user" = "Show all messages from %@?"; @@ -456,6 +459,8 @@ // Events formatter "event_formatter_member_updates" = "%tu membership changes"; +"event_formatter_widget_added" = "%@ widget added by %@"; +"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 %@"; @@ -525,3 +530,7 @@ "widget_integration_missing_user_id" = "Missing user_id in request."; "widget_integration_room_not_visible" = "Room %@ is not visible."; +// Share extension +"share_extension_auth_prompt" = "Login in the main app to share content"; +"share_extension_failed_to_encrypt" = "Failed to send. Check in the main app the encryption settings for this room"; + diff --git a/Riot/Assets/es.lproj/Vector.strings b/Riot/Assets/es.lproj/Vector.strings index 20cb49b36..f6c67d3bc 100644 --- a/Riot/Assets/es.lproj/Vector.strings +++ b/Riot/Assets/es.lproj/Vector.strings @@ -2,3 +2,45 @@ "title_home" = "Inicio"; "title_favourites" = "Favoritos"; "title_people" = "Contactos"; +"title_rooms" = "Salas"; +"warning" = "Atención"; +// Actions +"view" = "Ver"; +"next" = "SIguiente"; +"back" = "Anterior"; +"continue" = "Continuar"; +"create" = "Crear"; +"start" = "Iniciar"; +"leave" = "Salir"; +"remove" = "Remover"; +"invite" = "Invitar"; +"retry" = "Re-intentar"; +"on" = "Activado"; +"off" = "Desactivado"; +"cancel" = "Cancelar"; +"save" = "Guardar"; +"join" = "Entrar"; +"decline" = "Rechazar"; +"accept" = "Aceptar"; +"preview" = "Vista previa"; +"camera" = "Cámara"; +"voice" = "Voz"; +"video" = "Vídeo"; +"active_call" = "Llamada activa"; +"active_call_details" = "Llamada activa (%@)"; +"later" = "Después"; +"rename" = "Renombrar"; +"collapse" = "colapsar"; +// Authentication +"auth_login" = "Ingresar"; +"auth_register" = "Registrar"; +"auth_submit" = "Enviar"; +"auth_skip" = "Omitir"; +"auth_send_reset_email" = "Enviar Email de restauración"; +"auth_return_to_login" = "Regresar a pantalla de ingreso"; +"auth_user_id_placeholder" = "Email o nombre de usuario"; +"auth_password_placeholder" = "Contraseña"; +"auth_new_password_placeholder" = "Nueva contraseña"; +"auth_user_name_placeholder" = "Nombre de usuario"; +"auth_optional_email_placeholder" = "Dirección de Email (opcional)"; +"auth_email_placeholder" = "Dirección Email"; diff --git a/Riot/Assets/eu.lproj/Vector.strings b/Riot/Assets/eu.lproj/Vector.strings index 17830aa3d..1eed34a84 100644 --- a/Riot/Assets/eu.lproj/Vector.strings +++ b/Riot/Assets/eu.lproj/Vector.strings @@ -422,3 +422,14 @@ "bug_report_progress_zipping" = "Egunkariak biltzen"; "bug_report_progress_uploading" = "Txostena igotzen"; "room_details_advanced_e2e_encryption_prompt_message" = "Muturretik muturrerako zifratzea esperimentala da eta agian ez dabil behar bezala.\n\nEz zenuke datuak babesteko erabili behar oraindik.\n\nGailuek ezin izango dute gelara elakrtu aurreko historiala deszifratu.\n\nBehin gela batean zifratzea aktibatuta ez dago gero desaktibatzerik (oraingoz).\n\nZifratutako mezuak ezin izango dira ikusi oraindik zifratzea onartzen ez duten bezeroetan."; +"auth_home_server_placeholder" = "URL (adib. https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (adib. https://matrix.org)"; +"room_ongoing_conference_call_with_close" = "Konferentzia deia abian. elkartu %@ edo %@ gisa. %@."; +"room_ongoing_conference_call_close" = "Itxi"; +"room_conference_call_no_power" = "Baimena behar duzu konferentzia deia kudeatzeko gela honetan"; +"settings_labs_create_conference_with_jitsi" = "Sortu konferentzia deia jitsi erabiliz"; +"call_already_displayed" = "Badago de bat abian."; +"call_jitsi_error" = "Hutsegitea konferentzia deia elkartzean."; +// Widget +"widget_no_power_to_manage" = "Baimena behar duzu trepetak kudeatzeko gela honetan"; +"widget_creation_failure" = "Trepetaren sorrerak huts egin du"; diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 7cf68651c..06bec44d4 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -417,8 +417,12 @@ "auth_phone_in_use" = "Ce numéro de téléphone est déjà utilisé"; "auth_email_not_found" = "Échec lors de l'envoi de l'e-mail : Cette adresse e-mail n'a pas pu être trouvée"; "settings_ui_language" = "Langue"; -"settings_ui_light_theme" = "Thème clair"; -"settings_ui_dark_theme" = "Thème sombre"; +"settings_ui_theme" = "Thème"; +"settings_ui_theme_auto" = "Auto"; +"settings_ui_theme_light" = "Clair"; +"settings_ui_theme_dark" = "Sombre"; +"settings_ui_theme_picker_title" = "Selectionnez un thème"; +"settings_ui_theme_picker_message" = "\"Auto\" utilise le paramètre \"Inverser les couleurs\" de votre appareil"; "collapse" = "réduire"; "auth_untrusted_id_server" = "Le serveur d'identité n'est pas fiable"; "settings_user_interface" = "INTERFACE UTILISATEUR"; diff --git a/Riot/Assets/nl.lproj/Vector.strings b/Riot/Assets/nl.lproj/Vector.strings index cb160fb24..6290d9b57 100644 --- a/Riot/Assets/nl.lproj/Vector.strings +++ b/Riot/Assets/nl.lproj/Vector.strings @@ -440,11 +440,20 @@ "contacts_user_directory_offline_section" = "GEBRUIKERSADRESBOEK (offline)"; "settings_user_interface" = "GEBRUIKERSINTERFACE"; "settings_ui_language" = "Taal"; -"settings_ui_light_theme" = "Lichte thema"; -"settings_ui_dark_theme" = "Donkere thema"; // Read Receipts "read_receipts_list" = "Leesbewijzen Lijst"; "receipt_status_read" = "Lees: "; // Events formatter "event_formatter_member_updates" = "%tu lidmaatschap aanpassingen"; "bug_report_send" = "Stuur"; +"auth_home_server_placeholder" = "URL (bv. https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (bv. https://matrix.org)"; +"room_ongoing_conference_call_with_close" = "Lopend vergadergesprek. Neem deel als %@ of %@. %@ het."; +"room_ongoing_conference_call_close" = "Sluiten"; +"room_conference_call_no_power" = "Je hebt permissie nodig om het vergadergesprek in deze ruimte te beheren"; +"settings_labs_create_conference_with_jitsi" = "Maak vergadergesprekken met jitsi"; +"call_already_displayed" = "Er is al een gesprek aan de gang."; +"call_jitsi_error" = "Het is niet gelukt om aan het vergadergesprek deel te nemen."; +// Widget +"widget_no_power_to_manage" = "Je hebt permissie nodig om widgets in deze ruimte te beheren"; +"widget_creation_failure" = "Het creëren van de widget is fout gegaan"; diff --git a/Riot/Assets/ru.lproj/Localizable.strings b/Riot/Assets/ru.lproj/Localizable.strings index 09bc3ceba..a93cb96f6 100644 --- a/Riot/Assets/ru.lproj/Localizable.strings +++ b/Riot/Assets/ru.lproj/Localizable.strings @@ -41,7 +41,7 @@ /* Incoming unnamed voice conference invite from a specific person */ "VOICE_CONF_FROM_USER" = "Групповой звонок от %@"; /* Incoming unnamed video conference invite from a specific person */ -"VIDEO_CONF_FROM_USER" = "Видеоконференция %@"; +"VIDEO_CONF_FROM_USER" = "Групповой видеозвонок от %@"; /* Incoming named voice conference invite from a specific person */ "VOICE_CONF_NAMED_FROM_USER" = "Групповой звонок от %@: '%@'"; /* Incoming named video conference invite from a specific person */ diff --git a/Riot/Assets/ru.lproj/Vector.strings b/Riot/Assets/ru.lproj/Vector.strings index 33e345610..b4d02c15f 100644 --- a/Riot/Assets/ru.lproj/Vector.strings +++ b/Riot/Assets/ru.lproj/Vector.strings @@ -262,8 +262,6 @@ "settings_global_settings_info" = "Глобальные настройки уведомлений доступны в вашем %@ веб-клиенте"; "settings_on_denied_notification" = "Уведомления для %@ запрещены, пожалуйста, разрешите их в настройках вашего устройства"; "settings_ui_language" = "Язык"; -"settings_ui_light_theme" = "Светлая тема"; -"settings_ui_dark_theme" = "Темная тема"; "settings_unignore_user" = "Показать все сообщения от %@?"; "settings_labs_e2e_encryption" = "Сквозное шифрование"; "settings_labs_e2e_encryption_prompt_message" = "Чтобы завершить настройку шифрования, вы должны войти в систему еще раз."; @@ -421,3 +419,15 @@ "settings_contacts_phonebook_country" = "Страна телефонной книги"; "no_voip" = "%@ вызывает вас, но %@ пока не поддерживает вызовы.\nМожно пропустить это уведомление и ответить на звонок с другого устройства, или вы можете отклонить его."; "room_participants_remove_third_party_invite_msg" = "Удалить приглашение стороннего сервера невозможно, пока не описан API"; +"auth_home_server_placeholder" = "URL (например https://matrix.org)"; +"auth_identity_server_placeholder" = "URL (например https://matrix.org)"; +"room_ongoing_conference_call_with_close" = "Текущий групповой звонок. Войдите как %@ или %@. %@."; +"room_ongoing_conference_call_close" = "Закрыть"; +"room_conference_call_no_power" = "Вам нужно разрешение на управление конференцией в этой комнате"; +"room_preview_unlinked_email_warning" = "Это приглашение было отправлено %@, и оно не связано с этой учетной записью. Возможно, вы пожелаете войти в систему с другой учетной записью или добавить это сообщение к вашей учетной записи."; +"settings_labs_create_conference_with_jitsi" = "Создать групповой вызов с jitsi"; +"call_already_displayed" = "Вызов уже установлен."; +"call_jitsi_error" = "Не удалось присоединиться к групповому вызову."; +// Widget +"widget_no_power_to_manage" = "Вам нужно разрешение на управление виджетами в этой комнате"; +"widget_creation_failure" = "Не удалось создать виджет"; diff --git a/Riot/Assets/zh_Hans.lproj/Vector.strings b/Riot/Assets/zh_Hans.lproj/Vector.strings index 304310339..27f99e6a9 100644 --- a/Riot/Assets/zh_Hans.lproj/Vector.strings +++ b/Riot/Assets/zh_Hans.lproj/Vector.strings @@ -421,3 +421,9 @@ "settings_ui_dark_theme" = "暗色主题"; // Events formatter "event_formatter_member_updates" = "%tu 的成员身份变化"; +"auth_home_server_placeholder" = "URL(例如 https://matrix.org)"; +"auth_identity_server_placeholder" = "URL(例如 https://matrix.org)"; +"contacts_user_directory_section" = "用户目录"; +"contacts_user_directory_offline_section" = "用户目录(离线)"; +"room_ongoing_conference_call_with_close" = "收到会议通话。以 %@ 或 %@.%@ 加入。"; +"room_ongoing_conference_call_close" = "关闭"; diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 792de6359..516f15635 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -46,25 +46,52 @@ { NSString *displayText; - // Prepare the display name of the sender - NSString *senderDisplayName = roomState ? [self senderDisplayNameForEvent:event withRoomState:roomState] : event.sender; + Widget *widget = [[Widget alloc] initWithWidgetEvent:event inMatrixSession:mxSession]; + if (widget) + { + // Prepare the display name of the sender + NSString *senderDisplayName = roomState ? [self senderDisplayNameForEvent:event withRoomState:roomState] : event.sender; - if ([event.content[@"type"] isEqualToString:kWidgetTypeJitsi]) - { - // This is an alive jitsi widget - displayText = [NSString stringWithFormat:NSLocalizedStringFromTable(@"event_formatter_jitsi_widget_added", @"Vector", nil), senderDisplayName]; - } - else if (event.content.count == 0) - { - // This is a closed widget - // Check if it corresponds to a jitsi widget by looking at other state events for - // this jitsi widget (widget id = event.stateKey). - for (MXEvent *widgetStateEvent in [roomState stateEventsWithType:kWidgetEventTypeString]) + if (widget.isActive) { - if ([widgetStateEvent.stateKey isEqualToString:event.stateKey] && [widgetStateEvent.content[@"type"] isEqualToString:kWidgetTypeJitsi]) + if ([widget.type isEqualToString:kWidgetTypeJitsi]) { - displayText = [NSString stringWithFormat:NSLocalizedStringFromTable(@"event_formatter_jitsi_widget_removed", @"Vector", nil), senderDisplayName]; - break; + // This is an alive jitsi widget + displayText = [NSString stringWithFormat:NSLocalizedStringFromTable(@"event_formatter_jitsi_widget_added", @"Vector", nil), senderDisplayName]; + } + else + { + displayText = [NSString stringWithFormat:NSLocalizedStringFromTable(@"event_formatter_widget_added", @"Vector", nil), + widget.name ? widget.name : widget.type, + senderDisplayName]; + } + } + else + { + // This is a closed widget + // Check if it corresponds to a jitsi widget by looking at other state events for + // this jitsi widget (widget id = event.stateKey). + for (MXEvent *widgetStateEvent in [roomState stateEventsWithType:kWidgetEventTypeString]) + { + if ([widgetStateEvent.stateKey isEqualToString:widget.widgetId]) + { + Widget *activeWidget = [[Widget alloc] initWithWidgetEvent:widgetStateEvent inMatrixSession:mxSession]; + if (activeWidget.isActive) + { + if ([activeWidget.type isEqualToString:kWidgetTypeJitsi]) + { + // This was a jitsi widget + displayText = [NSString stringWithFormat:NSLocalizedStringFromTable(@"event_formatter_jitsi_widget_removed", @"Vector", nil), senderDisplayName]; + } + else + { + displayText = [NSString stringWithFormat:NSLocalizedStringFromTable(@"event_formatter_widget_removed", @"Vector", nil), + activeWidget.name ? activeWidget.name : activeWidget.type, + senderDisplayName]; + } + break; + } + } } } } diff --git a/Riot/Utils/RiotDesignValues.m b/Riot/Utils/RiotDesignValues.m index cd0ee8756..d52782d32 100644 --- a/Riot/Utils/RiotDesignValues.m +++ b/Riot/Utils/RiotDesignValues.m @@ -112,8 +112,10 @@ UIColor *kRiotDesignSearchBarTintColor = nil; // Observe user interface theme change. [[NSUserDefaults standardUserDefaults] addObserver:[RiotDesignValues sharedInstance] forKeyPath:@"userInterfaceTheme" options:0 context:nil]; [[RiotDesignValues sharedInstance] userInterfaceThemeDidChange]; -} + // Observe "Invert Colours" settings changes (available since iOS 11) + [[NSNotificationCenter defaultCenter] addObserver:[RiotDesignValues sharedInstance] selector:@selector(accessibilityInvertColorsStatusDidChange) name:UIAccessibilityInvertColorsStatusDidChangeNotification object:nil]; +} - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { @@ -123,10 +125,25 @@ UIColor *kRiotDesignSearchBarTintColor = nil; } } +- (void)accessibilityInvertColorsStatusDidChange +{ + // Refresh the theme only for "auto" + NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:@"userInterfaceTheme"]; + if (!theme || [theme isEqualToString:@"auto"]) + { + [self userInterfaceThemeDidChange]; + } +} + - (void)userInterfaceThemeDidChange { - // Retrieve the current selected theme ("light" if none). + // Retrieve the current selected theme ("light" if none. "auto" is used as default from iOS 11). NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:@"userInterfaceTheme"]; + + if (!theme || [theme isEqualToString:@"auto"]) + { + theme = UIAccessibilityIsInvertColorsEnabled() ? @"dark" : @"light"; + } // Currently only 2 themes is supported if ([theme isEqualToString:@"dark"]) diff --git a/Riot/Utils/Widgets/Widget.m b/Riot/Utils/Widgets/Widget.m index 9fe5e12b1..93ddd73a5 100644 --- a/Riot/Utils/Widgets/Widget.m +++ b/Riot/Utils/Widgets/Widget.m @@ -41,22 +41,25 @@ MXJSONModelSetDictionary(_data, widgetEvent.content[@"data"]); // Format the url string with user data - _url = [_url stringByReplacingOccurrencesOfString:@"$matrix_user_id" withString:mxSession.myUser.userId]; - _url = [_url stringByReplacingOccurrencesOfString:@"$matrix_display_name" - withString:mxSession.myUser.displayname ? mxSession.myUser.displayname : mxSession.myUser.userId]; - _url = [_url stringByReplacingOccurrencesOfString:@"$matrix_avatar_url" - withString:mxSession.myUser.avatarUrl ? mxSession.myUser.avatarUrl : @""]; + if (_url) + { + _url = [_url stringByReplacingOccurrencesOfString:@"$matrix_user_id" withString:mxSession.myUser.userId]; + _url = [_url stringByReplacingOccurrencesOfString:@"$matrix_display_name" + withString:mxSession.myUser.displayname ? mxSession.myUser.displayname : mxSession.myUser.userId]; + _url = [_url stringByReplacingOccurrencesOfString:@"$matrix_avatar_url" + withString:mxSession.myUser.avatarUrl ? mxSession.myUser.avatarUrl : @""]; - // And their scalar token - NSString *scalarToken = [[WidgetManager sharedManager] scalarTokenForMXSession:mxSession]; - if (scalarToken) - { - _url = [_url stringByAppendingString:[NSString stringWithFormat:@"&scalar_token=%@", scalarToken]]; - } - else - { - // Some widget can live without scalar token (ex: Jitsi widget) - NSLog(@"[Widget] Note: There is no scalar token for %@", self); + // And their scalar token + NSString *scalarToken = [[WidgetManager sharedManager] scalarTokenForMXSession:mxSession]; + if (scalarToken) + { + _url = [_url stringByAppendingString:[NSString stringWithFormat:@"&scalar_token=%@", scalarToken]]; + } + else + { + // Some widget can live without scalar token (ex: Jitsi widget) + NSLog(@"[Widget] Note: There is no scalar token for %@", self); + } } } diff --git a/Riot/Utils/Widgets/WidgetManager.h b/Riot/Utils/Widgets/WidgetManager.h index 77b78340b..f717f96df 100644 --- a/Riot/Utils/Widgets/WidgetManager.h +++ b/Riot/Utils/Widgets/WidgetManager.h @@ -72,12 +72,20 @@ WidgetManagerErrorCode; /** List all active widgets of a given type in a room. - @param widgetType the types of widget to search. + @param widgetTypes the types of widget to search. Nil means all types. @param room the room to check. @return a list of widgets. */ - (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes inRoom:(MXRoom*)room; +/** + List all active widgets of a given type in a room, excluding some types. + + @param notWidgetTypes the types of widget to not consider. Nil means all types. + @param room the room to check. + @return a list of widgets. + */ +- (NSArray *)widgetsNotOfTypes:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room; /** Add a modular widget to a room. diff --git a/Riot/Utils/Widgets/WidgetManager.m b/Riot/Utils/Widgets/WidgetManager.m index 169ca5c1e..80a7de1a6 100644 --- a/Riot/Utils/Widgets/WidgetManager.m +++ b/Riot/Utils/Widgets/WidgetManager.m @@ -87,7 +87,17 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; return [self widgetsOfTypes:nil inRoom:room]; } -- (NSArray *)widgetsOfTypes:(NSArray *)widgetTypes inRoom:(MXRoom *)room +- (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes inRoom:(MXRoom*)room; +{ + return [self widgetsOfTypes:widgetTypes butNotTypesOf:nil inRoom:room]; +} + +- (NSArray *)widgetsNotOfTypes:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room +{ + return [self widgetsOfTypes:nil butNotTypesOf:notWidgetTypes inRoom:room]; +} + +- (NSArray *)widgetsOfTypes:(NSArray*)widgetTypes butNotTypesOf:(NSArray*)notWidgetTypes inRoom:(MXRoom*)room; { // Widget id -> widget NSMutableDictionary *widgets = [NSMutableDictionary dictionary]; @@ -118,14 +128,21 @@ NSString *const WidgetManagerErrorDomain = @"WidgetManagerErrorDomain"; for (MXEvent *widgetEvent in widgetEvents) { // Filter widget types if required - if (widgetTypes) + if (widgetTypes || notWidgetTypes) { NSString *widgetType; MXJSONModelSetString(widgetType, widgetEvent.content[@"type"]); - if (widgetType && NSNotFound == [widgetTypes indexOfObject:widgetType]) + if (widgetType) { - continue; + if (widgetTypes && NSNotFound == [widgetTypes indexOfObject:widgetType]) + { + continue; + } + if (notWidgetTypes && NSNotFound != [notWidgetTypes indexOfObject:widgetType]) + { + continue; + } } } diff --git a/Riot/ViewController/ContactDetailsViewController.m b/Riot/ViewController/ContactDetailsViewController.m index fa97fbe79..1b69d4a10 100644 --- a/Riot/ViewController/ContactDetailsViewController.m +++ b/Riot/ViewController/ContactDetailsViewController.m @@ -35,7 +35,7 @@ #define TABLEVIEW_SECTION_HEADER_HEIGHT 28 #define TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN 0.01f -@interface ContactDetailsViewController () +@interface ContactDetailsViewController () { RoomMemberTitleView* contactTitleView; MXKImageView *contactAvatar; @@ -132,19 +132,62 @@ actionsArray = [[NSMutableArray alloc] init]; directChatsArray = [[NSMutableArray alloc] init]; + contactTitleView = [RoomMemberTitleView roomMemberTitleView]; + contactTitleView.delegate = self; + contactAvatar = contactTitleView.memberAvatar; + contactAvatar.contentMode = UIViewContentModeScaleAspectFill; + contactAvatar.defaultBackgroundColor = [UIColor clearColor]; + + if (@available(iOS 11.0, *)) + { + // Define directly the navigation titleView with the custom title view instance. Do not use anymore a container. + self.navigationItem.titleView = contactTitleView; + } + else + { + self.navigationItem.titleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 600, 40)]; + + // Add the title view and define edge constraints + contactTitleView.translatesAutoresizingMaskIntoConstraints = NO; + [self.navigationItem.titleView addSubview:contactTitleView]; + + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:contactTitleView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.navigationItem.titleView + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:contactTitleView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.navigationItem.titleView + attribute:NSLayoutAttributeBottom + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:contactTitleView + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:self.navigationItem.titleView + attribute:NSLayoutAttributeLeading + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:contactTitleView + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:self.navigationItem.titleView + attribute:NSLayoutAttributeTrailing + multiplier:1.0f + constant:0.0f]; + [NSLayoutConstraint activateConstraints:@[topConstraint, bottomConstraint, leadingConstraint, trailingConstraint]]; + } + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; [tap setNumberOfTouchesRequired:1]; [tap setNumberOfTapsRequired:1]; [tap setDelegate:self]; [self.contactNameLabelMask addGestureRecognizer:tap]; self.contactNameLabelMask.userInteractionEnabled = YES; - - self.navigationItem.titleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 600, 40)]; - - contactTitleView = [RoomMemberTitleView roomMemberTitleView]; - contactAvatar = contactTitleView.memberAvatar; - contactAvatar.contentMode = UIViewContentModeScaleAspectFill; - contactAvatar.defaultBackgroundColor = [UIColor clearColor]; // Add tap to show the contact avatar in fullscreen tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; @@ -162,40 +205,6 @@ [tap setDelegate:self]; [self.contactAvatarMask addGestureRecognizer:tap]; self.contactAvatarMask.userInteractionEnabled = YES; - - // Add the title view and define edge constraints - contactTitleView.translatesAutoresizingMaskIntoConstraints = NO; - [self.navigationItem.titleView addSubview:contactTitleView]; - - NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:contactTitleView - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.navigationItem.titleView - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]; - NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:contactTitleView - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.navigationItem.titleView - attribute:NSLayoutAttributeBottom - multiplier:1.0f - constant:0.0f]; - NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:contactTitleView - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:self.navigationItem.titleView - attribute:NSLayoutAttributeLeading - multiplier:1.0f - constant:0.0f]; - NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:contactTitleView - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:self.navigationItem.titleView - attribute:NSLayoutAttributeTrailing - multiplier:1.0f - constant:0.0f]; - [NSLayoutConstraint activateConstraints:@[topConstraint, bottomConstraint, leadingConstraint, trailingConstraint]]; // Register collection view cell class [self.tableView registerClass:TableViewCellWithButton.class forCellReuseIdentifier:[TableViewCellWithButton defaultReuseIdentifier]]; @@ -354,7 +363,8 @@ { [super viewDidLayoutSubviews]; - if (contactTitleView) + // Check whether the title view has been created and rendered. + if (contactTitleView && contactTitleView.superview) { // Adjust the header height by taking into account the actual position of the member avatar in title view // This position depends automatically on the screen orientation. @@ -1177,4 +1187,11 @@ } } +#pragma mark - RoomMemberTitleViewDelegate + +- (void)roomMemberTitleViewDidLayoutSubview:(RoomMemberTitleView*)titleView +{ + [self viewDidLayoutSubviews]; +} + @end diff --git a/Riot/ViewController/ContactsTableViewController.m b/Riot/ViewController/ContactsTableViewController.m index 299bab4f8..d41be0bb5 100644 --- a/Riot/ViewController/ContactsTableViewController.m +++ b/Riot/ViewController/ContactsTableViewController.m @@ -163,7 +163,7 @@ // Observe kAppDelegateDidTapStatusBarNotification. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [self.contactsTableView setContentOffset:CGPointMake(-self.contactsTableView.contentInset.left, -self.contactsTableView.contentInset.top) animated:YES]; + [self.contactsTableView setContentOffset:CGPointMake(-self.contactsTableView.mxk_adjustedContentInset.left, -self.contactsTableView.mxk_adjustedContentInset.top) animated:YES]; }]; @@ -285,7 +285,7 @@ - (void)scrollToTop:(BOOL)animated { // Scroll to the top - [self.contactsTableView setContentOffset:CGPointMake(-self.contactsTableView.contentInset.left, -self.contactsTableView.contentInset.top) animated:animated]; + [self.contactsTableView setContentOffset:CGPointMake(-self.contactsTableView.mxk_adjustedContentInset.left, -self.contactsTableView.mxk_adjustedContentInset.top) animated:animated]; } #pragma mark - UITableView delegate diff --git a/Riot/ViewController/DirectoryServerPickerViewController.m b/Riot/ViewController/DirectoryServerPickerViewController.m index 6beb3bbe2..e9935f681 100644 --- a/Riot/ViewController/DirectoryServerPickerViewController.m +++ b/Riot/ViewController/DirectoryServerPickerViewController.m @@ -153,7 +153,7 @@ // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES]; + [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; }]; diff --git a/Riot/ViewController/DirectoryViewController.m b/Riot/ViewController/DirectoryViewController.m index f75eae491..ef4883b40 100644 --- a/Riot/ViewController/DirectoryViewController.m +++ b/Riot/ViewController/DirectoryViewController.m @@ -115,7 +115,7 @@ // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES]; + [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; }]; diff --git a/Riot/ViewController/MediaPickerViewController.m b/Riot/ViewController/MediaPickerViewController.m index 0002f8960..262ecd1c0 100644 --- a/Riot/ViewController/MediaPickerViewController.m +++ b/Riot/ViewController/MediaPickerViewController.m @@ -1000,6 +1000,12 @@ static void *RecordingContext = &RecordingContext; NSLog(@"[MediaPickerVC] Attemping to setup AVCapture when it is already started!"); return; } + if (!cameraQueue) + { + NSLog(@"[MediaPickerVC] Attemping to setup AVCapture when it is being destroyed!"); + return; + } + isCaptureSessionSetupInProgress = YES; [self.cameraActivityIndicator startAnimating]; @@ -1192,6 +1198,12 @@ static void *RecordingContext = &RecordingContext; - (void)tearDownAVCapture { + if (!cameraQueue) + { + NSLog(@"[MediaPickerVC] Attemping to tear down AVCapture when it is being destroyed!"); + return; + } + dispatch_sync(cameraQueue, ^{ frontCameraInput = nil; backCameraInput = nil; diff --git a/Riot/ViewController/RecentsViewController.m b/Riot/ViewController/RecentsViewController.m index 7a0fd765a..82f00299e 100644 --- a/Riot/ViewController/RecentsViewController.m +++ b/Riot/ViewController/RecentsViewController.m @@ -656,7 +656,7 @@ } // Look for the lowest section index visible in the bottom sticky headers. - CGFloat maxVisiblePosY = self.recentsTableView.contentOffset.y + self.recentsTableView.frame.size.height - self.recentsTableView.contentInset.bottom; + CGFloat maxVisiblePosY = self.recentsTableView.contentOffset.y + self.recentsTableView.frame.size.height - self.recentsTableView.mxk_adjustedContentInset.bottom; UIView *lastDisplayedSectionHeader = displayedSectionHeaders.lastObject; for (UIView *header in _stickyHeadersBottomContainer.subviews) @@ -1252,7 +1252,7 @@ { if (!self.recentsSearchBar.isHidden) { - if (!self.recentsSearchBar.text.length && (scrollView.contentOffset.y + scrollView.contentInset.top > self.recentsSearchBar.frame.size.height)) + if (!self.recentsSearchBar.text.length && (scrollView.contentOffset.y + scrollView.mxk_adjustedContentInset.top > self.recentsSearchBar.frame.size.height)) { // Hide the search bar [self hideSearchBar:YES]; @@ -1770,7 +1770,7 @@ - (void)scrollToTop:(BOOL)animated { - [self.recentsTableView setContentOffset:CGPointMake(-self.recentsTableView.contentInset.left, -self.recentsTableView.contentInset.top) animated:animated]; + [self.recentsTableView setContentOffset:CGPointMake(-self.recentsTableView.mxk_adjustedContentInset.left, -self.recentsTableView.mxk_adjustedContentInset.top) animated:animated]; } - (void)scrollToTheTopTheNextRoomWithMissedNotificationsInSection:(NSInteger)section diff --git a/Riot/ViewController/RoomFilesSearchViewController.m b/Riot/ViewController/RoomFilesSearchViewController.m index c324416e4..9c7b09dab 100644 --- a/Riot/ViewController/RoomFilesSearchViewController.m +++ b/Riot/ViewController/RoomFilesSearchViewController.m @@ -118,7 +118,7 @@ // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [self.searchTableView setContentOffset:CGPointMake(-self.searchTableView.contentInset.left, -self.searchTableView.contentInset.top) animated:YES]; + [self.searchTableView setContentOffset:CGPointMake(-self.searchTableView.mxk_adjustedContentInset.left, -self.searchTableView.mxk_adjustedContentInset.top) animated:YES]; }]; } diff --git a/Riot/ViewController/RoomMemberDetailsViewController.m b/Riot/ViewController/RoomMemberDetailsViewController.m index 9aaed6b90..c0c004ca0 100644 --- a/Riot/ViewController/RoomMemberDetailsViewController.m +++ b/Riot/ViewController/RoomMemberDetailsViewController.m @@ -33,7 +33,7 @@ #define TABLEVIEW_SECTION_HEADER_HEIGHT 28 #define TABLEVIEW_SECTION_HEADER_HEIGHT_WHEN_HIDDEN 0.01f -@interface RoomMemberDetailsViewController () +@interface RoomMemberDetailsViewController () { RoomMemberTitleView* memberTitleView; @@ -118,6 +118,58 @@ [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. + memberTitleView = [RoomMemberTitleView roomMemberTitleView]; + memberTitleView.delegate = self; + + if (@available(iOS 11.0, *)) + { + // Define directly the navigation titleView with the custom title view instance. Do not use anymore a container. + self.navigationItem.titleView = memberTitleView; + } + else + { + self.navigationItem.titleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 600, 40)]; + + // Add the title view and define edge constraints + memberTitleView.translatesAutoresizingMaskIntoConstraints = NO; + [self.navigationItem.titleView addSubview:memberTitleView]; + + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.navigationItem.titleView + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.navigationItem.titleView + attribute:NSLayoutAttributeBottom + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView + attribute:NSLayoutAttributeLeading + relatedBy:NSLayoutRelationEqual + toItem:self.navigationItem.titleView + attribute:NSLayoutAttributeLeading + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView + attribute:NSLayoutAttributeTrailing + relatedBy:NSLayoutRelationEqual + toItem:self.navigationItem.titleView + attribute:NSLayoutAttributeTrailing + multiplier:1.0f + constant:0.0f]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, bottomConstraint, leadingConstraint, trailingConstraint]]; + } + + // Handle the member avatar at the view controller level. + self.memberThumbnail = memberTitleView.memberAvatar; + + // Add tap gesture on member's name UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; [tap setNumberOfTouchesRequired:1]; [tap setNumberOfTapsRequired:1]; @@ -125,11 +177,6 @@ [self.roomMemberNameLabelMask addGestureRecognizer:tap]; self.roomMemberNameLabelMask.userInteractionEnabled = YES; - self.navigationItem.titleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 600, 40)]; - - memberTitleView = [RoomMemberTitleView roomMemberTitleView]; - self.memberThumbnail = memberTitleView.memberAvatar; - // Add tap to show the room member avatar in fullscreen tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; [tap setNumberOfTouchesRequired:1]; @@ -146,40 +193,6 @@ [tap setDelegate:self]; [self.roomMemberAvatarMask addGestureRecognizer:tap]; self.roomMemberAvatarMask.userInteractionEnabled = YES; - - // Add the title view and define edge constraints - memberTitleView.translatesAutoresizingMaskIntoConstraints = NO; - [self.navigationItem.titleView addSubview:memberTitleView]; - - NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView - attribute:NSLayoutAttributeTop - relatedBy:NSLayoutRelationEqual - toItem:self.navigationItem.titleView - attribute:NSLayoutAttributeTop - multiplier:1.0f - constant:0.0f]; - NSLayoutConstraint *bottomConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.navigationItem.titleView - attribute:NSLayoutAttributeBottom - multiplier:1.0f - constant:0.0f]; - NSLayoutConstraint *leadingConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView - attribute:NSLayoutAttributeLeading - relatedBy:NSLayoutRelationEqual - toItem:self.navigationItem.titleView - attribute:NSLayoutAttributeLeading - multiplier:1.0f - constant:0.0f]; - NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:memberTitleView - attribute:NSLayoutAttributeTrailing - relatedBy:NSLayoutRelationEqual - toItem:self.navigationItem.titleView - attribute:NSLayoutAttributeTrailing - multiplier:1.0f - constant:0.0f]; - [NSLayoutConstraint activateConstraints:@[topConstraint, bottomConstraint, leadingConstraint, trailingConstraint]]; // Register collection view cell class [self.tableView registerClass:TableViewCellWithButton.class forCellReuseIdentifier:[TableViewCellWithButton defaultReuseIdentifier]]; @@ -323,7 +336,8 @@ } } - if (memberTitleView) + // Check whether the title view has been created and rendered. + if (memberTitleView && memberTitleView.superview) { // Adjust the header height by taking into account the actual position of the member avatar in title view // This position depends automatically on the screen orientation. @@ -1047,4 +1061,11 @@ } } +#pragma mark - RoomMemberTitleViewDelegate + +- (void)roomMemberTitleViewDidLayoutSubview:(RoomMemberTitleView*)titleView +{ + [self viewDidLayoutSubviews]; +} + @end diff --git a/Riot/ViewController/RoomMessagesSearchViewController.m b/Riot/ViewController/RoomMessagesSearchViewController.m index 0d1b8f548..2d6afda21 100644 --- a/Riot/ViewController/RoomMessagesSearchViewController.m +++ b/Riot/ViewController/RoomMessagesSearchViewController.m @@ -119,7 +119,7 @@ // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [self.searchTableView setContentOffset:CGPointMake(-self.searchTableView.contentInset.left, -self.searchTableView.contentInset.top) animated:YES]; + [self.searchTableView setContentOffset:CGPointMake(-self.searchTableView.mxk_adjustedContentInset.left, -self.searchTableView.mxk_adjustedContentInset.top) animated:YES]; }]; } diff --git a/Riot/ViewController/RoomSettingsViewController.m b/Riot/ViewController/RoomSettingsViewController.m index 3b3794b27..a82fd9151 100644 --- a/Riot/ViewController/RoomSettingsViewController.m +++ b/Riot/ViewController/RoomSettingsViewController.m @@ -278,7 +278,7 @@ NSString *const kRoomSettingsAdvancedE2eEnabledCellViewIdentifier = @"kRoomSetti // Observe appDelegateDidTapStatusBarNotificationObserver. appDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES]; + [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; }]; } diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 04d58bec3..dc2c4a079 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -110,7 +110,7 @@ #import "MXRoom+Riot.h" #import "IntegrationManagerViewController.h" -#import "WidgetViewController.h" +#import "WidgetPickerViewController.h" @interface RoomViewController () { @@ -374,6 +374,14 @@ missedDiscussionsBarButtonCustomView.backgroundColor = [UIColor clearColor]; missedDiscussionsBarButtonCustomView.clipsToBounds = NO; + NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:missedDiscussionsBarButtonCustomView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:21]; + missedDiscussionsBadgeLabelBgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 21, 21)]; [missedDiscussionsBadgeLabelBgView.layer setCornerRadius:10]; @@ -399,7 +407,7 @@ multiplier:1.0 constant:0]; - [NSLayoutConstraint activateConstraints:@[centerXConstraint, centerYConstraint]]; + [NSLayoutConstraint activateConstraints:@[heightConstraint, centerXConstraint, centerYConstraint]]; // Set up the room title view according to the data source (if any) [self refreshRoomTitle]; @@ -491,7 +499,7 @@ // Observe kAppDelegateDidTapStatusBarNotification. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [self.bubblesTableView setContentOffset:CGPointMake(-self.bubblesTableView.contentInset.left, -self.bubblesTableView.contentInset.top) animated:YES]; + [self.bubblesTableView setContentOffset:CGPointMake(-self.bubblesTableView.mxk_adjustedContentInset.left, -self.bubblesTableView.mxk_adjustedContentInset.top) animated:YES]; }]; } @@ -683,7 +691,7 @@ CGRect frame = expandedHeader.bottomBorderView.frame; self.expandedHeaderContainerHeightConstraint.constant = frame.origin.y + frame.size.height; - self.bubblesTableViewTopConstraint.constant = self.expandedHeaderContainerHeightConstraint.constant - self.bubblesTableView.contentInset.top; + self.bubblesTableViewTopConstraint.constant = self.expandedHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top; self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.expandedHeaderContainerHeightConstraint.constant; } // Check whether the preview header is visible @@ -710,12 +718,12 @@ CGRect frame = previewHeader.bottomBorderView.frame; self.previewHeaderContainerHeightConstraint.constant = frame.origin.y + frame.size.height; - self.bubblesTableViewTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant - self.bubblesTableView.contentInset.top; + self.bubblesTableViewTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top; self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant; } else { - self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.bubblesTableView.contentInset.top; + self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.bubblesTableView.mxk_adjustedContentInset.top; } [self refreshMissedDiscussionsCount:YES]; @@ -1189,13 +1197,31 @@ barButtonItem.enabled = YES; } - BOOL matrixAppsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"matrixApps"]; - if (!matrixAppsEnabled && self.navigationItem.rightBarButtonItems.count == 2) + if (self.navigationItem.rightBarButtonItems.count == 2) { - // If the setting is disabled, do not show the icon - self.navigationItem.rightBarButtonItems = @[self.navigationItem.rightBarButtonItem]; + BOOL matrixAppsEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:@"matrixApps"]; + if (!matrixAppsEnabled) + { + // If the setting is disabled, do not show the icon + self.navigationItem.rightBarButtonItems = @[self.navigationItem.rightBarButtonItem]; + } + else if (self.widgetsCount) + { + // Show there are widgets by changing the "apps" icon color + // TODO: Design must be reviewed + UIImage *icon = self.navigationItem.rightBarButtonItems[1].image; + icon = [MXKTools paintImage:icon withColor:kRiotColorPinkRed]; + icon = [icon imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; + + self.navigationItem.rightBarButtonItems[1].image = icon; + } + else + { + // Reset original icon + self.navigationItem.rightBarButtonItems[1].image = [UIImage imageNamed:@"apps-icon"]; + } } - + // Do not change title view class here if the expanded header is visible. if (self.expandedHeaderContainer.hidden) { @@ -1366,8 +1392,8 @@ [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn animations:^{ - self.bubblesTableViewTopConstraint.constant = (isVisible ? self.expandedHeaderContainerHeightConstraint.constant - self.bubblesTableView.contentInset.top : 0); - self.jumpToLastUnreadBannerContainerTopConstraint.constant = (isVisible ? self.expandedHeaderContainerHeightConstraint.constant : self.bubblesTableView.contentInset.top); + self.bubblesTableViewTopConstraint.constant = (isVisible ? self.expandedHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top : 0); + self.jumpToLastUnreadBannerContainerTopConstraint.constant = (isVisible ? self.expandedHeaderContainerHeightConstraint.constant : self.bubblesTableView.mxk_adjustedContentInset.top); if (roomAvatarView) { @@ -1482,7 +1508,7 @@ animations:^{ self.bubblesTableViewTopConstraint.constant = 0; - self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.bubblesTableView.contentInset.top; + self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.bubblesTableView.mxk_adjustedContentInset.top; // Force to render the view [self forceLayoutRefresh]; @@ -1573,7 +1599,7 @@ [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseIn animations:^{ - self.bubblesTableViewTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant - self.bubblesTableView.contentInset.top; + self.bubblesTableViewTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant - self.bubblesTableView.mxk_adjustedContentInset.top; self.jumpToLastUnreadBannerContainerTopConstraint.constant = self.previewHeaderContainerHeightConstraint.constant; if (roomAvatarView) @@ -2881,19 +2907,16 @@ // Matrix Apps button else if (self.navigationItem.rightBarButtonItems.count == 2 && sender == self.navigationItem.rightBarButtonItems[1]) { - // Temporary code to test `WidgetViewController` - // TODO: remove it -// NSArray *widgets = [[WidgetManager sharedManager] widgetsInRoom:self.roomDataSource.room]; -// if (widgets.count) -// { -// // Hide back button title -// self.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; -// -// WidgetViewController *widgetVC = [[WidgetViewController alloc] initForWidget:widgets[0]]; -// [self.navigationController pushViewController:widgetVC animated:YES]; -// } -// else + if (self.widgetsCount) { + WidgetPickerViewController *widgetPicker = [[WidgetPickerViewController alloc] initForMXSession:self.roomDataSource.mxSession + inRoom:self.roomDataSource.roomId]; + + [widgetPicker showInViewController:self]; + } + else + { + // No widgets -> Directly show the integration manager IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self.roomDataSource.mxSession inRoom:self.roomDataSource.roomId screen:kIntegrationManagerMainScreen @@ -2993,7 +3016,7 @@ // Switch back to the live mode when the user scrolls to the bottom of the non live timeline. if (!self.roomDataSource.isLive && ![self isRoomPreview]) { - CGFloat contentBottomPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.frame.size.height - self.bubblesTableView.contentInset.bottom; + CGFloat contentBottomPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.frame.size.height - self.bubblesTableView.mxk_adjustedContentInset.bottom; if (contentBottomPosY >= self.bubblesTableView.contentSize.height && ![self.roomDataSource.timeline canPaginate:MXTimelineDirectionForwards]) { [self goBackToLive]; @@ -3445,6 +3468,7 @@ // Update the bar [self refreshActivitiesViewDisplay]; [self refreshRoomInputToolbar]; + [self refreshRoomTitle]; } }]; } @@ -3465,6 +3489,12 @@ [[AppDelegate theDelegate] showErrorAsAlert:error]; } +- (NSUInteger)widgetsCount +{ + return [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] + inRoom:self.roomDataSource.room].count; +} + #pragma mark - Unreachable Network Handling - (void)refreshActivitiesViewDisplay @@ -3700,15 +3730,6 @@ if (missedCount) { - // Consider the main navigation controller if the current view controller is embedded inside a split view controller. - UINavigationController *mainNavigationController = self.navigationController; - if (self.splitViewController.isCollapsed && self.splitViewController.viewControllers.count) - { - mainNavigationController = self.splitViewController.viewControllers.firstObject; - } - UINavigationItem *backItem = mainNavigationController.navigationBar.backItem; - UIBarButtonItem *backButton = backItem.backBarButtonItem; - // Refresh missed discussions count label if (missedCount > 99) { @@ -3724,25 +3745,33 @@ // Update the label background view frame CGRect frame = missedDiscussionsBadgeLabelBgView.frame; frame.size.width = round(missedDiscussionsBadgeLabel.frame.size.width + 18); - if (backButton && !backButton.title.length) + + if ([GBDeviceInfo deviceInfo].osVersion.major < 11) { - // Shift the badge on the left to be close the back icon - frame.origin.x = ([GBDeviceInfo deviceInfo].displayInfo.display > GBDeviceDisplay4Inch ? -35 : -25); - } - else - { - frame.origin.x = 0; + // Consider the main navigation controller if the current view controller is embedded inside a split view controller. + UINavigationController *mainNavigationController = self.navigationController; + if (self.splitViewController.isCollapsed && self.splitViewController.viewControllers.count) + { + mainNavigationController = self.splitViewController.viewControllers.firstObject; + } + UINavigationItem *backItem = mainNavigationController.navigationBar.backItem; + UIBarButtonItem *backButton = backItem.backBarButtonItem; + + if (backButton && !backButton.title.length) + { + // Shift the badge on the left to be close the back icon + frame.origin.x = ([GBDeviceInfo deviceInfo].displayInfo.display > GBDeviceDisplay4Inch ? -35 : -25); + } + else + { + frame.origin.x = 0; + } } + // Caution: set label background view frame only in case of changes to prevent from looping on 'viewDidLayoutSubviews'. if (!CGRectEqualToRect(missedDiscussionsBadgeLabelBgView.frame, frame)) { missedDiscussionsBadgeLabelBgView.frame = frame; - - // Adjust the custom view width of the associated bar button - CGRect bgFrame = missedDiscussionsBarButtonCustomView.frame; - CGFloat width = frame.size.width + frame.origin.x; - bgFrame.size.width = (width > 0 ? width : 0); - missedDiscussionsBarButtonCustomView.frame = bgFrame; } // Set the right background color @@ -4075,12 +4104,12 @@ if (readMarkerTableViewCell && isAppeared && !self.isBubbleTableViewDisplayInTransition) { // Check whether the read marker is visible - CGFloat contentTopPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.contentInset.top; + CGFloat contentTopPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.mxk_adjustedContentInset.top; CGFloat readMarkerViewPosY = readMarkerTableViewCell.frame.origin.y + readMarkerTableViewCell.readMarkerView.frame.origin.y; if (contentTopPosY <= readMarkerViewPosY) { // Compute the max vertical position visible according to contentOffset - CGFloat contentBottomPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.frame.size.height - self.bubblesTableView.contentInset.bottom; + CGFloat contentBottomPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.frame.size.height - self.bubblesTableView.mxk_adjustedContentInset.bottom; if (readMarkerViewPosY <= contentBottomPosY) { // Launch animation @@ -4162,7 +4191,7 @@ // The read marker display is still enabled (see roomDataSource.showReadMarker flag), // this means the read marker was not been visible yet. // We show the banner if the marker is located in the top hidden part of the cell. - CGFloat contentTopPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.contentInset.top; + CGFloat contentTopPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.mxk_adjustedContentInset.top; CGFloat readMarkerViewPosY = roomBubbleTableViewCell.frame.origin.y + roomBubbleTableViewCell.readMarkerView.frame.origin.y; self.jumpToLastUnreadBannerContainer.hidden = (contentTopPosY < readMarkerViewPosY); } diff --git a/Riot/ViewController/RoomsViewController.m b/Riot/ViewController/RoomsViewController.m index 2ba04c107..64b6c121f 100644 --- a/Riot/ViewController/RoomsViewController.m +++ b/Riot/ViewController/RoomsViewController.m @@ -156,34 +156,47 @@ NSArray *roomDirectoryServers = [[NSUserDefaults standardUserDefaults] objectForKey:@"roomDirectoryServers"]; directoryServersDataSource.roomDirectoryServers = roomDirectoryServers; + __weak typeof(self) weakSelf = self; + [directoryServerPickerViewController displayWithDataSource:directoryServersDataSource onComplete:^(id cellData) { - if (cellData) + if (weakSelf && cellData) { + typeof(self) self = weakSelf; + // Use the selected directory server if (cellData.thirdPartyProtocolInstance) { - recentsDataSource.publicRoomsDirectoryDataSource.thirdpartyProtocolInstance = cellData.thirdPartyProtocolInstance; + self->recentsDataSource.publicRoomsDirectoryDataSource.thirdpartyProtocolInstance = cellData.thirdPartyProtocolInstance; } else if (cellData.homeserver) { - recentsDataSource.publicRoomsDirectoryDataSource.includeAllNetworks = cellData.includeAllNetworks; - recentsDataSource.publicRoomsDirectoryDataSource.homeserver = cellData.homeserver; + self->recentsDataSource.publicRoomsDirectoryDataSource.includeAllNetworks = cellData.includeAllNetworks; + self->recentsDataSource.publicRoomsDirectoryDataSource.homeserver = cellData.homeserver; } // Refresh data [self addSpinnerFooterView]; - [recentsDataSource.publicRoomsDirectoryDataSource paginate:^(NSUInteger roomsAdded) { + [self->recentsDataSource.publicRoomsDirectoryDataSource paginate:^(NSUInteger roomsAdded) { - // The table view is automatically filled - [self removeSpinnerFooterView]; + if (weakSelf) + { + typeof(self) self = weakSelf; - // Make the directory section appear full-page - [self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:recentsDataSource.directorySection] atScrollPosition:UITableViewScrollPositionTop animated:YES]; + // The table view is automatically filled + [self removeSpinnerFooterView]; + + // Make the directory section appear full-page + [self.recentsTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:self->recentsDataSource.directorySection] atScrollPosition:UITableViewScrollPositionTop animated:YES]; + } } failure:^(NSError *error) { - [self removeSpinnerFooterView]; + if (weakSelf) + { + typeof(self) self = weakSelf; + [self removeSpinnerFooterView]; + } }]; } }]; diff --git a/Riot/ViewController/SettingsViewController.h b/Riot/ViewController/SettingsViewController.h index 6713cb94d..6c9bff125 100644 --- a/Riot/ViewController/SettingsViewController.h +++ b/Riot/ViewController/SettingsViewController.h @@ -19,9 +19,8 @@ #import "DeviceView.h" #import "MediaPickerViewController.h" -#import "TableViewCellWithCheckBoxes.h" -@interface SettingsViewController : MXKTableViewController +@interface SettingsViewController : MXKTableViewController @end diff --git a/Riot/ViewController/SettingsViewController.m b/Riot/ViewController/SettingsViewController.m index 26c461620..01462e21b 100644 --- a/Riot/ViewController/SettingsViewController.m +++ b/Riot/ViewController/SettingsViewController.m @@ -40,6 +40,8 @@ #import "OLMKit/OLMKit.h" +#import "GBDeviceInfo_iOS.h" + NSString* const kSettingsViewControllerPhoneBookCountryCellId = @"kSettingsViewControllerPhoneBookCountryCellId"; @@ -205,9 +207,6 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); BOOL keepNewEmailEditing; BOOL keepNewPhoneNumberEditing; - // The user interface theme cell - TableViewCellWithCheckBoxes *uiThemeCell; - // The current pushed view controller UIViewController *pushedViewController; } @@ -250,7 +249,6 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); [self.tableView registerClass:MXKTableViewCellWithLabelAndSwitch.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndSwitch defaultReuseIdentifier]]; [self.tableView registerClass:MXKTableViewCellWithLabelAndMXKImageView.class forCellReuseIdentifier:[MXKTableViewCellWithLabelAndMXKImageView defaultReuseIdentifier]]; [self.tableView registerClass:TableViewCellWithPhoneNumberTextField.class forCellReuseIdentifier:[TableViewCellWithPhoneNumberTextField defaultReuseIdentifier]]; - [self.tableView registerClass:TableViewCellWithCheckBoxes.class forCellReuseIdentifier:[TableViewCellWithCheckBoxes defaultReuseIdentifier]]; // Enable self sizing cells self.tableView.rowHeight = UITableViewAutomaticDimension; @@ -417,7 +415,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); // Observe kAppDelegateDidTapStatusBarNotificationObserver. kAppDelegateDidTapStatusBarNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kAppDelegateDidTapStatusBarNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { - [self.tableView setContentOffset:CGPointMake(-self.tableView.contentInset.left, -self.tableView.contentInset.top) animated:YES]; + [self.tableView setContentOffset:CGPointMake(-self.tableView.mxk_adjustedContentInset.left, -self.tableView.mxk_adjustedContentInset.top) animated:YES]; }]; @@ -1695,36 +1693,39 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); } else if (row == USER_INTERFACE_THEME_INDEX) { - uiThemeCell = [tableView dequeueReusableCellWithIdentifier:[TableViewCellWithCheckBoxes defaultReuseIdentifier] forIndexPath:indexPath]; - - uiThemeCell.mainContainerLeadingConstraint.constant = uiThemeCell.separatorInset.left; - - uiThemeCell.checkBoxesNumber = 2; - - uiThemeCell.allowsMultipleSelection = NO; - uiThemeCell.delegate = self; - - NSArray *labels = uiThemeCell.labels; - UILabel *label; - label = labels[0]; - label.textColor = kRiotPrimaryTextColor; - label.text = NSLocalizedStringFromTable(@"settings_ui_light_theme", @"Vector", nil); - label = labels[1]; - label.textColor = kRiotPrimaryTextColor; - label.text = NSLocalizedStringFromTable(@"settings_ui_dark_theme", @"Vector", nil); - - NSString *selectedTheme = [[NSUserDefaults standardUserDefaults] stringForKey:@"userInterfaceTheme"]; - if (selectedTheme && [selectedTheme isEqualToString:@"dark"]) + cell = [tableView dequeueReusableCellWithIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; + if (!cell) { - [uiThemeCell setCheckBoxValue:YES atIndex:1]; + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kSettingsViewControllerPhoneBookCountryCellId]; } - else + + NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:@"userInterfaceTheme"]; + if (!theme) { - // Consider the light theme by default. - [uiThemeCell setCheckBoxValue:YES atIndex:0]; + if (@available(iOS 11.0, *)) + { + // "auto" is used the default value from iOS 11 + theme = @"auto"; + } + else + { + // Use "light" for older version + theme = @"light"; + } } - - cell = uiThemeCell; + + theme = [NSString stringWithFormat:@"settings_ui_theme_%@", theme]; + NSString *i18nTheme = NSLocalizedStringFromTable(theme, + @"Vector", + nil); + + cell.textLabel.textColor = kRiotPrimaryTextColor; + + cell.textLabel.text = NSLocalizedStringFromTable(@"settings_ui_theme", @"Vector", nil); + cell.detailTextLabel.text = i18nTheme; + + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + cell.selectionStyle = UITableViewCellSelectionStyleDefault; } } else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) @@ -2270,6 +2271,10 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); languagePickerViewController.delegate = self; [self pushViewController:languagePickerViewController]; } + else if (row == USER_INTERFACE_THEME_INDEX) + { + [self showThemePicker]; + } } else if (section == SETTINGS_SECTION_IGNORED_USERS_INDEX) { @@ -3430,6 +3435,90 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); } } +- (void)showThemePicker +{ + __weak typeof(self) weakSelf = self; + + __block UIAlertAction *autoAction, *lightAction, *darkAction; + NSString *themePickerMessage; + + void (^actionBlock)(UIAlertAction *action) = ^(UIAlertAction * action) { + + if (weakSelf) + { + typeof(self) self = weakSelf; + + NSString *newTheme; + if (action == autoAction) + { + newTheme = @"auto"; + } + else if (action == lightAction) + { + newTheme = @"light"; + } + else if (action == darkAction) + { + newTheme = @"dark"; + } + + NSString *theme = [[NSUserDefaults standardUserDefaults] stringForKey:@"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]; + + [self.tableView reloadData]; + } + } + }; + + if (@available(iOS 11.0, *)) + { + // Show "auto" only from iOS 11 + autoAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_auto", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:actionBlock]; + + // Explain what is "auto" + themePickerMessage = NSLocalizedStringFromTable(@"settings_ui_theme_picker_message", @"Vector", nil); + } + + lightAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_light", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:actionBlock]; + + darkAction = [UIAlertAction actionWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_dark", @"Vector", nil) + style:UIAlertActionStyleDefault + handler:actionBlock]; + + UIAlertController *themePicker = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"settings_ui_theme_picker_title", @"Vector", nil) + message:themePickerMessage + preferredStyle:UIAlertControllerStyleActionSheet]; + + if (autoAction) + { + [themePicker addAction:autoAction]; + } + [themePicker addAction:lightAction]; + [themePicker addAction:darkAction]; + + // Cancel button + [themePicker addAction:[UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleDefault + handler:nil]]; + + UIView *fromCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:USER_INTERFACE_THEME_INDEX inSection:SETTINGS_SECTION_USER_INTERFACE_INDEX]]; + [themePicker popoverPresentationController].sourceView = fromCell; + [themePicker popoverPresentationController].sourceRect = fromCell.bounds; + + [self presentViewController:themePicker animated:YES completion:nil]; +} + #pragma mark - MediaPickerViewController Delegate - (void)dismissMediaPicker @@ -3769,25 +3858,4 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(); } } -#pragma mark - TableViewCellWithCheckBoxesDelegate - -- (void)tableViewCellWithCheckBoxes:(TableViewCellWithCheckBoxes *)tableViewCellWithCheckBoxes didTapOnCheckBoxAtIndex:(NSUInteger)index -{ - if (tableViewCellWithCheckBoxes == uiThemeCell) - { - NSString *theme = (index == 0) ? @"light" : @"dark"; - BOOL isCurrentlySelected = [uiThemeCell checkBoxValueAtIndex:index]; - - if (!isCurrentlySelected) - { - // Clear fake Riot Avatars based on the previous theme. - [AvatarGenerator clear]; - - // The user wants to select this theme - [[NSUserDefaults standardUserDefaults] setObject:theme forKey:@"userInterfaceTheme"]; - [[NSUserDefaults standardUserDefaults] synchronize]; - } - } -} - @end diff --git a/Riot/ViewController/Widgets/WidgetPickerViewController.h b/Riot/ViewController/Widgets/WidgetPickerViewController.h new file mode 100644 index 000000000..8293c1029 --- /dev/null +++ b/Riot/ViewController/Widgets/WidgetPickerViewController.h @@ -0,0 +1,56 @@ +/* + Copyright 2017 Vector Creations 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 + +/** + `WidgetPickerViewController` displays the list of widgets within a room plus a + way to open the integration manager for this room. + + TODO: The feature is still in dev. WidgetPickerViewController` is not yet a pure + UIViewController. + As there is no specified design, the list is displayed in a simple UIAlertController. + It would be nice if this picker could directly: + - Remove a widget + - Launch the integration manager to edit a widget + - Automatically updates on widgets change + */ +@interface WidgetPickerViewController : NSObject + +/** + The UIAlertController instance which handles the dialog. + */ +@property (nonatomic, readonly) UIAlertController *alertController; + +/** + Create the `WidgetPickerViewController` instance. + + @param mxSession the session to use. + @param roomId the room where to list available widgets. + */ +- (instancetype)initForMXSession:(MXSession*)mxSession inRoom:(NSString*)roomId; + +/** + Show the dialog in a given view controller. + + @param mxkViewController the mxkViewController where to show the dialog. + */ +- (void)showInViewController:(MXKViewController*)mxkViewController; + +@end diff --git a/Riot/ViewController/Widgets/WidgetPickerViewController.m b/Riot/ViewController/Widgets/WidgetPickerViewController.m new file mode 100644 index 000000000..a083fc3a3 --- /dev/null +++ b/Riot/ViewController/Widgets/WidgetPickerViewController.m @@ -0,0 +1,99 @@ +/* + Copyright 2017 Vector Creations 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 "WidgetPickerViewController.h" + +#import "AppDelegate.h" + +#import "WidgetManager.h" +#import "WidgetViewController.h" +#import "IntegrationManagerViewController.h" + +@interface WidgetPickerViewController () +{ + MXSession *mxSession; + NSString *roomId; +} + +@end + +@implementation WidgetPickerViewController + +- (instancetype)initForMXSession:(MXSession*)theMXSession inRoom:(NSString*)theRoomId +{ + self = [super init]; + if (self) + { + mxSession = theMXSession; + roomId = theRoomId; + + _alertController = [UIAlertController alertControllerWithTitle:@"Matrix Apps" + message:nil + preferredStyle:UIAlertControllerStyleAlert]; + } + return self; +} + +- (void)showInViewController:(MXKViewController *)mxkViewController +{ + UIAlertAction *alertAction; + + MXRoom *room = [mxSession roomWithRoomId:roomId]; + + NSArray *widgets = [[WidgetManager sharedManager] widgetsNotOfTypes:@[kWidgetTypeJitsi] + inRoom:room]; + // List widgets + for (Widget *widget in widgets) + { + alertAction = [UIAlertAction actionWithTitle:widget.name + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * _Nonnull action) + { + // Hide back button title + mxkViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; + + // Display the widget + WidgetViewController *widgetVC = [[WidgetViewController alloc] initForWidget:widget]; + [mxkViewController.navigationController pushViewController:widgetVC animated:YES]; + }]; + [_alertController addAction:alertAction]; + } + + // Link to the integration manager + alertAction = [UIAlertAction actionWithTitle:@"Manage integrations..." + style:UIAlertActionStyleDefault + handler:^(UIAlertAction * _Nonnull action) + { + IntegrationManagerViewController *modularVC = [[IntegrationManagerViewController alloc] initForMXSession:self->mxSession + inRoom:self->roomId + screen:kIntegrationManagerMainScreen + widgetId:nil]; + + [mxkViewController presentViewController:modularVC animated:NO completion:nil]; + }]; + [_alertController addAction:alertAction]; + + // Cancel + alertAction = [UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:UIAlertActionStyleCancel + handler:nil]; + [_alertController addAction:alertAction]; + + // And show it + [mxkViewController presentViewController:_alertController animated:YES completion:nil]; +} + +@end diff --git a/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.m b/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.m index 71ba494ee..5b7fe73b2 100644 --- a/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.m +++ b/Riot/Views/RoomBubbleList/RoomEmptyBubbleCell.m @@ -20,6 +20,8 @@ - (void)prepareForReuse { + [super prepareForReuse]; + if (self.heightConstraint != 0) { self.heightConstraint = 0; diff --git a/Riot/Views/RoomBubbleList/RoomMembershipBubbleCell.m b/Riot/Views/RoomBubbleList/RoomMembershipBubbleCell.m index 5e25cf6e1..5073402c3 100644 --- a/Riot/Views/RoomBubbleList/RoomMembershipBubbleCell.m +++ b/Riot/Views/RoomBubbleList/RoomMembershipBubbleCell.m @@ -45,6 +45,8 @@ - (void)prepareForReuse { + [super prepareForReuse]; + if (self.pictureViewTopConstraint.constant != xibPictureViewTopConstraintConstant) { self.pictureViewTopConstraint.constant = xibPictureViewTopConstraintConstant; diff --git a/Riot/Views/RoomBubbleList/RoomMembershipCollapsedBubbleCell.m b/Riot/Views/RoomBubbleList/RoomMembershipCollapsedBubbleCell.m index 193a8d6ca..b24f91fac 100644 --- a/Riot/Views/RoomBubbleList/RoomMembershipCollapsedBubbleCell.m +++ b/Riot/Views/RoomBubbleList/RoomMembershipCollapsedBubbleCell.m @@ -45,6 +45,8 @@ - (void)prepareForReuse { + [super prepareForReuse]; + // Reset avatars for (UIView *avatarView in self.avatarsView.subviews) { diff --git a/Riot/Views/RoomInputToolbar/KeyboardGrowingTextView.m b/Riot/Views/RoomInputToolbar/KeyboardGrowingTextView.m new file mode 100644 index 000000000..702a4b35f --- /dev/null +++ b/Riot/Views/RoomInputToolbar/KeyboardGrowingTextView.m @@ -0,0 +1,41 @@ +/* + Copyright 2015 OpenMarket Ltd + Copyright 2017 Vector Creations 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 "RoomInputToolbarView.h" + +@interface KeyboardGrowingTextView: HPGrowingTextView +- (NSArray *)keyCommands; +@end + +@implementation KeyboardGrowingTextView + +- (NSArray *)keyCommands { + return @[ + [UIKeyCommand keyCommandWithInput:@"\r" modifierFlags:0 action:@selector(keyCommandSelector:)] + ]; +} + +- (void)keyCommandSelector:(UIKeyCommand *)sender { + if ([sender.input isEqualToString:@"\r"] && [self.delegate isKindOfClass: RoomInputToolbarView.class]){ + RoomInputToolbarView *ritv = (RoomInputToolbarView *)self.delegate; + [ritv onTouchUpInside:ritv.rightInputToolbarButton]; // touch the Send button. + } +} + +@end diff --git a/Riot/Views/RoomInputToolbar/RoomInputToolbarView.xib b/Riot/Views/RoomInputToolbar/RoomInputToolbarView.xib index 09895caa8..dd571895a 100644 --- a/Riot/Views/RoomInputToolbar/RoomInputToolbarView.xib +++ b/Riot/Views/RoomInputToolbar/RoomInputToolbarView.xib @@ -46,7 +46,7 @@ - + diff --git a/Riot/Views/RoomMember/RoomMemberTitleView.h b/Riot/Views/RoomMember/RoomMemberTitleView.h index 05e12d66c..302160d31 100644 --- a/Riot/Views/RoomMember/RoomMemberTitleView.h +++ b/Riot/Views/RoomMember/RoomMemberTitleView.h @@ -16,6 +16,20 @@ #import +// We add here a protocol to handle title view layout update. +@class RoomMemberTitleView; +@protocol RoomMemberTitleViewDelegate + +@optional +/** + Tells the delegate that the layout has been updated. + + @param titleView the room member title view. + */ +- (void)roomMemberTitleViewDidLayoutSubview:(RoomMemberTitleView*)titleView; + +@end + @interface RoomMemberTitleView : MXKView /** @@ -38,4 +52,9 @@ @property (weak, nonatomic) IBOutlet UIImageView *memberBadge; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *memberAvatarCenterXConstraint; +/** + The delegate. + */ +@property (nonatomic) id delegate; + @end diff --git a/Riot/Views/RoomMember/RoomMemberTitleView.m b/Riot/Views/RoomMember/RoomMemberTitleView.m index 8cbdc856b..7eabc79fa 100644 --- a/Riot/Views/RoomMember/RoomMemberTitleView.m +++ b/Riot/Views/RoomMember/RoomMemberTitleView.m @@ -40,29 +40,60 @@ if (self.superview) { - // Center horizontally the avatar into the navigation bar - CGRect frame = self.superview.frame; - UINavigationBar *navigationBar; - UIView *superView = self; - while (superView.superview) + if (@available(iOS 11.0, *)) { - if ([superView.superview isKindOfClass:[UINavigationBar class]]) + // Force the title view layout by adding 2 new constraints on the UINavigationBarContentView instance. + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.superview + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.superview + attribute:NSLayoutAttributeCenterX + multiplier:1.0f + constant:0.0f]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, centerXConstraint]]; + + // Do not crop the avatar + self.superview.clipsToBounds = NO; + } + else + { + // Center horizontally the avatar into the navigation bar + CGRect frame = self.superview.frame; + UINavigationBar *navigationBar; + UIView *superView = self; + while (superView.superview) { - navigationBar = (UINavigationBar*)superView.superview; - break; + if ([superView.superview isKindOfClass:[UINavigationBar class]]) + { + navigationBar = (UINavigationBar*)superView.superview; + break; + } + + superView = superView.superview; } - superView = superView.superview; - } - - if (navigationBar) - { - CGSize navBarSize = navigationBar.frame.size; - CGFloat superviewCenterX = frame.origin.x + (frame.size.width / 2); - - self.memberAvatarCenterXConstraint.constant = (navBarSize.width / 2) - superviewCenterX; + if (navigationBar) + { + CGSize navBarSize = navigationBar.frame.size; + CGFloat superviewCenterX = frame.origin.x + (frame.size.width / 2); + + self.memberAvatarCenterXConstraint.constant = (navBarSize.width / 2) - superviewCenterX; + } } } + + if (_delegate && [_delegate respondsToSelector:@selector(roomMemberTitleViewDidLayoutSubview:)]) + { + [_delegate roomMemberTitleViewDidLayoutSubview:self]; + } } @end diff --git a/Riot/Views/RoomTitle/RoomAvatarTitleView.m b/Riot/Views/RoomTitle/RoomAvatarTitleView.m index a9a6bb2ea..db361c5be 100644 --- a/Riot/Views/RoomTitle/RoomAvatarTitleView.m +++ b/Riot/Views/RoomTitle/RoomAvatarTitleView.m @@ -40,27 +40,53 @@ if (self.superview) { - // Center horizontally the avatar into the navigation bar - CGRect frame = self.superview.frame; - UINavigationBar *navigationBar; - UIView *superView = self; - while (superView.superview) + if (@available(iOS 11.0, *)) { - if ([superView.superview isKindOfClass:[UINavigationBar class]]) + // Force the title view layout by adding 2 new constraints on the UINavigationBarContentView instance. + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.superview + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.superview + attribute:NSLayoutAttributeCenterX + multiplier:1.0f + constant:0.0f]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, centerXConstraint]]; + + // Do not crop the avatar + self.superview.clipsToBounds = NO; + } + else + { + // Center horizontally the avatar into the navigation bar + CGRect frame = self.superview.frame; + UINavigationBar *navigationBar; + UIView *superView = self; + while (superView.superview) { - navigationBar = (UINavigationBar*)superView.superview; - break; + if ([superView.superview isKindOfClass:[UINavigationBar class]]) + { + navigationBar = (UINavigationBar*)superView.superview; + break; + } + + superView = superView.superview; } - superView = superView.superview; - } - - if (navigationBar) - { - CGSize navBarSize = navigationBar.frame.size; - CGFloat superviewCenterX = frame.origin.x + (frame.size.width / 2); - - self.roomAvatarCenterXConstraint.constant = (navBarSize.width / 2) - superviewCenterX; + if (navigationBar) + { + CGSize navBarSize = navigationBar.frame.size; + CGFloat superviewCenterX = frame.origin.x + (frame.size.width / 2); + + self.roomAvatarCenterXConstraint.constant = (navBarSize.width / 2) - superviewCenterX; + } } } } diff --git a/Riot/Views/RoomTitle/RoomTitleView.m b/Riot/Views/RoomTitle/RoomTitleView.m index 2b856af2c..2b7a9f56d 100644 --- a/Riot/Views/RoomTitle/RoomTitleView.m +++ b/Riot/Views/RoomTitle/RoomTitleView.m @@ -73,35 +73,59 @@ if (self.superview) { - // Center horizontally the display name into the navigation bar - CGRect frame = self.superview.frame; - - // Look for the navigation bar. - UINavigationBar *navigationBar; - UIView *superView = self; - while (superView.superview) + if (@available(iOS 11.0, *)) { - if ([superView.superview isKindOfClass:[UINavigationBar class]]) - { - navigationBar = (UINavigationBar*)superView.superview; - break; - } + // Force the title view layout by adding 2 new constraints on the UINavigationBarContentView instance. + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.superview + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.superview + attribute:NSLayoutAttributeCenterX + multiplier:1.0f + constant:0.0f]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, centerXConstraint]]; - superView = superView.superview; } - - if (navigationBar) + else { - CGSize navBarSize = navigationBar.frame.size; - CGFloat superviewCenterX = frame.origin.x + (frame.size.width / 2); + // Center horizontally the display name into the navigation bar + CGRect frame = self.superview.frame; - // Check whether the view is not moving away (see navigation between view controllers). - if (superviewCenterX < navBarSize.width) + // Look for the navigation bar. + UINavigationBar *navigationBar; + UIView *superView = self; + while (superView.superview) { - // Center the display name - self.displayNameCenterXConstraint.constant = (navBarSize.width / 2) - superviewCenterX; + if ([superView.superview isKindOfClass:[UINavigationBar class]]) + { + navigationBar = (UINavigationBar*)superView.superview; + break; + } + + superView = superView.superview; } - } + + if (navigationBar) + { + CGSize navBarSize = navigationBar.frame.size; + CGFloat superviewCenterX = frame.origin.x + (frame.size.width / 2); + + // Check whether the view is not moving away (see navigation between view controllers). + if (superviewCenterX < navBarSize.width) + { + // Center the display name + self.displayNameCenterXConstraint.constant = (navBarSize.width / 2) - superviewCenterX; + } + } + } } } diff --git a/Riot/Views/RoomTitle/SimpleRoomTitleView.m b/Riot/Views/RoomTitle/SimpleRoomTitleView.m index 982784428..c143fb194 100644 --- a/Riot/Views/RoomTitle/SimpleRoomTitleView.m +++ b/Riot/Views/RoomTitle/SimpleRoomTitleView.m @@ -38,29 +38,52 @@ if (self.superview) { - // Center horizontally the display name into the navigation bar - CGRect frame = self.superview.frame; - UINavigationBar *navigationBar; - UIView *superView = self; - while (superView.superview) + if (@available(iOS 11.0, *)) { - if ([superView.superview isKindOfClass:[UINavigationBar class]]) + // Force the title view layout by adding 2 new constraints on the UINavigationBarContentView instance. + NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeTop + relatedBy:NSLayoutRelationEqual + toItem:self.superview + attribute:NSLayoutAttributeTop + multiplier:1.0f + constant:0.0f]; + NSLayoutConstraint *centerXConstraint = [NSLayoutConstraint constraintWithItem:self + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.superview + attribute:NSLayoutAttributeCenterX + multiplier:1.0f + constant:0.0f]; + + [NSLayoutConstraint activateConstraints:@[topConstraint, centerXConstraint]]; + } + else + { + // Center horizontally the display name into the navigation bar + CGRect frame = self.superview.frame; + UINavigationBar *navigationBar; + UIView *superView = self; + while (superView.superview) { - navigationBar = (UINavigationBar*)superView.superview; - break; + if ([superView.superview isKindOfClass:[UINavigationBar class]]) + { + navigationBar = (UINavigationBar*)superView.superview; + break; + } + + superView = superView.superview; } - superView = superView.superview; + if (navigationBar) + { + CGSize navBarSize = navigationBar.frame.size; + CGFloat superviewCenterX = frame.origin.x + (frame.size.width / 2); + + // Center the display name + self.displayNameCenterXConstraint.constant = (navBarSize.width / 2) - superviewCenterX; + } } - - if (navigationBar) - { - CGSize navBarSize = navigationBar.frame.size; - CGFloat superviewCenterX = frame.origin.x + (frame.size.width / 2); - - // Center the display name - self.displayNameCenterXConstraint.constant = (navBarSize.width / 2) - superviewCenterX; - } } } diff --git a/RiotShareExtension/Model/ShareDataSource.h b/RiotShareExtension/Model/ShareDataSource.h new file mode 100644 index 000000000..2a50bfdee --- /dev/null +++ b/RiotShareExtension/Model/ShareDataSource.h @@ -0,0 +1,38 @@ +/* + Copyright 2017 Aram Sargsyan + + 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 + +typedef NS_ENUM(NSInteger, ShareDataSourceMode) +{ + DataSourceModePeople, + DataSourceModeRooms +}; + + +@interface ShareDataSource : MXKRecentsDataSource + +- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode; + +/** + Returns the cell data at the index path + + @param indexPath the index of the cell + @return the MXKRecentCellData instance if it exists + */ +- (MXKRecentCellData *)cellDataAtIndexPath:(NSIndexPath *)indexPath; + +@end diff --git a/RiotShareExtension/Model/ShareDataSource.m b/RiotShareExtension/Model/ShareDataSource.m new file mode 100644 index 000000000..1a64991fd --- /dev/null +++ b/RiotShareExtension/Model/ShareDataSource.m @@ -0,0 +1,174 @@ +/* + Copyright 2017 Aram Sargsyan + + 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 "ShareDataSource.h" +#import "ShareExtensionManager.h" +#import "RecentRoomTableViewCell.h" + +@interface ShareDataSource () + +@property (nonatomic, readwrite) ShareDataSourceMode dataSourceMode; + +@property NSArray *recentCellDatas; +@property NSMutableArray *visibleRoomCellDatas; + +@end + +@implementation ShareDataSource + +- (instancetype)initWithMode:(ShareDataSourceMode)dataSourceMode +{ + self = [super init]; + if (self) + { + self.dataSourceMode = dataSourceMode; + + [self loadCellData]; + } + return self; +} + +- (void)destroy +{ + [super destroy]; + + _recentCellDatas = nil; + _visibleRoomCellDatas = nil; +} + +#pragma mark - Private + +- (void)loadCellData +{ + [[ShareExtensionManager sharedManager].fileStore asyncRoomsSummaries:^(NSArray * _Nonnull roomsSummaries) { + + NSMutableArray *cellData = [NSMutableArray array]; + + for (MXRoomSummary *roomSummary in roomsSummaries) + { + MXKRecentCellData *recentCellData = [[MXKRecentCellData alloc] initWithRoomSummary:roomSummary andRecentListDataSource:nil]; + + if ((self.dataSourceMode == DataSourceModeRooms) ^ roomSummary.isDirect) + { + [cellData addObject:recentCellData]; + } + } + + // Sort rooms according to their last messages (most recent first) + NSComparator comparator = ^NSComparisonResult(MXKRecentCellData *recentCellData1, MXKRecentCellData *recentCellData2) { + + NSComparisonResult result = NSOrderedAscending; + if (recentCellData2.roomSummary.lastMessageOriginServerTs > recentCellData1.roomSummary.lastMessageOriginServerTs) + { + result = NSOrderedDescending; + } + else if (recentCellData2.roomSummary.lastMessageOriginServerTs == recentCellData1.roomSummary.lastMessageOriginServerTs) + { + result = NSOrderedSame; + } + return result; + }; + [cellData sortUsingComparator:comparator]; + + self.recentCellDatas = cellData; + + dispatch_async(dispatch_get_main_queue(), ^{ + + [self.delegate dataSource:self didCellChange:nil]; + + }); + + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[ShareDataSource failed to get room summaries]"); + + }]; +} + +#pragma mark - MXKRecentsDataSource + +- (MXKRecentCellData *)cellDataAtIndexPath:(NSIndexPath *)indexPath +{ + if (self.visibleRoomCellDatas) + { + return self.visibleRoomCellDatas[indexPath.row]; + } + return self.recentCellDatas[indexPath.row]; +} + +- (void)searchWithPatterns:(NSArray *)patternsList +{ + if (self.visibleRoomCellDatas) + { + [self.visibleRoomCellDatas removeAllObjects]; + } + else + { + self.visibleRoomCellDatas = [NSMutableArray arrayWithCapacity:self.recentCellDatas.count]; + } + if (patternsList.count) + { + for (MXKRecentCellData *cellData in self.recentCellDatas) + { + for (NSString* pattern in patternsList) + { + if (cellData.roomSummary.displayname && [cellData.roomSummary.displayname rangeOfString:pattern options:NSCaseInsensitiveSearch].location != NSNotFound) + { + [self.visibleRoomCellDatas addObject:cellData]; + break; + } + } + } + } + else + { + self.visibleRoomCellDatas = nil; + } + [self.delegate dataSource:self didCellChange:nil]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + if (self.visibleRoomCellDatas) + { + return self.visibleRoomCellDatas.count; + } + return self.recentCellDatas.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + RecentRoomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[RecentRoomTableViewCell defaultReuseIdentifier]]; + + [cell render:[self cellDataAtIndexPath:indexPath]]; + + return cell; +} + +- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath +{ + return NO; +} + + +@end diff --git a/RiotShareExtension/Model/ShareExtensionManager.h b/RiotShareExtension/Model/ShareExtensionManager.h index 065b59b2e..7ba468716 100644 --- a/RiotShareExtension/Model/ShareExtensionManager.h +++ b/RiotShareExtension/Model/ShareExtensionManager.h @@ -21,10 +21,11 @@ @class SharePresentingViewController; /** - Posted when the matrix session has been changed. - The notification object is the matrix session. + Posted when the matrix user account and his data has been checked and updated. + The notification object is the MXKAccount instance. */ -extern NSString *const kShareExtensionManagerDidChangeMXSessionNotification; +extern NSString *const kShareExtensionManagerDidUpdateAccountDataNotification; + /** The protocol for the manager's delegate @@ -42,6 +43,11 @@ extern NSString *const kShareExtensionManagerDidChangeMXSessionNotification; @optional +/** + Called when the manager starts sending the content to a room + @param extensionManager the ShareExtensionManager object that called the method + @param room the room where content will be sent + */ - (void)shareExtensionManager:(ShareExtensionManager *)extensionManager didStartSendingContentToRoom:(MXRoom *)room; /** @@ -71,9 +77,14 @@ extern NSString *const kShareExtensionManagerDidChangeMXSessionNotification; @property (nonatomic) SharePresentingViewController *primaryViewController; /** - The associated matrix session (nil by default). + The current user account */ -@property (nonatomic, readonly) MXSession *mxSession; +@property (nonatomic, readonly) MXKAccount *userAccount; + +/** + The shared file store + */ +@property (nonatomic, readonly) MXFileStore *fileStore; /** A delegate used to notify about needed UI changes when sharing @@ -89,9 +100,9 @@ extern NSString *const kShareExtensionManagerDidChangeMXSessionNotification; Send the content that the user has chosen to a room @param room the room to send the content to @param failureBlock the code to be executed when sharing has failed for whatever reason - note: there is no "successBlock" parameter because when the sharing succeds, the extension needs to close itself + note: there is no "successBlock" parameter because when the sharing succeeds, the extension needs to close itself */ -- (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)())failureBlock; +- (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))failureBlock; /** Checks if there is an image in the user chosen content @@ -106,3 +117,10 @@ extern NSString *const kShareExtensionManagerDidChangeMXSessionNotification; - (void)terminateExtensionCanceled:(BOOL)canceled; @end + + +@interface NSItemProvider (ShareExtensionManager) + +@property BOOL isLoaded; + +@end diff --git a/RiotShareExtension/Model/ShareExtensionManager.m b/RiotShareExtension/Model/ShareExtensionManager.m index 47a679a0b..8fb75f3c0 100644 --- a/RiotShareExtension/Model/ShareExtensionManager.m +++ b/RiotShareExtension/Model/ShareExtensionManager.m @@ -18,8 +18,9 @@ #import "SharePresentingViewController.h" #import "MXKPieChartHUD.h" @import MobileCoreServices; +#import "objc/runtime.h" -NSString *const kShareExtensionManagerDidChangeMXSessionNotification = @"kShareExtensionManagerDidChangeMXSessionNotification"; +NSString *const kShareExtensionManagerDidUpdateAccountDataNotification = @"kShareExtensionManagerDidUpdateAccountDataNotification"; typedef NS_ENUM(NSInteger, ImageCompressionMode) { @@ -29,13 +30,15 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) ImageCompressionModeLarge }; + @interface ShareExtensionManager () -// The current user account -@property (nonatomic) MXKAccount *userAccount; +@property (nonatomic, readwrite) MXKAccount *userAccount; -@property ImageCompressionMode imageCompressionMode; -@property CGFloat actualLargeSize; +@property (nonatomic) NSMutableArray *pendingImages; +@property (nonatomic) NSMutableDictionary *imageUploadProgresses; +@property (nonatomic) ImageCompressionMode imageCompressionMode; +@property (nonatomic) CGFloat actualLargeSize; @end @@ -52,6 +55,9 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) sharedInstance = [[self alloc] init]; + sharedInstance.pendingImages = [NSMutableArray array]; + sharedInstance.imageUploadProgresses = [NSMutableDictionary dictionary]; + [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(onMediaUploadProgress:) name:kMXMediaUploadProgressNotification object:nil]; // Add observer to handle logout @@ -59,14 +65,15 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) // Add observer on the Extension host [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(checkUserAccount) name:NSExtensionHostWillEnterForegroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:sharedInstance selector:@selector(suspendSession) name:NSExtensionHostDidEnterBackgroundNotification object:nil]; MXSDKOptions *sdkOptions = [MXSDKOptions sharedInstance]; - // Apply the application group sdkOptions.applicationGroupIdentifier = @"group.im.vector"; // Disable identicon use sdkOptions.disableIdenticonUseForUserAvatar = YES; + // Enable e2e encryption for newly created MXSession + sdkOptions.enableCryptoWhenStartingMXSession = YES; + }); return sharedInstance; } @@ -85,48 +92,31 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) if (!firstAccount || ![self.userAccount.mxCredentials.accessToken isEqualToString:firstAccount.mxCredentials.accessToken]) { // Remove this account - [self.userAccount closeSession:YES]; self.userAccount = nil; - _mxSession = nil; - - // Post notification - [[NSNotificationCenter defaultCenter] postNotificationName:kShareExtensionManagerDidChangeMXSessionNotification object:_mxSession userInfo:nil]; } } + if (!self.userAccount) + { + // We consider the first enabled account. + // TODO: Handle multiple accounts + self.userAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; + } + + // Reset the file store to reload the room data. + if (_fileStore) + { + [_fileStore close]; + _fileStore = nil; + } + if (self.userAccount) { - // Resume the matrix session - [self.userAccount resume]; + _fileStore = [[MXFileStore alloc] initWithCredentials:self.userAccount.mxCredentials]; } - else - { - // Prepare a new session if a new account is available. - [self prepareSession]; - } -} - -- (void)prepareSession -{ - // We consider the first enabled account. - // TODO: Handle multiple accounts - self.userAccount = [MXKAccountManager sharedManager].activeAccounts.firstObject; - if (self.userAccount) - { - NSLog(@"[ShareExtensionManager] openSession for %@ account", self.userAccount.mxCredentials.userId); - // Use MXFileStore as MXStore to permanently store events. - [self.userAccount openSessionWithStore:[[MXFileStore alloc] init]]; - - _mxSession = self.userAccount.mxSession; - - // Post notification - [[NSNotificationCenter defaultCenter] postNotificationName:kShareExtensionManagerDidChangeMXSessionNotification object:_mxSession userInfo:nil]; - } -} - -- (void)suspendSession -{ - [self.userAccount pauseInBackgroundTask]; + + // Post notification + [[NSNotificationCenter defaultCenter] postNotificationName:kShareExtensionManagerDidUpdateAccountDataNotification object:self.userAccount userInfo:nil]; } #pragma mark - Public @@ -135,11 +125,11 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) { _shareExtensionContext = shareExtensionContext; - // Prepare or resume the matrix session. + // Check the current matrix user. [self checkUserAccount]; } -- (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)())failureBlock +- (void)sendContentToRoom:(MXRoom *)room failureBlock:(void(^)(NSError *error))failureBlock { NSString *UTTypeText = (__bridge NSString *)kUTTypeText; NSString *UTTypeURL = (__bridge NSString *)kUTTypeURL; @@ -150,6 +140,8 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) __weak typeof(self) weakSelf = self; + [self.pendingImages removeAllObjects]; + for (NSExtensionItem *item in self.shareExtensionContext.inputItems) { for (NSItemProvider *itemProvider in item.attachments) @@ -207,30 +199,24 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeImage]) { - [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(NSData *imageData, NSError * _Null_unspecified error) { - - // Switch back on the main thread to handle correctly the UI change - dispatch_async(dispatch_get_main_queue(), ^{ - - if (weakSelf) - { - typeof(self) self = weakSelf; - UIImage *image = [[UIImage alloc] initWithData:imageData]; - - UIAlertController *compressionPrompt = [self compressionPromptForImage:image shareBlock:^{ - - [self sendImage:image withProvider:itemProvider toRoom:room extensionItem:item failureBlock:failureBlock]; - - }]; - - if (compressionPrompt) - { - [self.delegate shareExtensionManager:self showImageCompressionPrompt:compressionPrompt]; - } - } + itemProvider.isLoaded = NO; + [itemProvider loadItemForTypeIdentifier:UTTypeImage options:nil completionHandler:^(NSData *imageData, NSError * _Null_unspecified error) + { + if (weakSelf) + { + itemProvider.isLoaded = YES; + [self.pendingImages addObject:imageData]; - }); - + if ([self areAttachmentsFullyLoaded]) + { + UIImage *firstImage = [UIImage imageWithData:self.pendingImages.firstObject]; + UIAlertController *compressionPrompt = [self compressionPromptForImage:firstImage shareBlock:^{ + [self sendImages:self.pendingImages withProviders:item.attachments toRoom:room extensionItem:item failureBlock:failureBlock]; + }]; + + [self.delegate shareExtensionManager:self showImageCompressionPrompt:compressionPrompt]; + } + } }]; } else if ([itemProvider hasItemConformingToTypeIdentifier:UTTypeVideo]) @@ -288,8 +274,6 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) - (void)terminateExtensionCanceled:(BOOL)canceled { - [self suspendSession]; - if (canceled) { [self.shareExtensionContext cancelRequestWithError:[NSError errorWithDomain:@"MXUserCancelErrorDomain" code:4201 userInfo:nil]]; @@ -303,12 +287,24 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) self.primaryViewController = nil; } +- (void)roomFromRoomSummary:(MXRoomSummary *)roomSummary store:(MXFileStore *)fileStore session:(MXSession *)session +{ + [fileStore asyncAccountDataOfRoom:roomSummary.roomId success:^(MXRoomAccountData * _Nonnull accountData) { + [fileStore asyncStateEventsOfRoom:roomSummary.roomId success:^(NSArray * _Nonnull roomStateEvents) { + MXRoom *room = [[MXRoom alloc] initWithRoomId:roomSummary.roomId andMatrixSession:session andStateEvents:roomStateEvents andAccountData:accountData]; + } failure:^(NSError * _Nonnull error) { + //sjh + }]; + } failure:^(NSError * _Nonnull error) { + //shj + }]; + +} + #pragma mark - Private - (void)completeRequestReturningItems:(nullable NSArray *)items completionHandler:(void(^ __nullable)(BOOL expired))completionHandler; { - [self suspendSession]; - [self.shareExtensionContext completeRequestReturningItems:items completionHandler:completionHandler]; [self.primaryViewController destroy]; @@ -471,19 +467,44 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) } } +- (BOOL)areAttachmentsFullyLoaded +{ + for (NSExtensionItem *item in self.shareExtensionContext.inputItems) + { + for (NSItemProvider *itemProvider in item.attachments) + { + if (itemProvider.isLoaded == NO) + { + return NO; + } + } + } + return YES; +} + #pragma mark - Notifications - (void)onMediaUploadProgress:(NSNotification *)notification { + self.imageUploadProgresses[notification.object] = (NSNumber *)notification.userInfo[kMXMediaLoaderProgressValueKey]; + if ([self.delegate respondsToSelector:@selector(shareExtensionManager:mediaUploadProgress:)]) { - [self.delegate shareExtensionManager:self mediaUploadProgress:((NSNumber *)notification.userInfo[kMXMediaLoaderProgressValueKey]).floatValue]; + const NSInteger totalImagesCount = self.pendingImages.count; + CGFloat totalProgress = 0.0; + + for (NSNumber *progress in self.imageUploadProgresses.allValues) + { + totalProgress += progress.floatValue/totalImagesCount; + } + + [self.delegate shareExtensionManager:self mediaUploadProgress:totalProgress]; } } #pragma mark - Sharing -- (void)sendText:(NSString *)text toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)())failureBlock +- (void)sendText:(NSString *)text toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)(NSError *error))failureBlock { [self didStartSendingToRoom:room]; if (!text) @@ -491,13 +512,12 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) NSLog(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); if (failureBlock) { - failureBlock(); + failureBlock(nil); } return; } __weak typeof(self) weakSelf = self; - [room sendTextMessage:text success:^(NSString *eventId) { if (weakSelf) { @@ -508,12 +528,12 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) NSLog(@"[ShareExtensionManager] sendTextMessage failed."); if (failureBlock) { - failureBlock(); + failureBlock(error); } }]; } -- (void)sendFileWithUrl:(NSURL *)fileUrl toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)())failureBlock +- (void)sendFileWithUrl:(NSURL *)fileUrl toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)(NSError *error))failureBlock { [self didStartSendingToRoom:room]; if (!fileUrl) @@ -521,7 +541,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) NSLog(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); if (failureBlock) { - failureBlock(); + failureBlock(nil); } return; } @@ -543,85 +563,108 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) NSLog(@"[ShareExtensionManager] sendFile failed."); if (failureBlock) { - failureBlock(); + failureBlock(error); } } keepActualFilename:YES]; } -- (void)sendImage:(UIImage *)image withProvider:(NSItemProvider*)itemProvider toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)())failureBlock + +- (void)sendImages:(NSMutableArray *)imageDatas withProviders:(NSArray*)itemProviders toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)(NSError *error))failureBlock { [self didStartSendingToRoom:room]; - if (!image) + + for (NSInteger index = 0; index < imageDatas.count; index++) { - NSLog(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); - if (failureBlock) + NSItemProvider *itemProvider = itemProviders[index]; + NSData *imageData = imageDatas[index]; + UIImage *image = [UIImage imageWithData:imageData]; + + if (!imageData) { - failureBlock(); + NSLog(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); + if (failureBlock) + { + failureBlock(nil); + failureBlock = nil; + } + return; } - return; - } - - // Prepare the image - NSData *imageData; - - if (self.imageCompressionMode == ImageCompressionModeSmall) - { - image = [MXKTools reduceImage:image toFitInSize:CGSizeMake(MXKTOOLS_SMALL_IMAGE_SIZE, MXKTOOLS_SMALL_IMAGE_SIZE)]; - } - else if (self.imageCompressionMode == ImageCompressionModeMedium) - { - image = [MXKTools reduceImage:image toFitInSize:CGSizeMake(MXKTOOLS_MEDIUM_IMAGE_SIZE, MXKTOOLS_MEDIUM_IMAGE_SIZE)]; - } - else if (self.imageCompressionMode == ImageCompressionModeLarge) - { - image = [MXKTools reduceImage:image toFitInSize:CGSizeMake(self.actualLargeSize, self.actualLargeSize)]; - } - - // Make sure the uploaded image orientation is up - image = [MXKTools forceImageOrientationUp:image]; - - NSString *mimeType; - if ([itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypePNG]) - { - mimeType = @"image/png"; - imageData = UIImagePNGRepresentation(image); - } - else - { - // Use jpeg format by default. - mimeType = @"image/jpeg"; - imageData = UIImageJPEGRepresentation(image, 0.9); - } - - UIImage *thumbnail = nil; - // Thumbnail is useful only in case of encrypted room - if (room.state.isEncrypted) - { - thumbnail = [MXKTools reduceImage:image toFitInSize:CGSizeMake(800, 600)]; - if (thumbnail == image) + + // Prepare the image + NSData *convertedImageData; + + if (self.imageCompressionMode == ImageCompressionModeSmall) { - thumbnail = nil; + image = [MXKTools reduceImage:image toFitInSize:CGSizeMake(MXKTOOLS_SMALL_IMAGE_SIZE, MXKTOOLS_SMALL_IMAGE_SIZE)]; } + else if (self.imageCompressionMode == ImageCompressionModeMedium) + { + image = [MXKTools reduceImage:image toFitInSize:CGSizeMake(MXKTOOLS_MEDIUM_IMAGE_SIZE, MXKTOOLS_MEDIUM_IMAGE_SIZE)]; + } + else if (self.imageCompressionMode == ImageCompressionModeLarge) + { + image = [MXKTools reduceImage:image toFitInSize:CGSizeMake(self.actualLargeSize, self.actualLargeSize)]; + } + + // Make sure the uploaded image orientation is up + image = [MXKTools forceImageOrientationUp:image]; + + NSString *mimeType; + if ([itemProvider hasItemConformingToTypeIdentifier:(__bridge NSString *)kUTTypePNG]) + { + mimeType = @"image/png"; + convertedImageData = UIImagePNGRepresentation(image); + } + else + { + // Use jpeg format by default. + mimeType = @"image/jpeg"; + convertedImageData = UIImageJPEGRepresentation(image, 0.9); + } + + UIImage *thumbnail = nil; + // Thumbnail is useful only in case of encrypted room + if (room.state.isEncrypted) + { + thumbnail = [MXKTools reduceImage:image toFitInSize:CGSizeMake(800, 600)]; + if (thumbnail == image) + { + thumbnail = nil; + } + } + + __weak typeof(self) weakSelf = self; + + [room sendImage:convertedImageData withImageSize:image.size mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { + if (weakSelf) + { + typeof(self) self = weakSelf; + [imageDatas removeObject:imageData]; + + if (!imageDatas.count) + { + [self.shareExtensionContext completeRequestReturningItems:@[extensionItem] completionHandler:nil]; + } + + } + } failure:^(NSError *error) { + NSLog(@"[ShareExtensionManager] sendImage failed."); + [imageDatas removeObject:imageData]; + + if (!imageDatas.count) + { + if (failureBlock) + { + failureBlock(error); + } + } + + }]; } - __weak typeof(self) weakSelf = self; - - [room sendImage:imageData withImageSize:image.size mimeType:mimeType andThumbnail:thumbnail localEcho:nil success:^(NSString *eventId) { - if (weakSelf) - { - typeof(self) self = weakSelf; - [self completeRequestReturningItems:@[extensionItem] completionHandler:nil]; - } - } failure:^(NSError *error) { - NSLog(@"[ShareExtensionManager] sendImage failed."); - if (failureBlock) - { - failureBlock(); - } - }]; } -- (void)sendVideo:(NSURL *)videoLocalUrl toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)())failureBlock +- (void)sendVideo:(NSURL *)videoLocalUrl toRoom:(MXRoom *)room extensionItem:(NSExtensionItem *)extensionItem failureBlock:(void(^)(NSError *error))failureBlock { [self didStartSendingToRoom:room]; if (!videoLocalUrl) @@ -629,7 +672,7 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) NSLog(@"[ShareExtensionManager] loadItemForTypeIdentifier: failed."); if (failureBlock) { - failureBlock(); + failureBlock(nil); } return; } @@ -656,10 +699,27 @@ typedef NS_ENUM(NSInteger, ImageCompressionMode) NSLog(@"[ShareExtensionManager] sendVideo failed."); if (failureBlock) { - failureBlock(); + failureBlock(error); } }]; } @end + + +@implementation NSItemProvider (ShareExtensionManager) + +- (void)setIsLoaded:(BOOL)isLoaded +{ + NSNumber *number = [NSNumber numberWithBool:isLoaded]; + objc_setAssociatedObject(self, @selector(isLoaded), number, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +- (BOOL)isLoaded +{ + NSNumber *number = objc_getAssociatedObject(self, @selector(isLoaded)); + return number.boolValue; +} + +@end diff --git a/RiotShareExtension/Model/ShareRecentsDataSource.m b/RiotShareExtension/Model/ShareRecentsDataSource.m deleted file mode 100644 index 4df22655c..000000000 --- a/RiotShareExtension/Model/ShareRecentsDataSource.m +++ /dev/null @@ -1,144 +0,0 @@ -/* - Copyright 2017 Aram Sargsyan - - 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 "ShareRecentsDataSource.h" -#import "RoomTableViewCell.h" -#import "RecentCellData.h" - -@interface ShareRecentsDataSource () - -@property (nonatomic, readwrite) ShareRecentsDataSourceMode dataSourceMode; - -@property NSMutableArray *recentRooms; -@property NSMutableArray *recentPeople; - -@end - -@implementation ShareRecentsDataSource - -- (instancetype)initWithMatrixSession:(MXSession *)mxSession dataSourceMode:(ShareRecentsDataSourceMode)dataSourceMode -{ - self = [super initWithMatrixSession:mxSession]; - if (self) - { - self.dataSourceMode = dataSourceMode; - _recentRooms = [NSMutableArray array]; - _recentPeople = [NSMutableArray array]; - } - return self; -} - - -#pragma mark - Private - -- (void)updateArrays -{ - [self.recentPeople removeAllObjects]; - [self.recentRooms removeAllObjects]; - - MXKSessionRecentsDataSource *recentsDataSource = displayedRecentsDataSourceArray.firstObject; - NSInteger count = recentsDataSource.numberOfCells; - - for (int index = 0; index < count; index++) - { - id cellData = [recentsDataSource cellDataAtIndex:index]; - MXRoom* room = cellData.roomSummary.room; - - if (self.dataSourceMode == RecentsDataSourceModePeople) - { - if (room.isDirect && room.state.membership == MXMembershipJoin) - { - [self.recentPeople addObject:cellData]; - } - } - else if (self.dataSourceMode == RecentsDataSourceModeRooms) - { - if (!room.isDirect && room.state.membership == MXMembershipJoin) - { - [self.recentRooms addObject:cellData]; - } - } - } -} - -#pragma mark - MXKRecentsDataSource - -- (Class)cellDataClassForCellIdentifier:(NSString *)identifier -{ - return RecentCellData.class; -} - -- (id)cellDataAtIndexPath:(NSIndexPath *)indexPath -{ - id cellData = nil; - - if (self.dataSourceMode == RecentsDataSourceModePeople) - { - cellData = self.recentPeople[indexPath.row]; - } - else - { - cellData = self.recentRooms[indexPath.row]; - } - - return cellData; -} - -- (void)dataSource:(MXKDataSource*)dataSource didCellChange:(id)changes -{ - [super dataSource:dataSource didCellChange:changes]; - - [self updateArrays]; - [self.delegate dataSource:self didCellChange:changes]; -} - -#pragma mark - UITableViewDataSource - -- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView -{ - return 1; -} - -- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section -{ - if (self.dataSourceMode == RecentsDataSourceModePeople) - { - return self.recentPeople.count; - } - else - { - return self.recentRooms.count; - } -} - -- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath -{ - RoomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[RoomTableViewCell defaultReuseIdentifier]]; - - MXRoom *room = [self cellDataAtIndexPath:indexPath].roomSummary.room; - - [cell render:room]; - - if (!room.summary.displayname.length && !cell.titleLabel.text.length) - { - cell.titleLabel.text = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); - } - - return cell; -} - - -@end diff --git a/RiotShareExtension/ViewController/FallbackViewController.m b/RiotShareExtension/ViewController/FallbackViewController.m index 479e97f5e..cae2cabce 100644 --- a/RiotShareExtension/ViewController/FallbackViewController.m +++ b/RiotShareExtension/ViewController/FallbackViewController.m @@ -30,7 +30,7 @@ [super viewDidLoad]; self.titleLabel.textColor = kRiotSecondaryTextColor; - self.titleLabel.text = NSLocalizedStringFromTable(@"auth_share_extension_prompt", @"Vector", nil); + self.titleLabel.text = NSLocalizedStringFromTable(@"share_extension_auth_prompt", @"Vector", nil); } - (void)didReceiveMemoryWarning diff --git a/RiotShareExtension/ViewController/RoomsListViewController.h b/RiotShareExtension/ViewController/RoomsListViewController.h index 9a87e86a9..677fcf173 100644 --- a/RiotShareExtension/ViewController/RoomsListViewController.h +++ b/RiotShareExtension/ViewController/RoomsListViewController.h @@ -16,7 +16,7 @@ #import #import "MXRoom+Riot.h" -#import "ShareRecentsDataSource.h" +#import "ShareDataSource.h" @interface RoomsListViewController : MXKRecentListViewController diff --git a/RiotShareExtension/ViewController/RoomsListViewController.m b/RiotShareExtension/ViewController/RoomsListViewController.m index 7322674d5..90bcae16b 100644 --- a/RiotShareExtension/ViewController/RoomsListViewController.m +++ b/RiotShareExtension/ViewController/RoomsListViewController.m @@ -15,7 +15,7 @@ */ #import "RoomsListViewController.h" -#import "RoomTableViewCell.h" +#import "RecentRoomTableViewCell.h" #import "NSBundle+MatrixKit.h" #import "ShareExtensionManager.h" #import "RecentCellData.h" @@ -23,8 +23,6 @@ #import "MXKPieChartView.h" #import "MXKPieChartHUD.h" - - @interface RoomsListViewController () @property (nonatomic) MXKPieChartHUD *hudView; @@ -35,7 +33,6 @@ @end - @implementation RoomsListViewController #pragma mark - Class methods @@ -73,7 +70,7 @@ { [super viewDidLoad]; - [self.recentsTableView registerNib:[RoomTableViewCell nib] forCellReuseIdentifier:[RoomTableViewCell defaultReuseIdentifier]]; + [self.recentsTableView registerNib:[RecentRoomTableViewCell nib] forCellReuseIdentifier:[RecentRoomTableViewCell defaultReuseIdentifier]]; [self configureSearchBar]; } @@ -130,35 +127,78 @@ - (void)showShareAlertForRoomPath:(NSIndexPath *)indexPath { - // @TODO: the room should be instanciated here (only the room summary should be available from dataSource). - NSString *receipantName = [self.dataSource getRoomAtIndexPath:indexPath].summary.displayname; - if (!receipantName.length) + MXKRecentCellData *recentCellData = [self.dataSource cellDataAtIndexPath:indexPath]; + NSString *roomName = recentCellData.roomSummary.displayname; + if (!roomName.length) { - receipantName = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); + roomName = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); } - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"send_to", @"Vector", nil), receipantName] message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"send_to", @"Vector", nil), roomName] message:nil preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:UIAlertActionStyleCancel handler:nil]; [alertController addAction:cancelAction]; UIAlertAction *sendAction = [UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"send"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { - MXRoom *selectedRoom = [self.dataSource getRoomAtIndexPath:indexPath]; - [ShareExtensionManager sharedManager].delegate = self; - - [[ShareExtensionManager sharedManager] sendContentToRoom:selectedRoom failureBlock:^{ - [self showFailureAlert]; + // The selected room is instanciated here. + [[ShareExtensionManager sharedManager].fileStore asyncAccountDataOfRoom:recentCellData.roomSummary.roomId success:^(MXRoomAccountData * _Nonnull accountData) { + + [[ShareExtensionManager sharedManager].fileStore asyncStateEventsOfRoom:recentCellData.roomSummary.roomId success:^(NSArray * _Nonnull roomStateEvents) { + + MXSession *session = [[MXSession alloc] initWithMatrixRestClient:[[MXRestClient alloc] initWithCredentials:[ShareExtensionManager sharedManager].userAccount.mxCredentials andOnUnrecognizedCertificateBlock:nil]]; + + // To handle correctly the crypto, we have to set a store (use a fake store) + __weak MXSession *weakSession = session; + [session setStore:[[MXNoStore alloc] init] success:^{ + + if (weakSession) + { + __strong MXSession *session = weakSession; + + MXRoom *selectedRoom = [[MXRoom alloc] initWithRoomId:recentCellData.roomSummary.roomId andMatrixSession:session andStateEvents:roomStateEvents andAccountData:accountData]; + + [ShareExtensionManager sharedManager].delegate = self; + + [[ShareExtensionManager sharedManager] sendContentToRoom:selectedRoom failureBlock:^(NSError* error) { + + NSString *title; + if ([error.domain isEqualToString:MXEncryptingErrorDomain]) + { + title = NSLocalizedStringFromTable(@"share_extension_failed_to_encrypt", @"Vector", nil); + } + + [self showFailureAlert:title]; + }]; + } + + } failure:^(NSError *error) { + + NSLog(@"[RoomsListViewController] failed to prepare matrix session]"); + + }]; + + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[RoomsListViewController] failed to get state events"); + + }]; + + } failure:^(NSError * _Nonnull error) { + + NSLog(@"[RoomsListViewController] failed to get account data"); + }]; }]; + [alertController addAction:sendAction]; [self presentViewController:alertController animated:YES completion:nil]; } -- (void)showFailureAlert +- (void)showFailureAlert:(NSString *)title { - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title.length ? title : NSLocalizedStringFromTable(@"room_event_failed_to_send", @"Vector", nil) message:nil preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *okAction = [UIAlertAction actionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { if (self.failureBlock) { @@ -173,7 +213,7 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { - return [RoomTableViewCell cellHeight]; + return [RecentRoomTableViewCell cellHeight]; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath @@ -189,7 +229,7 @@ { if ([cellData isKindOfClass:[RecentCellData class]]) { - return [RoomTableViewCell class]; + return [RecentRoomTableViewCell class]; } return nil; } @@ -198,13 +238,23 @@ { if ([cellData isKindOfClass:[MXKRecentCellData class]]) { - return [RoomTableViewCell defaultReuseIdentifier]; + return [RecentRoomTableViewCell defaultReuseIdentifier]; } return nil; } #pragma mark - UISearchBarDelegate +- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText +{ + NSArray *patterns = nil; + if (searchText.length) + { + patterns = @[searchText]; + } + [self.dataSource searchWithPatterns:patterns]; +} + - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar { if (searchBar == _tableSearchBar) @@ -229,6 +279,7 @@ - (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { [self.recentsSearchBar setShowsCancelButton:NO animated:NO]; + [self.dataSource searchWithPatterns:nil]; } #pragma mark - UIScrollViewDelegate @@ -241,7 +292,7 @@ { if (!self.recentsSearchBar.isHidden) { - if (!self.recentsSearchBar.text.length && (scrollView.contentOffset.y + scrollView.contentInset.top > self.recentsSearchBar.frame.size.height)) + if (!self.recentsSearchBar.text.length && (scrollView.contentOffset.y + scrollView.mxk_adjustedContentInset.top > self.recentsSearchBar.frame.size.height)) { // Hide the search bar [self hideSearchBar:YES]; @@ -264,9 +315,12 @@ - (void)shareExtensionManager:(ShareExtensionManager *)extensionManager didStartSendingContentToRoom:(MXRoom *)room { - self.parentViewController.view.userInteractionEnabled = NO; - self.hudView = [MXKPieChartHUD showLoadingHudOnView:self.view WithMessage:NSLocalizedStringFromTable(@"sending", @"Vector", nil)]; - [self.hudView setProgress:0.0]; + if (!self.hudView) + { + self.parentViewController.view.userInteractionEnabled = NO; + self.hudView = [MXKPieChartHUD showLoadingHudOnView:self.view WithMessage:NSLocalizedStringFromTable(@"sending", @"Vector", nil)]; + [self.hudView setProgress:0.0]; + } } - (void)shareExtensionManager:(ShareExtensionManager *)extensionManager mediaUploadProgress:(CGFloat)progress diff --git a/RiotShareExtension/ViewController/ShareViewController.m b/RiotShareExtension/ViewController/ShareViewController.m index e02961a28..3e7f247e1 100644 --- a/RiotShareExtension/ViewController/ShareViewController.m +++ b/RiotShareExtension/ViewController/ShareViewController.m @@ -18,7 +18,7 @@ #import "SegmentedViewController.h" #import "RoomsListViewController.h" #import "FallbackViewController.h" -#import "ShareRecentsDataSource.h" +#import "ShareDataSource.h" #import "ShareExtensionManager.h" @@ -30,7 +30,7 @@ @property (nonatomic) SegmentedViewController *segmentedViewController; -@property (nonatomic) id shareExtensionManagerDidChangeMXSessionObserver; +@property (nonatomic) id shareExtensionManagerDidUpdateAccountDataObserver; @end @@ -44,10 +44,10 @@ { [super viewDidLoad]; - self.shareExtensionManagerDidChangeMXSessionObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kShareExtensionManagerDidChangeMXSessionNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { + self.shareExtensionManagerDidUpdateAccountDataObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kShareExtensionManagerDidUpdateAccountDataNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { [self configureViews]; - + }]; [self configureViews]; @@ -57,10 +57,10 @@ { [super destroy]; - if (self.shareExtensionManagerDidChangeMXSessionObserver) + if (self.shareExtensionManagerDidUpdateAccountDataObserver) { - [[NSNotificationCenter defaultCenter] removeObserver:self.shareExtensionManagerDidChangeMXSessionObserver]; - self.shareExtensionManagerDidChangeMXSessionObserver = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self.shareExtensionManagerDidUpdateAccountDataObserver]; + self.shareExtensionManagerDidUpdateAccountDataObserver = nil; } [self resetContentView]; @@ -94,7 +94,7 @@ [self resetContentView]; - if ([ShareExtensionManager sharedManager].mxSession) + if ([ShareExtensionManager sharedManager].userAccount) { self.tittleLabel.text = [NSString stringWithFormat:NSLocalizedStringFromTable(@"send_to", @"Vector", nil), @""]; [self configureSegmentedViewController]; @@ -120,12 +120,12 @@ }]; }; - ShareRecentsDataSource *roomsDataSource = [[ShareRecentsDataSource alloc] initWithMatrixSession:[ShareExtensionManager sharedManager].mxSession dataSourceMode:RecentsDataSourceModeRooms]; + ShareDataSource *roomsDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModeRooms]; RoomsListViewController *roomsViewController = [RoomsListViewController recentListViewController]; roomsViewController.failureBlock = failureBlock; [roomsViewController displayList:roomsDataSource]; - ShareRecentsDataSource *peopleDataSource = [[ShareRecentsDataSource alloc] initWithMatrixSession:[ShareExtensionManager sharedManager].mxSession dataSourceMode:RecentsDataSourceModePeople]; + ShareDataSource *peopleDataSource = [[ShareDataSource alloc] initWithMode:DataSourceModePeople]; RoomsListViewController *peopleViewController = [RoomsListViewController recentListViewController]; peopleViewController.failureBlock = failureBlock; [peopleViewController displayList:peopleDataSource]; diff --git a/RiotShareExtension/Model/ShareRecentsDataSource.h b/RiotShareExtension/Views/RecentRoomTableViewCell.h similarity index 66% rename from RiotShareExtension/Model/ShareRecentsDataSource.h rename to RiotShareExtension/Views/RecentRoomTableViewCell.h index e25d3d26d..959ca786e 100644 --- a/RiotShareExtension/Model/ShareRecentsDataSource.h +++ b/RiotShareExtension/Views/RecentRoomTableViewCell.h @@ -16,14 +16,8 @@ #import -typedef NS_ENUM(NSInteger, ShareRecentsDataSourceMode) -{ - RecentsDataSourceModePeople, - RecentsDataSourceModeRooms -}; +@interface RecentRoomTableViewCell : MXKRecentTableViewCell -@interface ShareRecentsDataSource : MXKRecentsDataSource - -- (instancetype)initWithMatrixSession:(MXSession *)mxSession dataSourceMode:(ShareRecentsDataSourceMode)dataSourceMode; ++ (CGFloat)cellHeight; @end diff --git a/RiotShareExtension/Views/RecentRoomTableViewCell.m b/RiotShareExtension/Views/RecentRoomTableViewCell.m new file mode 100644 index 000000000..121f32b0b --- /dev/null +++ b/RiotShareExtension/Views/RecentRoomTableViewCell.m @@ -0,0 +1,83 @@ +/* + Copyright 2017 Aram Sargsyan + + 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 "RecentRoomTableViewCell.h" + +#import "MXRoomSummary+Riot.h" + +@interface RecentRoomTableViewCell () + +@property (weak, nonatomic) IBOutlet MXKImageView *avatarImageView; +@property (weak, nonatomic) IBOutlet UIView *directRoomBorderView; +@property (weak, nonatomic) IBOutlet UILabel *roomTitleLabel; +@property (weak, nonatomic) IBOutlet UIImageView *encryptedRoomIcon; + +@end + +@implementation RecentRoomTableViewCell + +#pragma mark - MXKRecentTableViewCell + ++ (UINib *)nib +{ + // Check whether a nib file is available + NSBundle *mainBundle = [NSBundle bundleForClass:self.class]; + + NSString *path = [mainBundle pathForResource:NSStringFromClass([self class]) ofType:@"nib"]; + if (path) + { + return [UINib nibWithNibName:NSStringFromClass([self class]) bundle:mainBundle]; + } + return nil; +} + +- (void)layoutSubviews +{ + [super layoutSubviews]; + + // Round room avatars + [self.avatarImageView.layer setCornerRadius:self.avatarImageView.frame.size.width / 2]; + self.avatarImageView.clipsToBounds = YES; +} + +- (void)render:(MXKCellData *)cellData +{ + // Sanity check: accept only object of MXKRecentCellData classes or sub-classes + NSParameterAssert([cellData isKindOfClass:[MXKRecentCellData class]]); + + roomCellData = (id)cellData; + if (roomCellData) + { + [roomCellData.roomSummary setRoomAvatarImageIn:self.avatarImageView]; + + self.roomTitleLabel.text = roomCellData.roomSummary.displayname; + if (!self.roomTitleLabel.text.length) + { + self.roomTitleLabel.text = NSLocalizedStringFromTable(@"room_displayname_no_title", @"Vector", nil); + } + + self.directRoomBorderView.hidden = !roomCellData.roomSummary.isDirect; + + self.encryptedRoomIcon.hidden = !roomCellData.roomSummary.isEncrypted; + } +} + ++ (CGFloat)cellHeight +{ + return 74; +} + +@end diff --git a/RiotShareExtension/Views/RecentRoomTableViewCell.xib b/RiotShareExtension/Views/RecentRoomTableViewCell.xib new file mode 100644 index 000000000..97b7b6c1a --- /dev/null +++ b/RiotShareExtension/Views/RecentRoomTableViewCell.xib @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +