From 81468b70d02c0dba6338a0f5a8956022fdb0d01d Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Mon, 19 Jun 2017 10:32:22 +0200 Subject: [PATCH 01/48] Enhancement - Room Chat: Scrolling manually to the bottom of the no live timeline doesn't flip it to read/write view. #1312 --- Riot/ViewController/RoomViewController.m | 77 ++++++++++++++---------- 1 file changed, 46 insertions(+), 31 deletions(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 47a74d510..001c888b1 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2560,6 +2560,16 @@ [super scrollViewDidScroll:scrollView]; [self checkReadMarkerVisibility]; + + // Switch back to the live mode when the user scrolls to the bottom of the non live timeline. + if (!self.roomDataSource.isLive) + { + CGFloat contentBottomPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.frame.size.height - self.bubblesTableView.contentInset.bottom; + if (contentBottomPosY >= self.bubblesTableView.contentSize.height && ![self.roomDataSource.timeline canPaginate:MXTimelineDirectionForwards]) + { + [self goBackToLive]; + } + } } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView @@ -3039,37 +3049,7 @@ [roomActivitiesView displayScrollToBottomIcon:unreadCount onIconTapGesture:^{ - if (self.roomDataSource.isLive) - { - // Enable the read marker display, and disable its update (in order to not mark as read all the new messages by default). - self.roomDataSource.showReadMarker = YES; - self.updateRoomReadMarker = NO; - - [self scrollBubblesTableViewToBottomAnimated:YES]; - } - else - { - // Switch back to the room live timeline managed by MXKRoomDataSourceManager - MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; - MXKRoomDataSource *roomDataSource = [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId create:YES]; - - // Scroll to bottom the bubble history on the display refresh. - shouldScrollToBottomOnTableRefresh = YES; - - [self displayRoom:roomDataSource]; - - // The room view controller do not have here the data source ownership. - self.hasRoomDataSourceOwnership = NO; - - [self refreshActivitiesViewDisplay]; - [self refreshJumpToLastUnreadBannerDisplay]; - - if (self.saveProgressTextInput) - { - // Restore the potential message partially typed before jump to last unread messages. - self.inputToolbarView.textMessage = roomDataSource.partialTextMessage; - } - } + [self goBackToLive]; }]; } @@ -3087,6 +3067,41 @@ } } +- (void)goBackToLive +{ + if (self.roomDataSource.isLive) + { + // Enable the read marker display, and disable its update (in order to not mark as read all the new messages by default). + self.roomDataSource.showReadMarker = YES; + self.updateRoomReadMarker = NO; + + [self scrollBubblesTableViewToBottomAnimated:YES]; + } + else + { + // Switch back to the room live timeline managed by MXKRoomDataSourceManager + MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:self.mainSession]; + MXKRoomDataSource *roomDataSource = [roomDataSourceManager roomDataSourceForRoom:self.roomDataSource.roomId create:YES]; + + // Scroll to bottom the bubble history on the display refresh. + shouldScrollToBottomOnTableRefresh = YES; + + [self displayRoom:roomDataSource]; + + // The room view controller do not have here the data source ownership. + self.hasRoomDataSourceOwnership = NO; + + [self refreshActivitiesViewDisplay]; + [self refreshJumpToLastUnreadBannerDisplay]; + + if (self.saveProgressTextInput) + { + // Restore the potential message partially typed before jump to last unread messages. + self.inputToolbarView.textMessage = roomDataSource.partialTextMessage; + } + } +} + #pragma mark - Missed discussions handling - (void)refreshMissedDiscussionsCount:(BOOL)force From 1ef8c52dab62c69d6efd3e2173b1ff4739e8785b Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 19 Jun 2017 15:47:25 +0200 Subject: [PATCH 02/48] =?UTF-8?q?BugReportViewController:=20Remove=20*Coul?= =?UTF-8?q?d=20not=20load=20the=20=E2=80=9C=E2=80=9D=20image=20referenced?= =?UTF-8?q?=20from=20a=20nib*=20warning=20at=20runtime.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BugReportViewController.xib | 92 +------------------ 1 file changed, 5 insertions(+), 87 deletions(-) diff --git a/Riot/ViewController/BugReportViewController.xib b/Riot/ViewController/BugReportViewController.xib index d4ed64587..4ce911e45 100644 --- a/Riot/ViewController/BugReportViewController.xib +++ b/Riot/ViewController/BugReportViewController.xib @@ -1,11 +1,11 @@ - + - + @@ -102,7 +102,7 @@ - + @@ -125,7 +125,7 @@ - + @@ -243,88 +243,6 @@ - - -YnBsaXN0MDDUAQIDBAUGPT5YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoK4HCBMU -GR4fIyQrLjE3OlUkbnVsbNUJCgsMDQ4PEBESVk5TU2l6ZVYkY2xhc3NcTlNJbWFnZUZsYWdzVk5TUmVw -c1dOU0NvbG9ygAKADRIgwAAAgAOAC1h7MjIsIDIyfdIVChYYWk5TLm9iamVjdHOhF4AEgArSFQoaHaIb -HIAFgAaACRAA0iAKISJfEBROU1RJRkZSZXByZXNlbnRhdGlvboAHgAhPERAuTU0AKgAAB5gAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAADiEXLCliRX8/mGnGTbh97E22fuw/mGnGKWJFfw4hFywAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASLR87OIlfr1C+hPJWzY//VcmM -/1TIi/9UyIv/VcmM/1bNj/9QvoTyOIlfrxItHzsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAEEAgYoYkR/UcKH9ljPkP9VyYv/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VcmL/1jPkP9Rwof2KGJE -fwEEAgYAAAAAAAAAAAAAAAAAAAAAAAAAAAEEAgYyeFOcV8+Q/1XJjP9Uxor/VMaK/1TGiv9Uxor/VMaK -/1TGiv9Uxor/VMaK/1TGiv9Uxor/VcmM/1fPkP8yeFOcAQQCBgAAAAAAAAAAAAAAAAAAAAAoYkR/V8+Q -/1TIi/9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9UyIv/V8+Q -/yhiRH8AAAAAAAAAAAAAAAASLR46UsOH91XJjP9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK -/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1XJjP9Sw4f3Ei0eOgAAAAABAgIEOYlfslfPkP9Uxor/VMaK -/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1PGif9Uxor/UsWJ/1TGiv9Uxor/V8+Q -/zmJX7IBAgIEDSAWK1C/hfRVyYv/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK -/1LFif9XyIz/uenQ/5XfuP9Txon/VMaK/1XJi/9Qv4X0DSAWKyhiRX9WzY//VMaK/1TGiv9Uxor/VMaK -/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1LFif9Xxoz/vOrT///////i9uz/X8uS/1PGif9Uxor/Vs2P -/yhiRX8/mGnGVcmM/1TGiv9Uxor/VMaK/1TGiv9Uxor/U8aJ/1TGiv9Uxor/VMaK/1LFif9XyIz/vOrT -///////p+PH/ctKg/1HFiP9Uxor/VMaK/1XJjP8/mGnGTrd+7VTIiv9Uxor/VMaK/1TGiv9Uxor/U8aK -/1nJj/9RxYn/VMaK/1LFif9XyIz/vOrT///////p+PD/cdGf/0/Eh/9Uxor/VMaK/1TGiv9UyIr/Trd+ -7U22fuxUyIr/VMaK/1TGiv9Uxor/U8aJ/57hv//X8+X/Zs2X/07Ehv9XyIz/wOvV///////n+O//b8+e -/0/Eh/9Uxor/VMaK/1TGiv9Uxor/VMiK/022fuw/mmnGVcmM/1TGiv9Uxor/U8aK/1fIjP/K79z///// -/9nz5v9s0Jz/uurR///////m9+//b9Ge/0/Eh/9Uxor/VMaK/1TGiv9Uxor/VMaK/1XJjP8/mmnGKGJF -f1bNj/9Uxor/VMaK/1TGiv9SxYn/X8qT/8zw3///////9fv3///////n+O//b9Ge/0/Eh/9Uxor/VMaK -/1TGiv9Uxor/VMaK/1TGiv9WzY//KGJFfw0gFitQv4X0VcmL/1TGiv9Uxor/VMaK/1HFiP9eypL/zPDf -///////o+PD/b9Ge/0/Eh/9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9VyYv/UL+F9A0gFisBAgIEOohf -sVfPkP9Uxor/VMaK/1TGiv9Uxor/UcWI/2DLk/+76tH/ddOj/0/Fh/9Uxor/VMaK/1TGiv9Uxor/VMaK -/1TGiv9Uxor/V8+Q/zqIX7EBAgIEAAAAABItHjpSw4f3VcmM/1TGiv9Uxor/VMaK/1TGiv9SxYn/UsWI -/1LFiP9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VcmM/1LDh/cSLR46AAAAAAAAAAAAAAAAKGJE -f1fPkP9UyIv/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9Uxor/VMiL -/1fPkP8oYkR/AAAAAAAAAAAAAAAAAAAAAAEEAgYyeFOcV8+Q/1XJjP9Uxor/VMaK/1TGiv9Uxor/VMaK -/1TGiv9Uxor/VMaK/1TGiv9Uxor/VcmM/1fPkP8yeFOcAQQCBgAAAAAAAAAAAAAAAAAAAAAAAAAAAQQC -BihiRH9Rwof2WM+Q/1XJi/9Uxor/VMaK/1TGiv9Uxor/VMaK/1TGiv9VyYv/WM+Q/1HCh/YoYkR/AQQC -BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEi0fOzqJX69Qv4TzVs2P/1XJjP9UyIv/VMiL -/1XJjP9WzY//UL+E8zqJX68SLR87AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAADiEXLCliRX8/mGnGTbh97E24few/mGnGKWJFfw4hFywAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAA4BAAADAAAAAQAWAAABAQADAAAAAQAWAAABAgADAAAABAAACEYBAwADAAAAAQABAAAB -BgADAAAAAQACAAABEQAEAAAAAQAAAAgBEgADAAAAAQABAAABFQADAAAAAQAEAAABFgADAAAAAQAWAAAB -FwAEAAAAAQAAB5ABHAADAAAAAQABAAABUgADAAAAAQABAAABUwADAAAABAAACE6HcwAHAAAH2AAACFYA -AAAAAAgACAAIAAgAAQABAAEAAQAAB9hhcHBsAiAAAG1udHJSR0IgWFlaIAfZAAIAGQALABoAC2Fjc3BB -UFBMAAAAAGFwcGwAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtYXBwbAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2Rlc2MAAAEIAAAAb2RzY20AAAF4AAAFnGNwcnQA -AAcUAAAAOHd0cHQAAAdMAAAAFHJYWVoAAAdgAAAAFGdYWVoAAAd0AAAAFGJYWVoAAAeIAAAAFHJUUkMA -AAecAAAADmNoYWQAAAesAAAALGJUUkMAAAecAAAADmdUUkMAAAecAAAADmRlc2MAAAAAAAAAFEdlbmVy -aWMgUkdCIFByb2ZpbGUAAAAAAAAAAAAAABRHZW5lcmljIFJHQiBQcm9maWxlAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAB8AAAAMc2tTSwAAACgA -AAGEZGFESwAAAC4AAAGsY2FFUwAAACQAAAHadmlWTgAAACQAAAH+cHRCUgAAACYAAAIidWtVQQAAACoA -AAJIZnJGVQAAACgAAAJyaHVIVQAAACgAAAKaemhUVwAAABYAAALCbmJOTwAAACYAAALYY3NDWgAAACIA -AAL+aGVJTAAAAB4AAAMgaXRJVAAAACgAAAM+cm9STwAAACQAAANmZGVERQAAACwAAAOKa29LUgAAABYA -AAO2c3ZTRQAAACYAAALYemhDTgAAABYAAAPMamFKUAAAABoAAAPiZWxHUgAAACIAAAP8cHRQTwAAACYA -AAQebmxOTAAAACgAAAREZXNFUwAAACYAAAQedGhUSAAAACQAAARsdHJUUgAAACIAAASQZmlGSQAAACgA -AASyaHJIUgAAACgAAATacGxQTAAAACwAAAUCcnVSVQAAACIAAAUuYXJFRwAAACYAAAVQZW5VUwAAACYA -AAV2AFYBYQBlAG8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbABHAGUAbgBlAHIAZQBsACAA -UgBHAEIALQBiAGUAcwBrAHIAaQB2AGUAbABzAGUAUABlAHIAZgBpAGwAIABSAEcAQgAgAGcAZQBuAOgA -cgBpAGMAQx6lAHUAIABoAOwAbgBoACAAUgBHAEIAIABDAGgAdQBuAGcAUABlAHIAZgBpAGwAIABSAEcA -QgAgAEcAZQBuAOkAcgBpAGMAbwQXBDAEMwQwBDsETAQ9BDgEOQAgBD8EQAQ+BEQEMAQ5BDsAIABSAEcA -QgBQAHIAbwBmAGkAbAAgAGcA6QBuAOkAcgBpAHEAdQBlACAAUgBWAEIAwQBsAHQAYQBsAOEAbgBvAHMA -IABSAEcAQgAgAHAAcgBvAGYAaQBskBp1KAAgAFIARwBCACCCcl9pY8+P8ABHAGUAbgBlAHIAaQBzAGsA -IABSAEcAQgAtAHAAcgBvAGYAaQBsAE8AYgBlAGMAbgD9ACAAUgBHAEIAIABwAHIAbwBmAGkAbAXkBegF -1QXkBdkF3AAgAFIARwBCACAF2wXcBdwF2QBQAHIAbwBmAGkAbABvACAAUgBHAEIAIABnAGUAbgBlAHIA -aQBjAG8AUAByAG8AZgBpAGwAIABSAEcAQgAgAGcAZQBuAGUAcgBpAGMAQQBsAGwAZwBlAG0AZQBpAG4A -ZQBzACAAUgBHAEIALQBQAHIAbwBmAGkAbMd8vBgAIABSAEcAQgAg1QS4XNMMx3xmbpAaACAAUgBHAEIA -IGPPj/Blh072TgCCLAAgAFIARwBCACAw1zDtMNUwoTCkMOsDkwO1A70DuQO6A8wAIAPAA8EDvwPGA68D -uwAgAFIARwBCAFAAZQByAGYAaQBsACAAUgBHAEIAIABnAGUAbgDpAHIAaQBjAG8AQQBsAGcAZQBtAGUA -ZQBuACAAUgBHAEIALQBwAHIAbwBmAGkAZQBsDkIOGw4jDkQOHw4lDkwAIABSAEcAQgAgDhcOMQ5IDicO -RA4bAEcAZQBuAGUAbAAgAFIARwBCACAAUAByAG8AZgBpAGwAaQBZAGwAZQBpAG4AZQBuACAAUgBHAEIA -LQBwAHIAbwBmAGkAaQBsAGkARwBlAG4AZQByAGkBDQBrAGkAIABSAEcAQgAgAHAAcgBvAGYAaQBsAFUA -bgBpAHcAZQByAHMAYQBsAG4AeQAgAHAAcgBvAGYAaQBsACAAUgBHAEIEHgQxBEkEOAQ5ACAEPwRABD4E -RAQ4BDsETAAgAFIARwBCBkUGRAZBACAGKgY5BjEGSgZBACAAUgBHAEIAIAYnBkQGOQYnBkUARwBlAG4A -ZQByAGkAYwAgAFIARwBCACAAUAByAG8AZgBpAGwAZXRleHQAAAAAQ29weXJpZ2h0IDIwMDcgQXBwbGUg -SW5jLiwgYWxsIHJpZ2h0cyByZXNlcnZlZC4AWFlaIAAAAAAAAPNSAAEAAAABFs9YWVogAAAAAAAAdE0A -AD3uAAAD0FhZWiAAAAAAAABadQAArHMAABc0WFlaIAAAAAAAACgaAAAVnwAAuDZjdXJ2AAAAAAAAAAEB -zQAAc2YzMgAAAAAAAQxCAAAF3v//8yYAAAeSAAD9kf//+6L///2jAAAD3AAAwGzSJSYnKFokY2xhc3Nu -YW1lWCRjbGFzc2VzXxAQTlNCaXRtYXBJbWFnZVJlcKMnKSpaTlNJbWFnZVJlcFhOU09iamVjdNIlJiwt -V05TQXJyYXmiLCrSJSYvMF5OU011dGFibGVBcnJheaMvLCrTMjMKNDU2V05TV2hpdGVcTlNDb2xvclNw -YWNlRDAgMAAQA4AM0iUmODlXTlNDb2xvcqI4KtIlJjs8V05TSW1hZ2WiOypfEA9OU0tleWVkQXJjaGl2 -ZXLRP0BUcm9vdIABAAgAEQAaACMALQAyADcARgBMAFcAXgBlAHIAeQCBAIMAhQCKAIwAjgCXAJwApwCp -AKsArQCyALUAtwC5ALsAvQDCANkA2wDdEQ8RFBEfESgROxE/EUoRUxFYEWARYxFoEXcRexGCEYoRlxGc -EZ4RoBGlEa0RsBG1Eb0RwBHSEdUR2gAAAAAAAAIBAAAAAAAAAEEAAAAAAAAAAAAAAAAAABHcA - - + From 767698c3ea5a3b6d70dcdf4c6f709eee2774aeb5 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Tue, 20 Jun 2017 11:32:54 +0200 Subject: [PATCH 03/48] Enhancement - UX Rework: Update the buttons of the room expanded header. - Add a contact list button on the left of the members info to open contact list. - Add a add members button on the right to open the contacts picker. https://github.com/vector-im/riot-meta/issues/76 --- Riot.xcodeproj/project.pbxproj | 12 ++ Riot/Assets/Images/members_list_icon.png | Bin 0 -> 1083 bytes Riot/Assets/Images/members_list_icon@2x.png | Bin 0 -> 1666 bytes Riot/Assets/Images/members_list_icon@3x.png | Bin 0 -> 1988 bytes Riot/Base.lproj/Main.storyboard | 25 ++- .../RoomParticipantsViewController.m | 8 +- Riot/ViewController/RoomViewController.h | 4 +- Riot/ViewController/RoomViewController.m | 176 +++++++++++++++++- .../Views/RoomTitle/ExpandedRoomTitleView.xib | 49 +++-- Riot/Views/RoomTitle/RoomTitleView.h | 3 +- Riot/Views/RoomTitle/RoomTitleView.m | 10 + 11 files changed, 255 insertions(+), 32 deletions(-) create mode 100644 Riot/Assets/Images/members_list_icon.png create mode 100644 Riot/Assets/Images/members_list_icon@2x.png create mode 100644 Riot/Assets/Images/members_list_icon@3x.png diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index c767c6dd3..ad472c832 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -422,6 +422,9 @@ F083BEA21E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BD1B1E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.m */; }; F083BEA31E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.xib in Resources */ = {isa = PBXBuildFile; fileRef = F083BD1C1E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.xib */; }; F083BEA51E70356E00A9B29C /* RiotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F083BB041E7005FD00A9B29C /* RiotTests.m */; }; + 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 */; }; F0D869EB1EC455A100BB0A2B /* create_direct_chat.png in Resources */ = {isa = PBXBuildFile; fileRef = F0D869E81EC455A100BB0A2B /* create_direct_chat.png */; }; F0D869EC1EC455A100BB0A2B /* create_direct_chat@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0D869E91EC455A100BB0A2B /* create_direct_chat@2x.png */; }; F0D869ED1EC455A100BB0A2B /* create_direct_chat@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = F0D869EA1EC455A100BB0A2B /* create_direct_chat@3x.png */; }; @@ -1006,6 +1009,9 @@ F083BD1C1E7009ED00A9B29C /* TableViewCellWithPhoneNumberTextField.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = TableViewCellWithPhoneNumberTextField.xib; sourceTree = ""; }; F094A9A21B78D8F000B1FBBF /* Riot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Riot.app; sourceTree = BUILT_PRODUCTS_DIR; }; F094A9BE1B78D8F000B1FBBF /* RiotTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RiotTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + F0A4A1641EF7CB66003630DB /* members_list_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = members_list_icon.png; sourceTree = ""; }; + F0A4A1651EF7CB66003630DB /* members_list_icon@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "members_list_icon@2x.png"; sourceTree = ""; }; + F0A4A1661EF7CB66003630DB /* members_list_icon@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "members_list_icon@3x.png"; sourceTree = ""; }; F0D869E81EC455A100BB0A2B /* create_direct_chat.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = create_direct_chat.png; sourceTree = ""; }; F0D869E91EC455A100BB0A2B /* create_direct_chat@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "create_direct_chat@2x.png"; sourceTree = ""; }; F0D869EA1EC455A100BB0A2B /* create_direct_chat@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "create_direct_chat@3x.png"; sourceTree = ""; }; @@ -1156,6 +1162,9 @@ F083BB151E7009EC00A9B29C /* Images */ = { isa = PBXGroup; children = ( + F0A4A1641EF7CB66003630DB /* members_list_icon.png */, + F0A4A1651EF7CB66003630DB /* members_list_icon@2x.png */, + F0A4A1661EF7CB66003630DB /* members_list_icon@3x.png */, F0614A101EDEE65000F5DC9A /* cancel.png */, F0614A111EDEE65000F5DC9A /* cancel@2x.png */, F0614A121EDEE65000F5DC9A /* cancel@3x.png */, @@ -2130,6 +2139,7 @@ F083BD891E7009ED00A9B29C /* file_music_icon.png in Resources */, F083BDD11E7009ED00A9B29C /* settings_icon.png in Resources */, F083BD811E7009ED00A9B29C /* favourite@2x.png in Resources */, + F0A4A1681EF7CB66003630DB /* members_list_icon@2x.png in Resources */, F083BD9F1E7009ED00A9B29C /* mod_icon@2x.png in Resources */, F083BD801E7009ED00A9B29C /* favourite.png in Resources */, F083BD4B1E7009ED00A9B29C /* camera_capture.png in Resources */, @@ -2266,6 +2276,7 @@ F083BD631E7009ED00A9B29C /* direct_icon@2x.png in Resources */, F083BE991E7009ED00A9B29C /* MessagesSearchResultTextMsgBubbleCell.xib in Resources */, F083BD711E7009ED00A9B29C /* e2e_unencrypted.png in Resources */, + F0A4A1671EF7CB66003630DB /* members_list_icon.png in Resources */, F083BE731E7009ED00A9B29C /* RoomOutgoingTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, F083BD251E7009ED00A9B29C /* admin_icon.png in Resources */, F083BDBE1E7009ED00A9B29C /* riot_icon@3x.png in Resources */, @@ -2328,6 +2339,7 @@ F083BDE01E7009ED00A9B29C /* video_icon.png in Resources */, F083BD601E7009ED00A9B29C /* details_icon@2x.png in Resources */, F083BE5F1E7009ED00A9B29C /* RoomIncomingAttachmentWithPaginationTitleBubbleCell.xib in Resources */, + F0A4A1691EF7CB66003630DB /* members_list_icon@3x.png in Resources */, F083BE8D1E7009ED00A9B29C /* PreviewRoomTitleView.xib in Resources */, F083BD561E7009ED00A9B29C /* camera_video_capture.png in Resources */, F083BD651E7009ED00A9B29C /* directChatOff.png in Resources */, diff --git a/Riot/Assets/Images/members_list_icon.png b/Riot/Assets/Images/members_list_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..eb23ecd65356ab66a084764be7f1b6ec66ea9dd1 GIT binary patch literal 1083 zcmeAS@N?(olHy`uVBq!ia0vp^{2!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@z%2~Ij105p zNH8!kp3V%3D2ed(u}aR*)k{ptPfFFR$SnY>W?-j zfNYSkzLEl1NlCV?QiN}Sf^&XRs)DJWiJpOy9hZWFf=y9MnpKdC8&o@xXRDM^Qc_^0 zuU}qXu2*iXmtT~wZ)j<0sc&GUZ)BtkRH0j3nOBlnp_^B%3^4>|j!SBBa#3bMNoIbY z0?6FNr2NtnTO}osMQ{LdXGvxn!lt}psJDO~)CbAv8|oS8W77uoEZlu4x+)S2Aez9w zf>>q)GRn%oC^Hr4T#%#e3~j*1qlh6KX9G0d%DE^tu_V7JBtJjL&Jh&W3ZA)%>8U}f zi7Ayf`sga4nr-w!-bM00By7Q=z`(WR0_ucClpWV5<+_i+Xuaj>;uxYK+8ew#Tf|YM zK0odIB<71T$}^UHI?!)Y<*3sXv1F5|rBcLGzb%_Q1iM2t-@IXYD0FVOy+e0HZ$e=z z2bW+n$HNZJi5iZcK^_zS&UpU*>`p_C<>tFLzd!rF_HWufoj@}ei{R?1a~TwmDMwyd zb7oekRASXU#p5Y68K%n`%KyCF{B82SWWE34ffvHp?OPu|eadsek81)~*{##Nemdue zk)@rA*y5QoYA-a8*S*l6JzdMYQ7U0)ga+R{`yW3leWvHUv6y&AP5JkUd{JYC&g+T? zHPyCrW}FP)_q|NdM`MRrz$T;mr~k}L-=&slJ^!3;(UqsNGJcy@zEryJd`*?iDQzNh zPdgW~PRpIOnpB)w^tGbJw@I6Pp>|x;M4Rrg`sO&YT_XY*n{TStgWCU_N$1 zToM1SQ-MQfCj~zbJKI@aROJ2a~rStNZZ!&EmJGzRXkk*XnmI quY9-nwEgeQzI~n@H+_Nq!}+^DExyBJ6a5vGMLk{pT-G@yGywpbke2ZP literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images/members_list_icon@2x.png b/Riot/Assets/Images/members_list_icon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8eb7a46cab521a45cded7699aa4b1fc4c178cbbe GIT binary patch literal 1666 zcmeAS@N?(olHy`uVBq!ia0vp^av;pX1|+Qw)-3{3Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBD8X6a5n0T@z%2~Ij105p zNH8!kp3V%3D2ed(u}aR*)k{ptPfFFR$SnY>W?-j zfNYSkzLEl1NlCV?QiN}Sf^&XRs)DJWiJpOy9hZWFf=y9MnpKdC8&o@xXRDM^Qc_^0 zuU}qXu2*iXmtT~wZ)j<0sc&GUZ)BtkRH0j3nOBlnp_^B%3^4>|j!SBBa#3bMNoIbY z0?6FNr2NtnTO}osMQ{LdXGvxn!lt}psJDO~)CbAv8|oS8W77uoEZlu4x+)S2Aez9w zf>>q)GRn%oC^Hr4T#%#e3~j*1qlh6KX9G0d%DE^tu_V7JBtJjL&Jh&W3ZA)%>8U}f zi7Ayf`sga4nr-w!-bM00By7Q=z`(WR0_ucClpWV5<+_i+WTNQl;uzw=`!>usBPLYD z?)kad)6e*%IUQ@8A+$KROJr4~#|M5bk(Q+CPP;UxnylpA@nVsJ(?W?iP6ex*W|`<7 z3*Ee7(E~Salcr0{4lZKU6xvuiam(e-$tU;B{C=sqAa! zxfi+o?~&A=`*APFqba%VLOMR%TI+wAW=AE;1PQG<{qgS3$wzj2$w#?aurAe6`jTB_ zzT!ktQMB5_wbuSpQ@xzyjpkN4PHvdib=Yr7{ry7UrZOweb6#;Ob1i1ec14=*et2h| zvUYO#^18q{0~MW#8qc{b0}eW!@UU;Z_I~QsN844FYJOxc+P>}XoSviv&cOoI{^ zW$h2~ymYE&&F;;+0$tXtiBA_0pZLkon?LWu2TsO~`oFuF<0VCp-p`A9b?atz>+{u5 zGN&t=RQam4>TWRial7vXDbyw=TsJd{AT0My85#dJ9B=C?tgSn@ciyG zFO=SGy?xvM!IA}gmOWA0IZY~v^Tn1gPnUjmx8|F8BKMex?n$X-Vheey_ukjZIK_GX zjpZ!)SPSFFVsq>otEw&I|IBjwyHhala@D!=J=ZJOX)rab1fG_JT|cs=jylSMOxc`pVn*SRehXvDCv>96_a>ylrX zYA4*g`hQZ(16`~89lvr~9#aP`6YpEp%-Z2KXw;f3OZqd z&)R>C@4~~&>@Qv#pYWfbm1J7_!d^(a@g(0qZ)y4JtB*}Co&B;qvv-&I%ZHbCMK}0* f$?Mr1;P}J)nCGBQp)z4*}Q$iB}mtwf7 literal 0 HcmV?d00001 diff --git a/Riot/Assets/Images/members_list_icon@3x.png b/Riot/Assets/Images/members_list_icon@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ec24ed814d286277f7f5ce9f9361527a1c75fadb GIT binary patch literal 1988 zcmZuy2{;s58y@@CAWK_wrldH3MXJ3k{ zkB`uch$2$3R6SyZ=C>e!#If*?xJbr_QSc-pNDvo`BVD4P!C*n6KgWKZgs?vo5hM0l z@%cfi*f1y@0)zfPCkj5`uk!xGOK|ed?JKKq$x!^(AkE4CSPF^kMk0k8pA+;BWJ|yX z`n&jJ{i0CN|3>^(DM}C$d7g~-=W`V>GKQm||6|+7qo4x1f204cw_mON<{C@$GyQQ< z#?m^FJ~RLzmTPNq&J82Agt5zVvs7YKZZAZ>s*3TwB@_T^PE=8X=W1&;Ws9+}|G18W zStuZOg<|H1I%hQRimNCprX)d9k27K|6grbsVAp9KDBq{ei(~fdOEDx3b`!c`DL_1- zz-n@Ql*?#(f2VTm!)gVuNVlu76dzwER(L4Li=Ly`_Ak*-%SjE$1DYz2OYKf6+*Ti7 z*?Cz+*~4#w2Xwwm``pX1<)(^g;ne|e5||-n&T6si@v}d9<-hcqK@N z83qnrx)G!?NS%>ch@rEi6Vkqv4W>Oqi?p#y3_Z~98WT-Qc>!kXy5SMRvwFbj{62Ae zoX?}m8knZ_(-+!760dH(gRR%)WHUt($`@qbh{g4% zjbQFoKc-E~b_p{qLl9q-U1Fy>_fiVW_Il{54K+THp9xKK3U89($^w(sg8=w?+s@|%>T6Q{`(l5m= zCzY=xV>>%c?r>lGY-?WSNDVkEi#wGwd&T92rgy^L4<7XA@BzC#(lybW_2A$|`Q+cs z7!!4-4%b@jt$l2Z&1T|>Dpm%TOTVt54G9<@1hFnxRQizwNuIW2>W6qQ7RXDcJ;i zyeIb6on!YmN=Puf4Q9Q%m=x}-st;;pXPRX3uBXj*?hoXn2^TFe1qH)1jY1hbXEyJ* z7JJ#Eh7%5)jayGP)K;h2kVQY_B2!jZFs&Y580##bV-KG)v9_oYq{J|6H}Jf zrV#>n+Y1?u!eI*FdM?4Pt#0fZwTjj8jh6Z0-Rg`Z<#DvVis+me?bY8Cbxt=kLeA*3 z+~-m*-$2r*OI7#NUaT1SMYWUj14qZo zJ;CwC_MMNRnPg29U@;ZDF;Y4lXRRwCe=HC2GtQz9hDdbXGBB63k)UB5 zIg@P$x@=>hrQB-0Uv5@#qlzDk`2(7d6h`}ok9r(;W)m3KVm7)`ZZ4Z#rN^iTXM z%!UiuRn5BI3K33Tb)2ofRiy5p{9?oOkP0)k`O*VeQCG>@6&N^vq^??#C8K#~{L%E& zmgkmJF|iV3%b(DPnoSZ + @@ -524,6 +525,24 @@ + + + + + + + + + + + + + + + + + + @@ -558,9 +577,9 @@ - + + - - + diff --git a/Riot/ViewController/RoomParticipantsViewController.m b/Riot/ViewController/RoomParticipantsViewController.m index 0fd839f48..9f762fbd0 100644 --- a/Riot/ViewController/RoomParticipantsViewController.m +++ b/Riot/ViewController/RoomParticipantsViewController.m @@ -601,7 +601,7 @@ // Add a plus icon to the contact cell in the contacts picker, in order to make it more understandable for the end user. contactsDataSource.contactCellAccessoryImage = [UIImage imageNamed:@"plus_icon"]; - // List all the participants by their matrix user id, or a room 3pid invite token to ignore them during the contacts search. + // List all the participants matrix user id to ignore them during the contacts search. for (Contact *contact in actualParticipants) { [contactsDataSource.ignoredContactsByMatrixId setObject:contact forKey:contact.mxMember.userId]; @@ -612,10 +612,6 @@ { [contactsDataSource.ignoredContactsByMatrixId setObject:contact forKey:contact.mxMember.userId]; } - else if (contact.mxThirdPartyInvite) - { - [contactsDataSource.ignoredContactsByMatrixId setObject:contact forKey:contact.mxThirdPartyInvite.token]; - } } if (userParticipant) { @@ -905,7 +901,7 @@ - (void)pushViewController:(UIViewController*)viewController { // Check whether the view controller is displayed inside a segmented one. - if (self.parentViewController) + if (self.parentViewController.navigationController) { // Hide back button title self.parentViewController.navigationItem.backBarButtonItem =[[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; diff --git a/Riot/ViewController/RoomViewController.h b/Riot/ViewController/RoomViewController.h index 45fd0f0bd..ac4c2ac11 100644 --- a/Riot/ViewController/RoomViewController.h +++ b/Riot/ViewController/RoomViewController.h @@ -23,9 +23,11 @@ #import "RoomParticipantsViewController.h" +#import "ContactsTableViewController.h" + #import "UIViewController+RiotSearch.h" -@interface RoomViewController : MXKRoomViewController +@interface RoomViewController : MXKRoomViewController // The expanded header @property (weak, nonatomic) IBOutlet UIView *expandedHeaderContainer; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 001c888b1..77460e2c0 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -906,7 +906,7 @@ } failure:^(NSError *error) { - NSLog(@"[Vector RoomVC] Join roomAlias (%@) failed", roomAlias); + NSLog(@"[RoomVC] Join roomAlias (%@) failed", roomAlias); //Alert user [[AppDelegate theDelegate] showErrorAsAlert:error]; @@ -1007,7 +1007,7 @@ if (previewHeader || (self.expandedHeaderContainer.isHidden == NO)) { // Here [destroy] is called before [viewWillDisappear:] - NSLog(@"[Vector RoomVC] destroyed whereas it is still visible"); + NSLog(@"[RoomVC] destroyed whereas it is still visible"); [previewHeader removeFromSuperview]; previewHeader = nil; @@ -1198,7 +1198,7 @@ // - if the event details view is displayed if (isVisible && (isSizeTransitionInProgress == YES || !self.roomDataSource || !self.roomDataSource.isLive || (self.roomDataSource.room.state.membership != MXMembershipJoin) || !self.splitViewController || encryptionInfoView.superview || eventDetailsView.superview)) { - NSLog(@"[Vector RoomVC] Show expanded header ignored"); + NSLog(@"[RoomVC] Show expanded header ignored"); return; } @@ -1279,7 +1279,7 @@ // or if the view controller is not embedded inside a split view controller yet. if (isVisible && (isSizeTransitionInProgress == YES || !self.splitViewController)) { - NSLog(@"[Vector RoomVC] Show preview header ignored"); + NSLog(@"[RoomVC] Show preview header ignored"); return; } @@ -1984,7 +1984,7 @@ __strong __typeof(weakSelf)strongSelf = weakSelf; [strongSelf stopActivityIndicator]; - NSLog(@"[Vector RoomVC] Redact event (%@) failed", selectedEvent.eventId); + NSLog(@"[RoomVC] Redact event (%@) failed", selectedEvent.eventId); //Alert user [[AppDelegate theDelegate] showErrorAsAlert:error]; @@ -2071,7 +2071,7 @@ __strong __typeof(weakSelf)strongSelf = weakSelf; [strongSelf stopActivityIndicator]; - NSLog(@"[Vector RoomVC] Ignore user (%@) failed", selectedEvent.sender); + NSLog(@"[RoomVC] Ignore user (%@) failed", selectedEvent.sender); //Alert user [[AppDelegate theDelegate] showErrorAsAlert:error]; @@ -2093,7 +2093,7 @@ __strong __typeof(weakSelf)strongSelf = weakSelf; [strongSelf stopActivityIndicator]; - NSLog(@"[Vector RoomVC] Report event (%@) failed", selectedEvent.eventId); + NSLog(@"[RoomVC] Report event (%@) failed", selectedEvent.eventId); //Alert user [[AppDelegate theDelegate] showErrorAsAlert:error]; @@ -2351,6 +2351,46 @@ unknownDevices = nil; } } + else if ([[segue identifier] isEqualToString:@"showContactPicker"]) + { + ContactsTableViewController *contactsPickerViewController = (ContactsTableViewController*)pushedViewController; + + // Set delegate to handle selected contact + contactsPickerViewController.contactsTableViewControllerDelegate = self; + + // Prepare its data source + ContactsDataSource *contactsDataSource = [[ContactsDataSource alloc] init]; + contactsDataSource.areSectionsShrinkable = YES; + contactsDataSource.displaySearchInputInContactsList = YES; + contactsDataSource.forceMatrixIdInDisplayName = YES; + // Add a plus icon to the contact cell in the contacts picker, in order to make it more understandable for the end user. + contactsDataSource.contactCellAccessoryImage = [UIImage imageNamed:@"plus_icon"]; + + // List all the participants matrix user id to ignore them during the contacts search. + MXSession* session = self.roomDataSource.mxSession; + NSString* roomId = self.roomDataSource.roomId; + MXRoom *room = [session roomWithRoomId:roomId]; + if (room) + { + NSArray *members = [room.state membersWithoutConferenceUser]; + + for (MXRoomMember *mxMember in members) + { + // Check his status + if (mxMember.membership == MXMembershipJoin || mxMember.membership == MXMembershipInvite) + { + // Create the contact related to this member + MXKContact *contact = [[MXKContact alloc] initMatrixContactWithDisplayName:mxMember.displayname andMatrixID:mxMember.userId]; + [contactsDataSource.ignoredContactsByMatrixId setObject:contact forKey:mxMember.userId]; + } + } + } + + [contactsPickerViewController showSearch:YES]; + contactsPickerViewController.searchBar.placeholder = NSLocalizedStringFromTable(@"room_participants_invite_another_user", @"Vector", nil); + + [contactsPickerViewController displayList:contactsDataSource]; + } // Hide back button title self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; @@ -2732,6 +2772,11 @@ selectedRoomDetailsIndex = 0; [self performSegueWithIdentifier:@"showRoomDetails" sender:self]; } + else if (tappedView == titleView.addParticipantMask) + { + // Open contact picker + [self performSegueWithIdentifier:@"showContactPicker" sender:self]; + } else if (tappedView == previewHeader.rightButton) { // 'Join' button has been pressed @@ -2820,7 +2865,7 @@ } failure:^(NSError *error) { [self stopActivityIndicator]; - NSLog(@"[Vector RoomVC] Failed to reject an invited room (%@) failed", self.roomDataSource.room.state.roomId); + NSLog(@"[RoomVC] Failed to reject an invited room (%@) failed", self.roomDataSource.room.state.roomId); }]; } @@ -3021,7 +3066,7 @@ { [roomActivitiesView displayOngoingConferenceCall:^(BOOL video) { - NSLog(@"[Vector RoomVC] onOngoingConferenceCallPressed"); + NSLog(@"[RoomVC] onOngoingConferenceCallPressed"); // Make sure there is not yet a call if (![customizedRoomDataSource.mxSession.callManager callInRoom:customizedRoomDataSource.roomId]) @@ -3621,5 +3666,118 @@ } } +#pragma mark - ContactsTableViewControllerDelegate + +- (void)contactsTableViewController:(ContactsTableViewController *)contactsTableViewController didSelectContact:(MXKContact*)contact +{ + __weak typeof(self) weakSelf = self; + + if (currentAlert) + { + [currentAlert dismiss:NO]; + currentAlert = nil; + } + + // Invite ? + NSString *promptMsg = [NSString stringWithFormat:NSLocalizedStringFromTable(@"room_participants_invite_prompt_msg", @"Vector", nil), contact.displayName]; + currentAlert = [[MXKAlert alloc] initWithTitle:NSLocalizedStringFromTable(@"room_participants_invite_prompt_title", @"Vector", nil) + message:promptMsg + style:MXKAlertStyleAlert]; + + currentAlert.cancelButtonIndex = [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] + style:MXKAlertActionStyleCancel + handler:^(MXKAlert *alert) { + + __strong __typeof(weakSelf)strongSelf = weakSelf; + strongSelf->currentAlert = nil; + + }]; + + [currentAlert addActionWithTitle:NSLocalizedStringFromTable(@"invite", @"Vector", nil) + style:MXKAlertActionStyleDefault + handler:^(MXKAlert *alert) { + + __strong __typeof(weakSelf)strongSelf = weakSelf; + strongSelf->currentAlert = nil; + + MXSession* session = strongSelf.roomDataSource.mxSession; + NSString* roomId = strongSelf.roomDataSource.roomId; + MXRoom *room = [session roomWithRoomId:roomId]; + + NSArray *identifiers = contact.matrixIdentifiers; + NSString *participantId; + + if (identifiers.count) + { + participantId = identifiers.firstObject; + + // Invite this user if a room is defined + [room inviteUser:participantId success:^{ + + // Refresh display by removing the contacts picker + [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; + + } failure:^(NSError *error) { + + NSLog(@"[RoomVC] Invite %@ failed", participantId); + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + else + { + if (contact.emailAddresses.count) + { + // This is a local contact, consider the first email by default. + // TODO: Prompt the user to select the right email. + MXKEmail *email = contact.emailAddresses.firstObject; + participantId = email.emailAddress; + } + else + { + // This is the text filled by the user. + participantId = contact.displayName; + } + + // Is it an email or a Matrix user ID? + if ([MXTools isEmailAddress:participantId]) + { + [room inviteUserByEmail:participantId success:^{ + + // Refresh display by removing the contacts picker + [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; + + } failure:^(NSError *error) { + + NSLog(@"[RoomVC] Invite be email %@ failed", participantId); + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + else //if ([MXTools isMatrixUserIdentifier:participantId]) + { + [room inviteUser:participantId success:^{ + + // Refresh display by removing the contacts picker + [contactsTableViewController withdrawViewControllerAnimated:YES completion:nil]; + + } failure:^(NSError *error) { + + NSLog(@"[RoomVC] Invite %@ failed", participantId); + // Alert user + [[AppDelegate theDelegate] showErrorAsAlert:error]; + + }]; + } + } + + }]; + + currentAlert.mxkAccessibilityIdentifier = @"RoomVCInviteAlert"; + [currentAlert showInViewController:self]; +} + @end diff --git a/Riot/Views/RoomTitle/ExpandedRoomTitleView.xib b/Riot/Views/RoomTitle/ExpandedRoomTitleView.xib index 200d52f66..78005181b 100644 --- a/Riot/Views/RoomTitle/ExpandedRoomTitleView.xib +++ b/Riot/Views/RoomTitle/ExpandedRoomTitleView.xib @@ -1,11 +1,12 @@ - - + + - + + @@ -47,12 +48,12 @@ - - + + - - + + @@ -67,9 +68,28 @@ - + + + + + + + + + + + + + + + + + + + + @@ -77,15 +97,18 @@ + - + + - + + @@ -97,16 +120,17 @@ + - + @@ -119,6 +143,7 @@ - + + diff --git a/Riot/Views/RoomTitle/RoomTitleView.h b/Riot/Views/RoomTitle/RoomTitleView.h index 36d9ee86f..23b198f5e 100644 --- a/Riot/Views/RoomTitle/RoomTitleView.h +++ b/Riot/Views/RoomTitle/RoomTitleView.h @@ -36,6 +36,7 @@ @property (weak, nonatomic) IBOutlet UIView *titleMask; @property (weak, nonatomic) IBOutlet UIView *roomDetailsMask; +@property (weak, nonatomic) IBOutlet UIView *addParticipantMask; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *displayNameCenterXConstraint; @property (weak, nonatomic) IBOutlet UIImageView *roomDetailsIconImageView; @@ -54,4 +55,4 @@ */ - (void)reportTapGesture:(UITapGestureRecognizer*)tapGestureRecognizer; -@end \ No newline at end of file +@end diff --git a/Riot/Views/RoomTitle/RoomTitleView.m b/Riot/Views/RoomTitle/RoomTitleView.m index cbe4b0278..2240da8e5 100644 --- a/Riot/Views/RoomTitle/RoomTitleView.m +++ b/Riot/Views/RoomTitle/RoomTitleView.m @@ -59,6 +59,16 @@ [self.roomDetailsMask addGestureRecognizer:tap]; self.roomDetailsMask.userInteractionEnabled = YES; } + + if (_addParticipantMask) + { + UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(reportTapGesture:)]; + [tap setNumberOfTouchesRequired:1]; + [tap setNumberOfTapsRequired:1]; + [tap setDelegate:self]; + [self.addParticipantMask addGestureRecognizer:tap]; + self.addParticipantMask.userInteractionEnabled = YES; + } } - (void)layoutSubviews From b7c035c0b0c2bf493516655cfae081632cb89e1d Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Tue, 20 Jun 2017 14:36:14 +0200 Subject: [PATCH 04/48] Bug Fix - Contact search: Unexpected empty search result. To reproduce: 1- search with "tst" pattern 2- erase the 2 last characters of the pattern to set "test" -> empty result NOK --- Riot/Model/Contact/ContactsDataSource.m | 27 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/Riot/Model/Contact/ContactsDataSource.m b/Riot/Model/Contact/ContactsDataSource.m index de0179948..5c880b241 100644 --- a/Riot/Model/Contact/ContactsDataSource.m +++ b/Riot/Model/Contact/ContactsDataSource.m @@ -148,14 +148,13 @@ { // Update search results. searchText = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSMutableArray *unfilteredLocalContacts; + NSMutableArray *unfilteredMatrixContacts; searchProcessingCount++; if (!searchText.length) { - searchProcessingLocalContacts = nil; - searchProcessingMatrixContacts = nil; - // Disclose by default the sections if a search was in progress. if (searchProcessingText.length) { @@ -164,18 +163,28 @@ } else if (forceRefresh || !searchProcessingText.length || [searchText hasPrefix:searchProcessingText] == NO) { - // Retrieve all the local contacts - searchProcessingLocalContacts = [self unfilteredLocalContactsArray]; - - // Retrieve all known matrix users - searchProcessingMatrixContacts = [self unfilteredMatrixContactsArray]; + // Prepare on the main thread the arrays used to initialize the search on the processing queue. + unfilteredLocalContacts = [self unfilteredLocalContactsArray]; + unfilteredMatrixContacts = [self unfilteredMatrixContactsArray]; // Disclose the sections shrinkedSectionsBitMask = 0; } dispatch_async(searchProcessingQueue, ^{ - + + // Reset the current arrays if it is required + if (!searchText.length) + { + searchProcessingLocalContacts = nil; + searchProcessingMatrixContacts = nil; + } + else if (unfilteredLocalContacts) + { + searchProcessingLocalContacts = unfilteredLocalContacts; + searchProcessingMatrixContacts = unfilteredMatrixContacts; + } + for (NSUInteger index = 0; index < searchProcessingLocalContacts.count;) { MXKContact* contact = searchProcessingLocalContacts[index]; From f64e87dee9ade6176cd6e490e8b5e593e26ab4b7 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Tue, 20 Jun 2017 17:06:12 +0200 Subject: [PATCH 05/48] Bug Fix - tap-on-tab should include the top-of-page location in its cycle of options #1316 --- Riot/ViewController/RecentsViewController.m | 25 +++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Riot/ViewController/RecentsViewController.m b/Riot/ViewController/RecentsViewController.m index 363255c33..913461394 100644 --- a/Riot/ViewController/RecentsViewController.m +++ b/Riot/ViewController/RecentsViewController.m @@ -327,10 +327,10 @@ [self prepareStickyHeaders]; // In case of split view controller where the primary and secondary view controllers are displayed side-by-side on screen, - // the selected room (if any) is updated and kept visible. + // the selected room (if any) is updated. if (!self.splitViewController.isCollapsed) { - [self refreshCurrentSelectedCell:YES]; + [self refreshCurrentSelectedCell:NO]; } } @@ -1662,10 +1662,27 @@ - (void)scrollToTheTopTheNextRoomWithMissedNotificationsInSection:(NSInteger)section { - UITableViewCell *firstVisibleCell = self.recentsTableView.visibleCells.firstObject; + UITableViewCell *firstVisibleCell; + NSIndexPath *firstVisibleCellIndexPath; + + UIView *firstSectionHeader = displayedSectionHeaders.firstObject; + + if (firstSectionHeader && firstSectionHeader.frame.origin.y <= self.recentsTableView.contentOffset.y) + { + // Compute the height of the hidden part of the section header. + CGFloat hiddenPart = self.recentsTableView.contentOffset.y - firstSectionHeader.frame.origin.y; + CGFloat firstVisibleCellPosY = self.recentsTableView.contentOffset.y + (firstSectionHeader.frame.size.height - hiddenPart); + firstVisibleCellIndexPath = [self.recentsTableView indexPathForRowAtPoint:CGPointMake(0, firstVisibleCellPosY)]; + firstVisibleCell = [self.recentsTableView cellForRowAtIndexPath:firstVisibleCellIndexPath]; + } + else + { + firstVisibleCell = self.recentsTableView.visibleCells.firstObject; + firstVisibleCellIndexPath = [self.recentsTableView indexPathForCell:firstVisibleCell]; + } + if (firstVisibleCell) { - NSIndexPath *firstVisibleCellIndexPath = [self.recentsTableView indexPathForCell:firstVisibleCell]; NSInteger nextCellRow = (firstVisibleCellIndexPath.section == section) ? firstVisibleCellIndexPath.row + 1 : 0; // Look for the next room with missed notifications. From b6383823d985e2ceb9547e25ca23caaa8ef45b68 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Tue, 20 Jun 2017 17:13:55 +0200 Subject: [PATCH 06/48] Add sanity check --- Riot/ViewController/RoomViewController.m | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 77460e2c0..c966e2dfc 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -3688,8 +3688,11 @@ style:MXKAlertActionStyleCancel handler:^(MXKAlert *alert) { - __strong __typeof(weakSelf)strongSelf = weakSelf; - strongSelf->currentAlert = nil; + if (weakSelf) + { + __strong __typeof(weakSelf)strongSelf = weakSelf; + strongSelf->currentAlert = nil; + } }]; @@ -3697,6 +3700,12 @@ style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) { + // Sanity check + if (!weakSelf) + { + return; + } + __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf->currentAlert = nil; From a4eaa453f48473ef3b90dd27bbfd7d465f24945d Mon Sep 17 00:00:00 2001 From: Denis Morozov Date: Tue, 20 Jun 2017 19:35:08 +0300 Subject: [PATCH 07/48] Fix crash on decline button --- Riot/AppDelegate.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 72c8def0f..85e83064d 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -2314,7 +2314,6 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Release properly [currentCallViewController destroy]; - currentCallViewController = nil; if (completion) { From 1e1b6e05fd5c34cabbb0eee64f1b50ae4500d6a6 Mon Sep 17 00:00:00 2001 From: Denis Morozov Date: Tue, 20 Jun 2017 21:36:51 +0300 Subject: [PATCH 08/48] Remove unnecessary construction methods from CallViewController --- Riot/ViewController/CallViewController.m | 31 ------------------------ 1 file changed, 31 deletions(-) diff --git a/Riot/ViewController/CallViewController.m b/Riot/ViewController/CallViewController.m index 8075d22ac..559ec4ff0 100644 --- a/Riot/ViewController/CallViewController.m +++ b/Riot/ViewController/CallViewController.m @@ -40,37 +40,6 @@ @implementation CallViewController -#pragma mark - Class methods - -+ (UINib *)nib -{ - return [UINib nibWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; -} - -+ (instancetype)callViewController:(MXCall*)call -{ - CallViewController* instance = [[[self class] alloc] initWithNibName:NSStringFromClass(self.class) - bundle:[NSBundle bundleForClass:self.class]]; - - // Load the view controller's view now (buttons and views will then be available). - if ([instance respondsToSelector:@selector(loadViewIfNeeded)]) - { - // iOS 9 and later - [instance loadViewIfNeeded]; - } - else if (instance.view) - { - // Patch: on iOS < 9.0, we load the view by calling its getter. - } - - instance.mxCall = call; - - return instance; -} - -#pragma mark - - - (void)finalizeInit { [super finalizeInit]; From 214baba2e2ecff6afda274c75b810797548e58a5 Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 21 Jun 2017 14:45:43 +0200 Subject: [PATCH 09/48] Fix: Room directory: stuck after the 20 first items #1329 --- Riot/ViewController/RoomsViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/ViewController/RoomsViewController.m b/Riot/ViewController/RoomsViewController.m index 53bb95b3d..3c14fc45f 100644 --- a/Riot/ViewController/RoomsViewController.m +++ b/Riot/ViewController/RoomsViewController.m @@ -268,9 +268,9 @@ - (void)triggerDirectoryPagination { - if (recentsDataSource.publicRoomsDirectoryDataSource.hasReachedPaginationEnd || footerSpinnerView) + if (!recentsDataSource || recentsDataSource.publicRoomsDirectoryDataSource.hasReachedPaginationEnd || footerSpinnerView) { - // We got all public rooms or we are already paginating + // We are not yet ready or we got all public rooms or we are already paginating // Do nothing return; } From adc9bbfc5acfa850af1154bab82e4c625a3f538f Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 21 Jun 2017 15:58:37 +0200 Subject: [PATCH 10/48] Fix: Room directory: "No public rooms available" is displayed while loading #1336 --- .../RoomList/PublicRoomsDirectoryDataSource.m | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.m b/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.m index 690996d0c..327d13a12 100644 --- a/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.m +++ b/Riot/Model/RoomList/PublicRoomsDirectoryDataSource.m @@ -214,6 +214,10 @@ double const kPublicRoomsDirectoryDataExpiration = 10; { if (_hasReachedPaginationEnd) { + if (complete) + { + complete(0); + } return nil; } @@ -338,14 +342,22 @@ double const kPublicRoomsDirectoryDataExpiration = 10; tableViewCell.textLabel.font = [UIFont systemFontOfSize:15.0]; tableViewCell.selectionStyle = UITableViewCellSelectionStyleNone; } - - if (_searchPattern.length) + + if (state == MXKDataSourceStateReady) { - tableViewCell.textLabel.text = NSLocalizedStringFromTable(@"search_no_result", @"Vector", nil); + if (_searchPattern.length) + { + tableViewCell.textLabel.text = NSLocalizedStringFromTable(@"search_no_result", @"Vector", nil); + } + else + { + tableViewCell.textLabel.text = NSLocalizedStringFromTable(@"room_directory_no_public_room", @"Vector", nil); + } } else { - tableViewCell.textLabel.text = NSLocalizedStringFromTable(@"room_directory_no_public_room", @"Vector", nil); + // Show nothing while loading and in other cases + tableViewCell.textLabel.text = @""; } return tableViewCell; From 45a03f8492cbd00fbfbe43ba04195d7ecd1cc7bb Mon Sep 17 00:00:00 2001 From: manuroe Date: Wed, 21 Jun 2017 16:00:05 +0200 Subject: [PATCH 11/48] Fix crash: Room directory: Clicking on "No public rooms available" make the app crash --- Riot/ViewController/RoomsViewController.m | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Riot/ViewController/RoomsViewController.m b/Riot/ViewController/RoomsViewController.m index 53bb95b3d..4c0c16d44 100644 --- a/Riot/ViewController/RoomsViewController.m +++ b/Riot/ViewController/RoomsViewController.m @@ -210,7 +210,12 @@ { if (indexPath.section == recentsDataSource.directorySection) { - [self openPublicRoomAtIndexPath:indexPath]; + // Sanity check + MXPublicRoom *publicRoom = [recentsDataSource.publicRoomsDirectoryDataSource roomAtIndexPath:indexPath]; + if (publicRoom) + { + [self openPublicRoomAtIndexPath:indexPath]; + } } else { From b93842f35a637ab540015df36d6eb248d8b1834a Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Wed, 21 Jun 2017 22:28:16 +0400 Subject: [PATCH 12/48] Created ReadReceiptsViewController --- Riot.xcodeproj/project.pbxproj | 12 +- Riot/Model/Room/RoomDataSource.m | 10 +- .../ReadReceiptsViewController.h | 24 +++ .../ReadReceiptsViewController.m | 137 ++++++++++++++++++ .../ReadReceiptsViewController.xib | 88 +++++++++++ Riot/ViewController/RoomViewController.h | 2 +- Riot/ViewController/RoomViewController.m | 9 ++ 7 files changed, 279 insertions(+), 3 deletions(-) create mode 100644 Riot/ViewController/ReadReceiptsViewController.h create mode 100644 Riot/ViewController/ReadReceiptsViewController.m create mode 100644 Riot/ViewController/ReadReceiptsViewController.xib diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index c767c6dd3..064e13a62 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 24B5103E1EFA7083004C6AD2 /* ReadReceiptsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 24B5103D1EFA7083004C6AD2 /* ReadReceiptsViewController.m */; }; + 24B510401EFA88CC004C6AD2 /* ReadReceiptsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 24B5103F1EFA88CC004C6AD2 /* ReadReceiptsViewController.xib */; }; 3205ED7D1E976C8A003D65FA /* DirectoryServerPickerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3205ED7C1E976C8A003D65FA /* DirectoryServerPickerViewController.m */; }; 3205ED841E97725E003D65FA /* DirectoryServerTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 3205ED821E97725E003D65FA /* DirectoryServerTableViewCell.m */; }; 3205ED851E97725E003D65FA /* DirectoryServerTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3205ED831E97725E003D65FA /* DirectoryServerTableViewCell.xib */; }; @@ -468,6 +470,9 @@ /* Begin PBXFileReference section */ 1129C74A281B080432B1A1A1 /* 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 = ""; }; + 24B5103C1EFA7083004C6AD2 /* ReadReceiptsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ReadReceiptsViewController.h; sourceTree = ""; }; + 24B5103D1EFA7083004C6AD2 /* ReadReceiptsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ReadReceiptsViewController.m; sourceTree = ""; }; + 24B5103F1EFA88CC004C6AD2 /* ReadReceiptsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ReadReceiptsViewController.xib; 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 = ""; }; @@ -1576,6 +1581,9 @@ F083BC541E7009EC00A9B29C /* StartChatViewController.xib */, F083BC551E7009EC00A9B29C /* UsersDevicesViewController.h */, F083BC561E7009EC00A9B29C /* UsersDevicesViewController.m */, + 24B5103C1EFA7083004C6AD2 /* ReadReceiptsViewController.h */, + 24B5103D1EFA7083004C6AD2 /* ReadReceiptsViewController.m */, + 24B5103F1EFA88CC004C6AD2 /* ReadReceiptsViewController.xib */, ); path = ViewController; sourceTree = ""; @@ -2032,6 +2040,7 @@ F083BD881E7009ED00A9B29C /* file_doc_icon@3x.png in Resources */, F083BDAB1E7009ED00A9B29C /* placeholder@2x.png in Resources */, F083BE4D1E7009ED00A9B29C /* RoomOutgoingEncryptedAttachmentWithoutSenderInfoBubbleCell.xib in Resources */, + 24B510401EFA88CC004C6AD2 /* ReadReceiptsViewController.xib in Resources */, F083BDC81E7009ED00A9B29C /* search_icon.png in Resources */, F083BDE91E7009ED00A9B29C /* ring.mp3 in Resources */, F083BE431E7009ED00A9B29C /* RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.xib in Resources */, @@ -2393,7 +2402,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -2445,6 +2454,7 @@ F083BE761E7009ED00A9B29C /* RoomOutgoingTextMsgWithPaginationTitleBubbleCell.m in Sources */, F083BE5A1E7009ED00A9B29C /* RoomIncomingAttachmentBubbleCell.m in Sources */, F083BE541E7009ED00A9B29C /* RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m in Sources */, + 24B5103E1EFA7083004C6AD2 /* ReadReceiptsViewController.m in Sources */, F083BE621E7009ED00A9B29C /* RoomIncomingTextMsgWithoutSenderInfoBubbleCell.m in Sources */, F083BE881E7009ED00A9B29C /* RoomMemberTitleView.m in Sources */, F083BE701E7009ED00A9B29C /* RoomOutgoingTextMsgBubbleCell.m in Sources */, diff --git a/Riot/Model/Room/RoomDataSource.m b/Riot/Model/Room/RoomDataSource.m index 4a1a71d77..f4a01c719 100644 --- a/Riot/Model/Room/RoomDataSource.m +++ b/Riot/Model/Room/RoomDataSource.m @@ -17,6 +17,7 @@ #import "RoomDataSource.h" +#import "AppDelegate.h" #import "EventFormatter.h" #import "RoomBubbleCellData.h" @@ -148,7 +149,7 @@ // Some vertical whitespaces are added in message text view (see RoomBubbleCellData class) to insert correctly multiple receipts. bubbleCell.bubbleOverlayContainer.backgroundColor = [UIColor clearColor]; bubbleCell.bubbleOverlayContainer.alpha = 1; - bubbleCell.bubbleOverlayContainer.userInteractionEnabled = NO; + bubbleCell.bubbleOverlayContainer.userInteractionEnabled = YES; bubbleCell.bubbleOverlayContainer.hidden = NO; NSInteger index = bubbleComponents.count; @@ -166,6 +167,7 @@ NSArray* receipts = [self.room getEventReceipts:component.event.eventId sorted:YES]; NSMutableArray *roomMembers; NSMutableArray *placeholders; + NSMutableArray *receiptDescriptions; // Check whether some receipts are found if (receipts.count) @@ -173,6 +175,9 @@ // Retrieve the corresponding room members roomMembers = [[NSMutableArray alloc] initWithCapacity:receipts.count]; placeholders = [[NSMutableArray alloc] initWithCapacity:receipts.count]; + receiptDescriptions = [[NSMutableArray alloc] initWithCapacity:receipts.count]; + + MXKEventFormatter *formatter = (MXKEventFormatter*)self.mxSession.roomSummaryUpdateDelegate; for (MXReceiptData* data in receipts) { @@ -181,6 +186,7 @@ { [roomMembers addObject:roomMember]; [placeholders addObject:[AvatarGenerator generateAvatarForMatrixItem:roomMember.userId withDisplayName:roomMember.displayname]]; + [receiptDescriptions addObject:[formatter dateStringFromTimestamp:data.ts withTime:YES]]; } } } @@ -190,6 +196,7 @@ { // Define the read receipts container, positioned on the right border of the bubble cell (Note the right margin 6 pts). MXKReceiptSendersContainer* avatarsContainer = [[MXKReceiptSendersContainer alloc] initWithFrame:CGRectMake(bubbleCell.frame.size.width - 156, bottomPositionY - 13, 150, 12) andRestClient:self.mxSession.matrixRestClient]; + avatarsContainer.delegate = [AppDelegate theDelegate].masterTabBarController.currentRoomViewController; // Custom avatar display avatarsContainer.maxDisplayedAvatars = 5; @@ -199,6 +206,7 @@ avatarsContainer.tag = index; [avatarsContainer refreshReceiptSenders:roomMembers withPlaceHolders:placeholders andAlignment:ReadReceiptAlignmentRight]; + avatarsContainer.recieptDescriptions = receiptDescriptions; avatarsContainer.translatesAutoresizingMaskIntoConstraints = NO; avatarsContainer.accessibilityIdentifier = @"readReceiptsContainer"; diff --git a/Riot/ViewController/ReadReceiptsViewController.h b/Riot/ViewController/ReadReceiptsViewController.h new file mode 100644 index 000000000..d8a7ad9b9 --- /dev/null +++ b/Riot/ViewController/ReadReceiptsViewController.h @@ -0,0 +1,24 @@ +/* + 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 +#import + +@interface ReadReceiptsViewController : UIViewController + ++ (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receiptDescriptions:(NSArray *)receiptDescriptions; + +@end diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m new file mode 100644 index 000000000..768024156 --- /dev/null +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -0,0 +1,137 @@ +/* + 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 "ReadReceiptsViewController.h" +#import + +@interface ReadReceiptsViewController () + +@property (nonatomic) MXRestClient* restClient; + +@property (nonatomic) NSArray *roomMembers; +@property (nonatomic) NSArray *placeholders; +@property (nonatomic) NSArray *recieptDescriptions; + +@property (weak, nonatomic) IBOutlet UIView *overlayView; +@property (weak, nonatomic) IBOutlet UILabel *titleLabel; +@property (weak, nonatomic) IBOutlet UIView *containerView; +@property (nonatomic) IBOutlet UITableView *receiptsTableView; + +@end + +@implementation ReadReceiptsViewController + +#pragma mark - Public + ++ (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receiptDescriptions:(NSArray *)receiptDescriptions +{ + ReadReceiptsViewController *receiptsController = [[[self class] alloc] initWithNibName:NSStringFromClass([self class]) bundle:nil]; + receiptsController.restClient = restClient; + receiptsController.roomMembers = roomMembers; + receiptsController.placeholders = placeholders; + receiptsController.recieptDescriptions = receiptDescriptions; + + receiptsController.providesPresentationContextTransitionStyle = YES; + receiptsController.definesPresentationContext = YES; + receiptsController.modalPresentationStyle = UIModalPresentationOverFullScreen; + receiptsController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; + + [viewController presentViewController:receiptsController animated:YES completion:nil]; +} + +#pragma mark - Lifecycle + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self configureViews]; + [self configureReceiptsTableView]; + [self addOverlayViewGesture]; +} + +- (void)didReceiveMemoryWarning +{ + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +#pragma mark - Views + +- (void)configureViews { + self.containerView.layer.cornerRadius = 20; + self.titleLabel.text = @"Read Receipts List"; +} + +- (void)configureReceiptsTableView { + self.receiptsTableView.dataSource = self; + [self.receiptsTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"id"]; + + //[self.receiptsTableView registerNib:[MXKReadReceiptTableview nib] forCellReuseIdentifier:@"s"]; + +} + +- (void)addOverlayViewGesture { + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(overlayTap)]; + [tapRecognizer setNumberOfTapsRequired:1]; + [tapRecognizer setNumberOfTouchesRequired:1]; + [self.overlayView addGestureRecognizer:tapRecognizer]; +} + +#pragma mark - Actions + +- (void)overlayTap { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return self.roomMembers.count; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return 1; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"id" forIndexPath:indexPath]; + + //configure cell + //add custom cell later + if (indexPath.row < self.roomMembers.count) + { + cell.textLabel.text = self.roomMembers[indexPath.row].displayname; + } + if (indexPath.row < self.placeholders.count) + { + [cell.imageView setImage:self.placeholders[indexPath.row]]; + } + if (indexPath.row < self.recieptDescriptions.count) + { + cell.detailTextLabel.text = self.recieptDescriptions[indexPath.row]; + } + + cell.selectionStyle = UITableViewCellSelectionStyleNone; + + return cell; +} + + +@end diff --git a/Riot/ViewController/ReadReceiptsViewController.xib b/Riot/ViewController/ReadReceiptsViewController.xib new file mode 100644 index 000000000..23813ac75 --- /dev/null +++ b/Riot/ViewController/ReadReceiptsViewController.xib @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Riot/ViewController/RoomViewController.h b/Riot/ViewController/RoomViewController.h index 45fd0f0bd..0dcdd0bda 100644 --- a/Riot/ViewController/RoomViewController.h +++ b/Riot/ViewController/RoomViewController.h @@ -25,7 +25,7 @@ #import "UIViewController+RiotSearch.h" -@interface RoomViewController : MXKRoomViewController +@interface RoomViewController : MXKRoomViewController // The expanded header @property (weak, nonatomic) IBOutlet UIView *expandedHeaderContainer; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 47a74d510..bfdc50054 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -45,6 +45,8 @@ #import "UsersDevicesViewController.h" +#import "ReadReceiptsViewController.h" + #import "RoomEmptyBubbleCell.h" #import "RoomIncomingTextMsgBubbleCell.h" @@ -2817,6 +2819,13 @@ } } +#pragma mark - MXKRecieptSendersContainerDelegate + +- (void)didTapReceiptsContainerWithRestClient:(MXRestClient *)restClient RoomMembers:(NSArray *)roomMembers avatars:(NSArray *)avatars recieptDescriptions:(NSArray *)recieptDescriptions { + NSLog(@"!!! SHOW RR DETAILS !!!"); + [ReadReceiptsViewController openInViewController:self withRestClient:restClient withRoomMembers:roomMembers placeholders:avatars receiptDescriptions:recieptDescriptions]; +} + #pragma mark - Typing management - (void)removeTypingNotificationsListener From fc00728b063f3b951f50af7f782cf9651c93ca73 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 22 Jun 2017 01:45:11 +0400 Subject: [PATCH 13/48] Modified ReadReceiptsViewController to download resized avatars of users. If the user doesn't have an avatar, the placeholder is placed. --- .../ReadReceiptsViewController.m | 47 +++++++++++++------ .../ReadReceiptsViewController.xib | 8 ++-- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index 768024156..aead5f706 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -17,7 +17,7 @@ #import "ReadReceiptsViewController.h" #import -@interface ReadReceiptsViewController () +@interface ReadReceiptsViewController () @property (nonatomic) MXRestClient* restClient; @@ -28,7 +28,7 @@ @property (weak, nonatomic) IBOutlet UIView *overlayView; @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @property (weak, nonatomic) IBOutlet UIView *containerView; -@property (nonatomic) IBOutlet UITableView *receiptsTableView; +@property (weak, nonatomic) IBOutlet UITableView *receiptsTableView; @end @@ -71,20 +71,24 @@ #pragma mark - Views -- (void)configureViews { +- (void)configureViews +{ self.containerView.layer.cornerRadius = 20; self.titleLabel.text = @"Read Receipts List"; } -- (void)configureReceiptsTableView { +- (void)configureReceiptsTableView +{ self.receiptsTableView.dataSource = self; - [self.receiptsTableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"id"]; - - //[self.receiptsTableView registerNib:[MXKReadReceiptTableview nib] forCellReuseIdentifier:@"s"]; + self.receiptsTableView.delegate = self; + self.receiptsTableView.showsVerticalScrollIndicator = NO; + self.receiptsTableView.separatorStyle = UITableViewCellSeparatorStyleNone; + [self.receiptsTableView registerNib:[MXKReadReceiptTableViewCell nib] forCellReuseIdentifier:[MXKReadReceiptTableViewCell defaultReuseIdentifier]]; } -- (void)addOverlayViewGesture { +- (void)addOverlayViewGesture +{ UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(overlayTap)]; [tapRecognizer setNumberOfTapsRequired:1]; [tapRecognizer setNumberOfTouchesRequired:1]; @@ -111,21 +115,29 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"id" forIndexPath:indexPath]; + MXKReadReceiptTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[MXKReadReceiptTableViewCell defaultReuseIdentifier] forIndexPath:indexPath]; - //configure cell - //add custom cell later if (indexPath.row < self.roomMembers.count) { - cell.textLabel.text = self.roomMembers[indexPath.row].displayname; + NSString *name = self.roomMembers[indexPath.row].displayname; + if (name.length == 0) { + name = self.roomMembers[indexPath.row].userId; + } + cell.displayNameLabel.text = name; } if (indexPath.row < self.placeholders.count) { - [cell.imageView setImage:self.placeholders[indexPath.row]]; + NSString *avatarUrl = self.roomMembers[indexPath.row].avatarUrl; + if (self.restClient && avatarUrl) + { + CGFloat side = CGRectGetWidth(cell.imageView.frame); + avatarUrl = [self.restClient urlOfContentThumbnail:avatarUrl toFitViewSize:CGSizeMake(side, side) withMethod:MXThumbnailingMethodCrop]; + } + [cell.avatarImageView setImageURL:avatarUrl withType:nil andImageOrientation:UIImageOrientationUp previewImage:self.placeholders[indexPath.row]]; } if (indexPath.row < self.recieptDescriptions.count) { - cell.detailTextLabel.text = self.recieptDescriptions[indexPath.row]; + cell.receiptDescriptionLabel.text = self.recieptDescriptions[indexPath.row]; } cell.selectionStyle = UITableViewCellSelectionStyleNone; @@ -133,5 +145,12 @@ return cell; } +#pragma mark - UITableViewDelegate + +- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + return 70; +} + @end diff --git a/Riot/ViewController/ReadReceiptsViewController.xib b/Riot/ViewController/ReadReceiptsViewController.xib index 23813ac75..26b0926e7 100644 --- a/Riot/ViewController/ReadReceiptsViewController.xib +++ b/Riot/ViewController/ReadReceiptsViewController.xib @@ -28,7 +28,7 @@ - + @@ -48,7 +48,7 @@ - + @@ -59,10 +59,10 @@ - + - + From 5d12ae36fa849000fe95b85382f5abf60961b953 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 22 Jun 2017 02:03:03 +0400 Subject: [PATCH 14/48] Minor changes --- Riot/ViewController/ReadReceiptsViewController.m | 3 ++- Riot/ViewController/RoomViewController.m | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index aead5f706..e87329baa 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -97,7 +97,8 @@ #pragma mark - Actions -- (void)overlayTap { +- (void)overlayTap +{ [self dismissViewControllerAnimated:YES completion:nil]; } diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index b89b2ffe2..3628c0324 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2876,8 +2876,8 @@ #pragma mark - MXKRecieptSendersContainerDelegate -- (void)didTapReceiptsContainerWithRestClient:(MXRestClient *)restClient RoomMembers:(NSArray *)roomMembers avatars:(NSArray *)avatars recieptDescriptions:(NSArray *)recieptDescriptions { - NSLog(@"!!! SHOW RR DETAILS !!!"); +- (void)didTapReceiptsContainerWithRestClient:(MXRestClient *)restClient RoomMembers:(NSArray *)roomMembers avatars:(NSArray *)avatars recieptDescriptions:(NSArray *)recieptDescriptions +{ [ReadReceiptsViewController openInViewController:self withRestClient:restClient withRoomMembers:roomMembers placeholders:avatars receiptDescriptions:recieptDescriptions]; } From 1f5213ae1bbbe10b89ce4d748f7c5882aba37b4c Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 22 Jun 2017 10:14:57 +0200 Subject: [PATCH 15/48] Bug Fix - Crash when hitting a room header after some special steps #1340 --- Riot/Base.lproj/Main.storyboard | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Riot/Base.lproj/Main.storyboard b/Riot/Base.lproj/Main.storyboard index abe200650..1a75e072a 100644 --- a/Riot/Base.lproj/Main.storyboard +++ b/Riot/Base.lproj/Main.storyboard @@ -87,6 +87,7 @@ + @@ -154,7 +155,7 @@ - + @@ -397,7 +398,7 @@ - + @@ -577,9 +578,10 @@ - - + - + + + From 1e3e68c01b46bb790db9a1f76f2487a136fc46c2 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 22 Jun 2017 10:51:40 +0200 Subject: [PATCH 16/48] Bug Fix - Chat screen: the search icon is missing after switching in live from a non live timeline #1344 --- Riot/Base.lproj/Main.storyboard | 1 - Riot/ViewController/RoomViewController.m | 13 ++++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Riot/Base.lproj/Main.storyboard b/Riot/Base.lproj/Main.storyboard index 1a75e072a..9fa6fc495 100644 --- a/Riot/Base.lproj/Main.storyboard +++ b/Riot/Base.lproj/Main.storyboard @@ -81,7 +81,6 @@ - diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index c966e2dfc..336144da9 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -162,6 +162,9 @@ // Tell whether the view controller is appeared or not. BOOL isAppeared; + + // The search bar buttom item back up. + UIBarButtonItem *searchBarButtonItem; } @end @@ -970,6 +973,7 @@ - (void)destroy { + searchBarButtonItem = nil; self.navigationItem.rightBarButtonItem.enabled = NO; if (currentAlert) @@ -1067,6 +1071,12 @@ - (void)refreshRoomTitle { + if (searchBarButtonItem && !self.navigationItem.rightBarButtonItem) + { + // Restore by default the search bar button. + self.navigationItem.rightBarButtonItem = searchBarButtonItem; + } + // Set the right room title view if (self.isRoomPreview) { @@ -1101,7 +1111,8 @@ } else { - // Hide the search button + // Remove the search button temporarily + searchBarButtonItem = self.navigationItem.rightBarButtonItem; self.navigationItem.rightBarButtonItem = nil; [self setRoomTitleViewClass:SimpleRoomTitleView.class]; From 74e71fc4006a66f81cb7c9e0d4d5c8b0b5733cda Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 22 Jun 2017 11:09:17 +0200 Subject: [PATCH 17/48] Fix: Crash when hitting room from unified search/browse directory #1342 --- Riot/ViewController/RoomViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index c966e2dfc..9bfd51f48 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2602,7 +2602,7 @@ [self checkReadMarkerVisibility]; // Switch back to the live mode when the user scrolls to the bottom of the non live timeline. - if (!self.roomDataSource.isLive) + if (!self.roomDataSource.isLive && ![self isRoomPreview]) { CGFloat contentBottomPosY = self.bubblesTableView.contentOffset.y + self.bubblesTableView.frame.size.height - self.bubblesTableView.contentInset.bottom; if (contentBottomPosY >= self.bubblesTableView.contentSize.height && ![self.roomDataSource.timeline canPaginate:MXTimelineDirectionForwards]) From f4471cd7b4de26df1157dd7b65b8bb5a870d56d2 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 22 Jun 2017 14:40:11 +0200 Subject: [PATCH 18/48] Bug Fix - Home: tapping on an unread room on home page takes you to the wrong room #1304 --- Riot/ViewController/HomeViewController.m | 6 ++++-- Riot/Views/RoomList/RoomCollectionViewCell.m | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Riot/ViewController/HomeViewController.m b/Riot/ViewController/HomeViewController.m index 1bb6685bd..1570e310b 100644 --- a/Riot/ViewController/HomeViewController.m +++ b/Riot/ViewController/HomeViewController.m @@ -240,9 +240,11 @@ { if (self.delegate) { - id cellData = [recentsDataSource cellDataAtIndexPath:[NSIndexPath indexPathForRow:indexPath.item inSection:collectionView.tag]]; + RoomCollectionViewCell *roomCollectionViewCell = (RoomCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath]; - [self.delegate recentListViewController:self didSelectRoom:cellData.roomSummary.roomId inMatrixSession:cellData.roomSummary.room.mxSession]; + id renderedCellData = (id)roomCollectionViewCell.renderedCellData; + + [self.delegate recentListViewController:self didSelectRoom:renderedCellData.roomSummary.roomId inMatrixSession:renderedCellData.roomSummary.room.mxSession]; } // Hide the keyboard when user select a room diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index bdfcc1fc3..f37cbcb68 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -54,8 +54,6 @@ - (void)layoutSubviews { [super layoutSubviews]; - - } - (void)render:(MXKCellData *)cellData @@ -137,6 +135,11 @@ } } +- (MXKCellData*)renderedCellData +{ + return roomCellData; +} + + (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth { // The height is fixed From cfae3b5f7162da0a7108e46a9b6487fb8d99684e Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 22 Jun 2017 14:43:21 +0200 Subject: [PATCH 19/48] Revert "Bug Fix - Home: tapping on an unread room on home page takes you to the wrong room" This reverts commit 06a7712d46f348cdd287d73eda40632fc5419531. --- Riot/ViewController/HomeViewController.m | 6 ++---- Riot/Views/RoomList/RoomCollectionViewCell.m | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/Riot/ViewController/HomeViewController.m b/Riot/ViewController/HomeViewController.m index 1570e310b..1bb6685bd 100644 --- a/Riot/ViewController/HomeViewController.m +++ b/Riot/ViewController/HomeViewController.m @@ -240,11 +240,9 @@ { if (self.delegate) { - RoomCollectionViewCell *roomCollectionViewCell = (RoomCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath]; + id cellData = [recentsDataSource cellDataAtIndexPath:[NSIndexPath indexPathForRow:indexPath.item inSection:collectionView.tag]]; - id renderedCellData = (id)roomCollectionViewCell.renderedCellData; - - [self.delegate recentListViewController:self didSelectRoom:renderedCellData.roomSummary.roomId inMatrixSession:renderedCellData.roomSummary.room.mxSession]; + [self.delegate recentListViewController:self didSelectRoom:cellData.roomSummary.roomId inMatrixSession:cellData.roomSummary.room.mxSession]; } // Hide the keyboard when user select a room diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index f37cbcb68..bdfcc1fc3 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -54,6 +54,8 @@ - (void)layoutSubviews { [super layoutSubviews]; + + } - (void)render:(MXKCellData *)cellData @@ -135,11 +137,6 @@ } } -- (MXKCellData*)renderedCellData -{ - return roomCellData; -} - + (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth { // The height is fixed From 230d15889a3819e479b5262a5501f1dc02699146 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 22 Jun 2017 15:03:30 +0200 Subject: [PATCH 20/48] Bug Fix - Home: tapping on an unread room on home page takes you to the wrong room #1304 --- Riot/Views/RoomList/RoomCollectionViewCell.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index f37cbcb68..59358e606 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -161,6 +161,8 @@ [self removeGestureRecognizer:self.gestureRecognizers[0]]; } self.tag = -1; + + roomCellData = nil; } @end From eb7c699e4a75852d7b6b51c1e314eed4c6aa8a35 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 22 Jun 2017 15:17:24 +0200 Subject: [PATCH 21/48] Bug Fix - Home: tapping on an unread room on home page takes you to the wrong room #1304 --- Riot/ViewController/HomeViewController.m | 6 ++++-- Riot/Views/RoomList/RoomCollectionViewCell.m | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Riot/ViewController/HomeViewController.m b/Riot/ViewController/HomeViewController.m index 1bb6685bd..1570e310b 100644 --- a/Riot/ViewController/HomeViewController.m +++ b/Riot/ViewController/HomeViewController.m @@ -240,9 +240,11 @@ { if (self.delegate) { - id cellData = [recentsDataSource cellDataAtIndexPath:[NSIndexPath indexPathForRow:indexPath.item inSection:collectionView.tag]]; + RoomCollectionViewCell *roomCollectionViewCell = (RoomCollectionViewCell*)[collectionView cellForItemAtIndexPath:indexPath]; - [self.delegate recentListViewController:self didSelectRoom:cellData.roomSummary.roomId inMatrixSession:cellData.roomSummary.room.mxSession]; + id renderedCellData = (id)roomCollectionViewCell.renderedCellData; + + [self.delegate recentListViewController:self didSelectRoom:renderedCellData.roomSummary.roomId inMatrixSession:renderedCellData.roomSummary.room.mxSession]; } // Hide the keyboard when user select a room diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index c22a48ccc..59358e606 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -54,8 +54,6 @@ - (void)layoutSubviews { [super layoutSubviews]; - - } - (void)render:(MXKCellData *)cellData @@ -137,6 +135,11 @@ } } +- (MXKCellData*)renderedCellData +{ + return roomCellData; +} + + (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth { // The height is fixed From 9f956c7f06da5f7bc2026b5607680176541a5727 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 23 Jun 2017 11:44:13 +0200 Subject: [PATCH 22/48] Fix: Room directory: stuck after the 20 first items https://github.com/vector-im/riot-ios/issues/1329#issuecomment-310361132 --- Riot/ViewController/RoomsViewController.m | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Riot/ViewController/RoomsViewController.m b/Riot/ViewController/RoomsViewController.m index f6861efc7..2ba04c107 100644 --- a/Riot/ViewController/RoomsViewController.m +++ b/Riot/ViewController/RoomsViewController.m @@ -273,9 +273,12 @@ - (void)triggerDirectoryPagination { - if (!recentsDataSource || recentsDataSource.publicRoomsDirectoryDataSource.hasReachedPaginationEnd || footerSpinnerView) + if (!recentsDataSource + || recentsDataSource.state == MXKDataSourceStateUnknown + || recentsDataSource.publicRoomsDirectoryDataSource.hasReachedPaginationEnd + || footerSpinnerView) { - // We are not yet ready or we got all public rooms or we are already paginating + // We are not yet ready or being killed or we got all public rooms or we are already paginating // Do nothing return; } From 6def97197833a9b2f80e1821630136552e280b0e Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Fri, 23 Jun 2017 16:40:09 +0400 Subject: [PATCH 23/48] Review changes --- Riot/Model/Room/RoomDataSource.m | 8 ++------ Riot/ViewController/ReadReceiptsViewController.h | 2 +- Riot/ViewController/ReadReceiptsViewController.m | 14 +++++++++----- Riot/ViewController/RoomViewController.h | 2 +- Riot/ViewController/RoomViewController.m | 6 +++--- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Riot/Model/Room/RoomDataSource.m b/Riot/Model/Room/RoomDataSource.m index f4a01c719..949cfeeb7 100644 --- a/Riot/Model/Room/RoomDataSource.m +++ b/Riot/Model/Room/RoomDataSource.m @@ -167,7 +167,6 @@ NSArray* receipts = [self.room getEventReceipts:component.event.eventId sorted:YES]; NSMutableArray *roomMembers; NSMutableArray *placeholders; - NSMutableArray *receiptDescriptions; // Check whether some receipts are found if (receipts.count) @@ -175,9 +174,6 @@ // Retrieve the corresponding room members roomMembers = [[NSMutableArray alloc] initWithCapacity:receipts.count]; placeholders = [[NSMutableArray alloc] initWithCapacity:receipts.count]; - receiptDescriptions = [[NSMutableArray alloc] initWithCapacity:receipts.count]; - - MXKEventFormatter *formatter = (MXKEventFormatter*)self.mxSession.roomSummaryUpdateDelegate; for (MXReceiptData* data in receipts) { @@ -186,7 +182,6 @@ { [roomMembers addObject:roomMember]; [placeholders addObject:[AvatarGenerator generateAvatarForMatrixItem:roomMember.userId withDisplayName:roomMember.displayname]]; - [receiptDescriptions addObject:[formatter dateStringFromTimestamp:data.ts withTime:YES]]; } } } @@ -206,7 +201,8 @@ avatarsContainer.tag = index; [avatarsContainer refreshReceiptSenders:roomMembers withPlaceHolders:placeholders andAlignment:ReadReceiptAlignmentRight]; - avatarsContainer.recieptDescriptions = receiptDescriptions; + avatarsContainer.mxSession = self.mxSession; + avatarsContainer.readReceipts = receipts; avatarsContainer.translatesAutoresizingMaskIntoConstraints = NO; avatarsContainer.accessibilityIdentifier = @"readReceiptsContainer"; diff --git a/Riot/ViewController/ReadReceiptsViewController.h b/Riot/ViewController/ReadReceiptsViewController.h index d8a7ad9b9..55557dcbf 100644 --- a/Riot/ViewController/ReadReceiptsViewController.h +++ b/Riot/ViewController/ReadReceiptsViewController.h @@ -19,6 +19,6 @@ @interface ReadReceiptsViewController : UIViewController -+ (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receiptDescriptions:(NSArray *)receiptDescriptions; ++ (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient session:(MXSession *)session withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receipts:(NSArray *)receipts; @end diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index e87329baa..405c4d5c5 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -20,10 +20,11 @@ @interface ReadReceiptsViewController () @property (nonatomic) MXRestClient* restClient; +@property (nonatomic) MXSession *session; @property (nonatomic) NSArray *roomMembers; @property (nonatomic) NSArray *placeholders; -@property (nonatomic) NSArray *recieptDescriptions; +@property (nonatomic) NSArray *receipts; @property (weak, nonatomic) IBOutlet UIView *overlayView; @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @@ -36,13 +37,15 @@ #pragma mark - Public -+ (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receiptDescriptions:(NSArray *)receiptDescriptions ++ (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient session:(MXSession *)session withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receipts:(NSArray *)receipts { ReadReceiptsViewController *receiptsController = [[[self class] alloc] initWithNibName:NSStringFromClass([self class]) bundle:nil]; receiptsController.restClient = restClient; + receiptsController.session = session; + receiptsController.roomMembers = roomMembers; receiptsController.placeholders = placeholders; - receiptsController.recieptDescriptions = receiptDescriptions; + receiptsController.receipts = receipts; receiptsController.providesPresentationContextTransitionStyle = YES; receiptsController.definesPresentationContext = YES; @@ -136,9 +139,10 @@ } [cell.avatarImageView setImageURL:avatarUrl withType:nil andImageOrientation:UIImageOrientationUp previewImage:self.placeholders[indexPath.row]]; } - if (indexPath.row < self.recieptDescriptions.count) + if (indexPath.row < self.receipts.count) { - cell.receiptDescriptionLabel.text = self.recieptDescriptions[indexPath.row]; + NSString *receiptDescription = [(MXKEventFormatter*)self.session.roomSummaryUpdateDelegate dateStringFromTimestamp:self.receipts[indexPath.row].ts withTime:YES]; + cell.receiptDescriptionLabel.text = receiptDescription; } cell.selectionStyle = UITableViewCellSelectionStyleNone; diff --git a/Riot/ViewController/RoomViewController.h b/Riot/ViewController/RoomViewController.h index 0dd1fa9aa..bd68a0284 100644 --- a/Riot/ViewController/RoomViewController.h +++ b/Riot/ViewController/RoomViewController.h @@ -27,7 +27,7 @@ #import "UIViewController+RiotSearch.h" -@interface RoomViewController : MXKRoomViewController +@interface RoomViewController : MXKRoomViewController // The expanded header @property (weak, nonatomic) IBOutlet UIView *expandedHeaderContainer; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 3628c0324..58af8af6d 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -2874,11 +2874,11 @@ } } -#pragma mark - MXKRecieptSendersContainerDelegate +#pragma mark - MXKReceiptSendersContainerDelegate -- (void)didTapReceiptsContainerWithRestClient:(MXRestClient *)restClient RoomMembers:(NSArray *)roomMembers avatars:(NSArray *)avatars recieptDescriptions:(NSArray *)recieptDescriptions +- (void)didTapReceiptsContainerWithRestClient:(MXRestClient *)restClient session:(MXSession *)session roomMembers:(NSArray *)roomMembers avatars:(NSArray *)avatars receipts:(NSArray *)readReceipts { - [ReadReceiptsViewController openInViewController:self withRestClient:restClient withRoomMembers:roomMembers placeholders:avatars receiptDescriptions:recieptDescriptions]; + [ReadReceiptsViewController openInViewController:self withRestClient:restClient session:session withRoomMembers:roomMembers placeholders:avatars receipts:readReceipts]; } #pragma mark - Typing management From 25a06ab8a7bb16b436a580ec3f4905e9cc6e03ab Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Fri, 23 Jun 2017 15:09:43 +0200 Subject: [PATCH 24/48] Bug Fix - Read marker: when being kicked, the "Jump to first unread message" shouldn't be displayed #1338 + Remove the link to the room settings in the title view after leaving the room --- Riot/ViewController/RoomViewController.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 56bae0b2d..1394dce60 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -856,6 +856,14 @@ { [self showExpandedHeader:NO]; + // Force a simple title view initialised with the current room before leaving actually the room. + [self setRoomTitleViewClass:SimpleRoomTitleView.class]; + self.titleView.editable = NO; + self.titleView.mxRoom = self.roomDataSource.room; + + // Hide the potential read marker banner. + self.jumpToLastUnreadBannerContainer.hidden = YES; + [super leaveRoomOnEvent:event]; } From a4ed741c375fe5ae072af1ed1fbfc00a229068d8 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Fri, 23 Jun 2017 16:23:28 +0200 Subject: [PATCH 25/48] version ++ --- CHANGES.rst | 21 +++++++++++++++++++++ Podfile | 2 +- Podfile.lock | 22 +++++++++++----------- Riot/Info.plist | 4 ++-- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 481f2d862..ce7bf0c74 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,24 @@ +Changes in 0.4.1 (2017-06-23) +=============================================== + +Improvements: + * Upgrade MatrixKit version (v0.5.1). + +Bug fixes: + * Room Chat: Scrolling manually to the bottom of the no live timeline doesn't flip it to read/write view (#1312). + * Enhancement - UX Rework: Update the buttons of the room expanded header (vector-im/riot-meta#76). + * Contact search: Unexpected empty search result. + * tap-on-tab should include the top-of-page location in its cycle of options (#1316). + * Fix crash on decline button, thanks to @morozkin (#1330). + * Room directory: stuck after the 20 first items (#1329). + * Room directory: "No public rooms available" is displayed while loading (#1336). + * Room directory: Clicking on "No public rooms available" make the app crash. + * Crash when hitting a room header after some special steps (#1340). + * Chat screen: the search icon is missing after switching in live from a non live timeline (#1344). + * Crash when hitting room from unified search/browse directory (#1342). + * tapping on an unread room on home page takes you to the wrong room (#1304). + * Read marker: when being kicked, the "Jump to first unread message" shouldn't be displayed (#1338). + Changes in 0.4.0 (2017-06-16) =============================================== diff --git a/Podfile b/Podfile index 60bb76b2d..fcbb3282c 100644 --- a/Podfile +++ b/Podfile @@ -8,7 +8,7 @@ target "Riot" do # Different flavours of pods to MatrixKit # The tagged version on which this version of Riot has been built -pod 'MatrixKit', '0.5.0' +pod 'MatrixKit', '0.5.1' # The lastest release available on the CocoaPods repository #pod 'MatrixKit' diff --git a/Podfile.lock b/Podfile.lock index 52fc909d5..aa5f8fa43 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,13 +39,13 @@ PODS: - GZIP (1.1.1) - HPGrowingTextView (1.1) - libPhoneNumber-iOS (0.9.10) - - MatrixKit (0.5.0): + - MatrixKit (0.5.1): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.17) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.10) - - MatrixSDK (= 0.8.0) - - MatrixSDK (0.8.0): + - MatrixSDK (= 0.8.1) + - MatrixSDK (0.8.1): - AFNetworking (~> 3.1.0) - GZIP (~> 1.1.1) - OLMKit (2.2.2): @@ -53,9 +53,9 @@ PODS: - OLMKit/olmcpp (= 2.2.2) - OLMKit/olmc (2.2.2) - OLMKit/olmcpp (2.2.2) - - Realm (2.8.1): - - Realm/Headers (= 2.8.1) - - Realm/Headers (2.8.1) + - Realm (2.8.3): + - Realm/Headers (= 2.8.3) + - Realm/Headers (2.8.3) - WebRTC (58.17.16937) DEPENDENCIES: @@ -63,7 +63,7 @@ DEPENDENCIES: - DTCoreText - GBDeviceInfo (~> 4.3.0) - GoogleAnalytics - - MatrixKit (= 0.5.0) + - MatrixKit (= 0.5.1) - OLMKit - Realm (~> 2.8.1) - WebRTC (= 58.17.16937) @@ -78,12 +78,12 @@ SPEC CHECKSUMS: GZIP: f8beb59597f651e6970a45b816508a9c6d700b77 HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 libPhoneNumber-iOS: f721ae4d5854bce60934f9fb9b0b28e8e68913cb - MatrixKit: 3783ee9b8b049a05a5856c66f749319ec1592161 - MatrixSDK: 6d1bf7ffc4792622da5441b4ddc6d5c02a0675b3 + MatrixKit: 185b23b7ff54237d615cb69a6760b2596656475c + MatrixSDK: 0b7e63cd59a6344dcd503157f4bada4a69dc75bd OLMKit: b9d8c0ffee9ea8c45bc0aaa9afb47f93fba7efbd - Realm: 2627602ad6818451f0cb8c2a6e072f7f10a5f360 + Realm: 3601ef091c8c499a31101d8563b991e75546cdce WebRTC: 1e9a85bf75509eec44be6478c64e9de65ac82332 -PODFILE CHECKSUM: 24c0a0406f24e3ba56478f5e9322cda986a7c199 +PODFILE CHECKSUM: af8b159b66069db7b31001f090aba7c298bb56e4 COCOAPODS: 1.2.1 diff --git a/Riot/Info.plist b/Riot/Info.plist index 67d83c41e..cdaf4b8c2 100644 --- a/Riot/Info.plist +++ b/Riot/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.4.0 + 0.4.1 CFBundleSignature ???? CFBundleVersion - 0.4.0 + 0.4.1 ITSAppUsesNonExemptEncryption ITSEncryptionExportComplianceCode From d91c305d1d62c39eaf070eae03c9bf06788c9058 Mon Sep 17 00:00:00 2001 From: manuroe Date: Mon, 26 Jun 2017 15:33:45 +0200 Subject: [PATCH 26/48] Fix: Crash when scrolling in the public rooms from Unified Search (#1355) Make pagination UI update of DirectoryViewController simpler. The issue is that the same PublicRoomsDirectoryDataSource instance is shared between DirectoryViewController and RoomsViewController. We can have parallel paginations which makes hard to count the exact number of added cells. --- Riot/ViewController/DirectoryViewController.m | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/Riot/ViewController/DirectoryViewController.m b/Riot/ViewController/DirectoryViewController.m index 8016bdbb4..ed5156d0f 100644 --- a/Riot/ViewController/DirectoryViewController.m +++ b/Riot/ViewController/DirectoryViewController.m @@ -230,19 +230,7 @@ if (roomsAdded) { - // Notify the table view there are new items at its tail - NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:roomsAdded]; - - NSUInteger numberOfRowsBefore = [self->dataSource tableView:self.tableView numberOfRowsInSection:0]; - numberOfRowsBefore -= roomsAdded; - - for (NSUInteger i = 0; i < roomsAdded; i++) - { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:(numberOfRowsBefore + i) inSection:0]; - [indexPaths addObject:indexPath]; - } - - [self.tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic]; + [self.tableView reloadData]; } [self removeSpinnerFooterView]; From ae1ae4d14151bbf1dfa2f925af6f07188f06cddf Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 26 Jun 2017 19:33:53 +0400 Subject: [PATCH 27/48] Removed the ReceiptsContainerDelegate, used didRecognizeAction: instead --- Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m | 8 ++++++++ Riot/Model/Room/RoomDataSource.m | 7 +++++-- Riot/ViewController/RoomViewController.h | 2 +- Riot/ViewController/RoomViewController.m | 12 +++++------- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index a5e16ec20..d13b8a0af 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -388,6 +388,14 @@ NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRi } } +- (IBAction)onReceiptContainerTap:(UITapGestureRecognizer *)sender +{ + if (self.delegate) + { + [self.delegate cell:self didRecognizeAction:kMXKRoomBubbleCellTapOnReceiptsContainer userInfo:@{kMXKRoomBubbleCellReceiptsContainerKey : sender.view}]; + } +} + #pragma mark - Internals - (void)addEditButtonForComponent:(NSUInteger)componentIndex completion:(void (^ __nullable)(BOOL finished))completion diff --git a/Riot/Model/Room/RoomDataSource.m b/Riot/Model/Room/RoomDataSource.m index 949cfeeb7..6b0623e74 100644 --- a/Riot/Model/Room/RoomDataSource.m +++ b/Riot/Model/Room/RoomDataSource.m @@ -191,7 +191,6 @@ { // Define the read receipts container, positioned on the right border of the bubble cell (Note the right margin 6 pts). MXKReceiptSendersContainer* avatarsContainer = [[MXKReceiptSendersContainer alloc] initWithFrame:CGRectMake(bubbleCell.frame.size.width - 156, bottomPositionY - 13, 150, 12) andRestClient:self.mxSession.matrixRestClient]; - avatarsContainer.delegate = [AppDelegate theDelegate].masterTabBarController.currentRoomViewController; // Custom avatar display avatarsContainer.maxDisplayedAvatars = 5; @@ -201,8 +200,12 @@ avatarsContainer.tag = index; [avatarsContainer refreshReceiptSenders:roomMembers withPlaceHolders:placeholders andAlignment:ReadReceiptAlignmentRight]; - avatarsContainer.mxSession = self.mxSession; avatarsContainer.readReceipts = receipts; + UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:cell action:@selector(onReceiptContainerTap:)]; + [tapRecognizer setNumberOfTapsRequired:1]; + [tapRecognizer setNumberOfTouchesRequired:1]; + [avatarsContainer addGestureRecognizer:tapRecognizer]; + avatarsContainer.userInteractionEnabled = YES; avatarsContainer.translatesAutoresizingMaskIntoConstraints = NO; avatarsContainer.accessibilityIdentifier = @"readReceiptsContainer"; diff --git a/Riot/ViewController/RoomViewController.h b/Riot/ViewController/RoomViewController.h index bd68a0284..ac4c2ac11 100644 --- a/Riot/ViewController/RoomViewController.h +++ b/Riot/ViewController/RoomViewController.h @@ -27,7 +27,7 @@ #import "UIViewController+RiotSearch.h" -@interface RoomViewController : MXKRoomViewController +@interface RoomViewController : MXKRoomViewController // The expanded header @property (weak, nonatomic) IBOutlet UIView *expandedHeaderContainer; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 58af8af6d..0b66455e2 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -1688,6 +1688,11 @@ [self showEncryptionInformation:tappedEvent]; } } + else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnReceiptsContainer]) + { + MXKReceiptSendersContainer *container = userInfo[kMXKRoomBubbleCellReceiptsContainerKey]; + [ReadReceiptsViewController openInViewController:self withRestClient:container.restClient session:self.mainSession withRoomMembers:container.roomMembers placeholders:container.placeholders receipts:container.readReceipts]; + } else { // Keep default implementation for other actions @@ -2874,13 +2879,6 @@ } } -#pragma mark - MXKReceiptSendersContainerDelegate - -- (void)didTapReceiptsContainerWithRestClient:(MXRestClient *)restClient session:(MXSession *)session roomMembers:(NSArray *)roomMembers avatars:(NSArray *)avatars receipts:(NSArray *)readReceipts -{ - [ReadReceiptsViewController openInViewController:self withRestClient:restClient session:session withRoomMembers:roomMembers placeholders:avatars receipts:readReceipts]; -} - #pragma mark - Typing management - (void)removeTypingNotificationsListener From 68b583cc5379dc928ab5ad0aafeed7dae4a6f57c Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 26 Jun 2017 20:51:13 +0400 Subject: [PATCH 28/48] Using Attributed string for read label --- Riot/ViewController/ReadReceiptsViewController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index 405c4d5c5..607f75d91 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -134,7 +134,7 @@ NSString *avatarUrl = self.roomMembers[indexPath.row].avatarUrl; if (self.restClient && avatarUrl) { - CGFloat side = CGRectGetWidth(cell.imageView.frame); + CGFloat side = CGRectGetWidth(cell.avatarImageView.frame); avatarUrl = [self.restClient urlOfContentThumbnail:avatarUrl toFitViewSize:CGSizeMake(side, side) withMethod:MXThumbnailingMethodCrop]; } [cell.avatarImageView setImageURL:avatarUrl withType:nil andImageOrientation:UIImageOrientationUp previewImage:self.placeholders[indexPath.row]]; From 8a6917543b2d7d23b68a4d12c3306d78c20aad6c Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Mon, 26 Jun 2017 22:22:32 +0400 Subject: [PATCH 29/48] Review changes --- Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h | 12 ++++++++++++ Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m | 1 + 2 files changed, 13 insertions(+) diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h index 9f5dc357d..7967ba35e 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.h @@ -23,6 +23,13 @@ */ extern NSString *const kMXKRoomBubbleCellRiotEditButtonPressed; +/** + Action identifier used when the user tapped on receipts area. + + The 'userInfo' disctionary contains an 'MXKReceiptSendersContainer' object under the 'kMXKRoomBubbleCellReceiptsContainerKey' key, representing the receipts container which was tapped on. + */ +extern NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer; + /** Define a `MXKRoomBubbleTableViewCell` category at Riot level to handle bubble customisation. */ @@ -56,6 +63,11 @@ extern NSString *const kMXKRoomBubbleCellRiotEditButtonPressed; */ - (void)addDateLabel; +/** + Called when the user taps on the Receipt Container. + */ +- (IBAction)onReceiptContainerTap:(UITapGestureRecognizer *)sender; + /** Blur the view by adding a transparent overlay. Default is NO. */ diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index d13b8a0af..2af3a588d 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -29,6 +29,7 @@ #define VECTOR_ROOMBUBBLETABLEVIEWCELL_MARK_WIDTH 4 NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRiotEditButtonPressed"; +NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellTapOnReceiptsContainer"; @implementation MXKRoomBubbleTableViewCell (Riot) From 6c023228a146bca65e312b6373ce4c87d6f87c35 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Tue, 27 Jun 2017 11:05:43 +0200 Subject: [PATCH 30/48] Read Receipts Details - RoomDataSource: Add RR container in the cell content view instead of the overlay container to not disturb tag gesture handling. https://github.com/vector-im/riot-ios/issues/59 --- .../MXKRoomBubbleTableViewCell+Riot.m | 9 ++++-- Riot/Model/Room/RoomDataSource.m | 30 +++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index 2af3a588d..b64339ddf 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -147,7 +147,7 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT // Retrieve the read receipts container related to the selected component (if any) // Blur the others - for (UIView* view in self.bubbleOverlayContainer.subviews) + for (UIView* view in self.tmpSubviews) { // Note read receipt container tag is equal to the index of the related component. if (view.tag != componentIndex) @@ -306,11 +306,14 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT self.bubbleOverlayContainer.alpha = 0.8; self.bubbleOverlayContainer.userInteractionEnabled = YES; - // Blur read receipts if any + // Blur subviews if any for (UIView* view in self.bubbleOverlayContainer.subviews) { view.alpha = 0.2; } + + // Move this view in front + [self.contentView bringSubviewToFront:self.bubbleOverlayContainer]; } else { @@ -321,7 +324,7 @@ NSString *const kMXKRoomBubbleCellTapOnReceiptsContainer = @"kMXKRoomBubbleCellT self.bubbleOverlayContainer.alpha = 1; self.bubbleOverlayContainer.userInteractionEnabled = NO; - // Restore read receipts display + // Restore subviews display for (UIView* view in self.bubbleOverlayContainer.subviews) { view.alpha = 1; diff --git a/Riot/Model/Room/RoomDataSource.m b/Riot/Model/Room/RoomDataSource.m index 6b0623e74..01e2d50dd 100644 --- a/Riot/Model/Room/RoomDataSource.m +++ b/Riot/Model/Room/RoomDataSource.m @@ -145,13 +145,8 @@ // Ignore the read receipts on the bubble without actual display. if ((self.showBubbleReceipts && cellData.hasReadReceipts) || self.showReadMarker) { - // Read receipts container are inserted here on the right side into the overlay container. + // Read receipts container are inserted here on the right side into the content view. // Some vertical whitespaces are added in message text view (see RoomBubbleCellData class) to insert correctly multiple receipts. - bubbleCell.bubbleOverlayContainer.backgroundColor = [UIColor clearColor]; - bubbleCell.bubbleOverlayContainer.alpha = 1; - bubbleCell.bubbleOverlayContainer.userInteractionEnabled = YES; - bubbleCell.bubbleOverlayContainer.hidden = NO; - NSInteger index = bubbleComponents.count; CGFloat bottomPositionY = bubbleCell.frame.size.height; while (index--) @@ -209,7 +204,17 @@ avatarsContainer.translatesAutoresizingMaskIntoConstraints = NO; avatarsContainer.accessibilityIdentifier = @"readReceiptsContainer"; - [bubbleCell.bubbleOverlayContainer addSubview:avatarsContainer]; + + // Add this read receipts container in the content view + if (!bubbleCell.tmpSubviews) + { + bubbleCell.tmpSubviews = [NSMutableArray arrayWithArray:@[avatarsContainer]]; + } + else + { + [bubbleCell.tmpSubviews addObject:avatarsContainer]; + } + [bubbleCell.contentView addSubview:avatarsContainer]; // Force receipts container size NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer @@ -231,14 +236,14 @@ NSLayoutConstraint *trailingConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual - toItem:bubbleCell.bubbleOverlayContainer + toItem:avatarsContainer.superview attribute:NSLayoutAttributeTrailing multiplier:1.0 constant:-6]; NSLayoutConstraint *topConstraint = [NSLayoutConstraint constraintWithItem:avatarsContainer attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual - toItem:bubbleCell.bubbleOverlayContainer + toItem:avatarsContainer.superview attribute:NSLayoutAttributeTop multiplier:1.0 constant:bottomPositionY - 13]; @@ -251,6 +256,13 @@ // Check whether the read marker must be displayed here. if (self.showReadMarker) { + // The read marker is added into the overlay container. + // CAUTION: Keep disabled the user interaction on this container to not disturb tap gesture handling. + bubbleCell.bubbleOverlayContainer.backgroundColor = [UIColor clearColor]; + bubbleCell.bubbleOverlayContainer.alpha = 1; + bubbleCell.bubbleOverlayContainer.userInteractionEnabled = NO; + bubbleCell.bubbleOverlayContainer.hidden = NO; + if ([component.event.eventId isEqualToString:self.room.accountData.readMarkerEventId]) { bubbleCell.readMarkerView = [[UIView alloc] initWithFrame:CGRectMake(0, bottomPositionY - 2, bubbleCell.bubbleOverlayContainer.frame.size.width, 2)]; From fa283f0952bcfca1d54d46133940267ea1045939 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Wed, 28 Jun 2017 18:51:05 +0200 Subject: [PATCH 31/48] Bug Fix - Chat screen: the message overlaps its timestamp (related to #1361) --- .../MXKRoomBubbleTableViewCell+Riot.m | 51 ++++++++++++++----- Riot/Model/Room/RoomBubbleCellData.h | 5 ++ Riot/Model/Room/RoomBubbleCellData.m | 21 ++++++++ 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m index a5e16ec20..02cb801b6 100644 --- a/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m +++ b/Riot/Categories/MXKRoomBubbleTableViewCell+Riot.m @@ -47,8 +47,15 @@ NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRi if (component && component.date) { + // Check whether this is the first displayed component. + BOOL isFirstDisplayedComponent = (componentIndex == 0); + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + isFirstDisplayedComponent = (componentIndex == ((RoomBubbleCellData*)bubbleData).oldestComponentIndex); + } + CGFloat timeLabelPosX = self.bubbleInfoContainer.frame.size.width - VECTOR_ROOMBUBBLETABLEVIEWCELL_TIMELABEL_WIDTH; - CGFloat timeLabelPosY = componentIndex ? component.position.y + self.msgTextViewTopConstraint.constant - self.bubbleInfoContainerTopConstraint.constant: 0; + CGFloat timeLabelPosY = isFirstDisplayedComponent ? 0 : component.position.y + self.msgTextViewTopConstraint.constant - self.bubbleInfoContainerTopConstraint.constant; UILabel *timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(timeLabelPosX, timeLabelPosY, VECTOR_ROOMBUBBLETABLEVIEWCELL_TIMELABEL_WIDTH , 18)]; timeLabel.text = [bubbleData.eventFormatter timeStringFromDate:component.date]; @@ -104,7 +111,7 @@ NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRi [NSLayoutConstraint activateConstraints:@[rightConstraint, topConstraint, widthConstraint, heightConstraint]]; // Check whether a vertical whitespace was applied to display correctly the timestamp. - if (componentIndex || bubbleData.shouldHideSenderInformation || bubbleData.shouldHideSenderName) + if (!isFirstDisplayedComponent || bubbleData.shouldHideSenderInformation || bubbleData.shouldHideSenderName) { // Adjust the position of the potential encryption icon in this case. if (self.encryptionStatusContainerView) @@ -170,19 +177,30 @@ NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRi // Define the marker frame CGFloat markPosY = component.position.y + self.msgTextViewTopConstraint.constant; - - CGFloat markHeight; - if (componentIndex == bubbleComponents.count - 1) + + NSInteger mostRecentComponentIndex = bubbleComponents.count - 1; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) { - // There is no component after this component in the cell, - // use the rest of the cell height - markHeight = self.contentView.frame.size.height - markPosY; + mostRecentComponentIndex = ((RoomBubbleCellData*)bubbleData).mostRecentComponentIndex; } - else + + // Compute the mark height. + // Use the rest of the cell height by default. + CGFloat markHeight = self.contentView.frame.size.height - markPosY; + if (componentIndex != mostRecentComponentIndex) { - // Stop the marker height to the top of the next component in the cell - MXKRoomBubbleComponent *nextComponent = bubbleComponents[componentIndex + 1]; - markHeight = nextComponent.position.y - component.position.y; + // There is another component (with display) after this component in the cell. + // Stop the marker height to the top of this component. + for (NSInteger index = componentIndex + 1; index < bubbleComponents.count; index ++) + { + MXKRoomBubbleComponent *nextComponent = bubbleComponents[index]; + + if (nextComponent.attributedTextMessage) + { + markHeight = nextComponent.position.y - component.position.y; + break; + } + } } UIView *markerView = [[UIView alloc] initWithFrame:CGRectMake(VECTOR_ROOMBUBBLETABLEVIEWCELL_MARK_X, @@ -394,10 +412,17 @@ NSString *const kMXKRoomBubbleCellRiotEditButtonPressed = @"kMXKRoomBubbleCellRi { MXKRoomBubbleComponent *component = bubbleData.bubbleComponents[componentIndex]; + // Check whether this is the first displayed component. + BOOL isFirstDisplayedComponent = (componentIndex == 0); + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + isFirstDisplayedComponent = (componentIndex == ((RoomBubbleCellData*)bubbleData).oldestComponentIndex); + } + // Define 'Edit' button frame UIImage *editIcon = [UIImage imageNamed:@"edit_icon"]; CGFloat editBtnPosX = self.bubbleInfoContainer.frame.size.width - VECTOR_ROOMBUBBLETABLEVIEWCELL_TIMELABEL_WIDTH - 22 - editIcon.size.width / 2; - CGFloat editBtnPosY = componentIndex ? component.position.y + self.msgTextViewTopConstraint.constant - self.bubbleInfoContainerTopConstraint.constant - 13 : -13; + CGFloat editBtnPosY = isFirstDisplayedComponent ? -13 : component.position.y + self.msgTextViewTopConstraint.constant - self.bubbleInfoContainerTopConstraint.constant - 13; UIButton *editButton = [[UIButton alloc] initWithFrame:CGRectMake(editBtnPosX, editBtnPosY, 44, 44)]; [editButton setImage:editIcon forState:UIControlStateNormal]; diff --git a/Riot/Model/Room/RoomBubbleCellData.h b/Riot/Model/Room/RoomBubbleCellData.h index a59d3103d..70e128448 100644 --- a/Riot/Model/Room/RoomBubbleCellData.h +++ b/Riot/Model/Room/RoomBubbleCellData.h @@ -37,6 +37,11 @@ */ @property(nonatomic) NSString *selectedEventId; +/** + The index of the oldest component (component with a timestamp, and an actual display). NSNotFound by default. + */ +@property(nonatomic, readonly) NSInteger oldestComponentIndex; + /** The index of the most recent component (component with a timestamp, and an actual display). NSNotFound by default. */ diff --git a/Riot/Model/Room/RoomBubbleCellData.m b/Riot/Model/Room/RoomBubbleCellData.m index 36388b619..e4cd8995a 100644 --- a/Riot/Model/Room/RoomBubbleCellData.m +++ b/Riot/Model/Room/RoomBubbleCellData.m @@ -348,6 +348,27 @@ static NSAttributedString *readReceiptVerticalWhitespace = nil; } } +- (NSInteger)oldestComponentIndex +{ + // Update the related component index + NSInteger oldestComponentIndex = NSNotFound; + + NSArray *components = self.bubbleComponents; + NSInteger index = 0; + while (index < components.count) + { + MXKRoomBubbleComponent *component = components[index]; + if (component.attributedTextMessage && component.date) + { + oldestComponentIndex = index; + break; + } + index++; + } + + return oldestComponentIndex; +} + - (NSInteger)mostRecentComponentIndex { // Update the related component index From b08dea5838cecf4633c6597ca3f335ce7b58c3df Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Wed, 28 Jun 2017 21:15:09 +0200 Subject: [PATCH 32/48] Bug Fix - Chat screen: several encryption icons are displayed on the same event. Fix tap handling on encryption icons (related to #1361) --- .../Encryption/RoomEncryptedDataBubbleCell.m | 6 +++++ .../RoomIncomingEncryptedTextMsgBubbleCell.m | 20 ++++++++++++++--- ...ptedTextMsgWithPaginationTitleBubbleCell.m | 20 ++++++++++++++--- ...ginationTitleWithoutSenderNameBubbleCell.m | 20 ++++++++++++++--- ...ryptedTextMsgWithoutSenderInfoBubbleCell.m | 20 ++++++++++++++--- ...ryptedTextMsgWithoutSenderNameBubbleCell.m | 20 ++++++++++++++--- .../RoomOutgoingEncryptedTextMsgBubbleCell.m | 22 +++++++++++++++---- ...ptedTextMsgWithPaginationTitleBubbleCell.m | 22 +++++++++++++++---- ...ginationTitleWithoutSenderNameBubbleCell.m | 22 +++++++++++++++---- ...ryptedTextMsgWithoutSenderInfoBubbleCell.m | 22 +++++++++++++++---- ...ryptedTextMsgWithoutSenderNameBubbleCell.m | 22 +++++++++++++++---- 11 files changed, 181 insertions(+), 35 deletions(-) diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomEncryptedDataBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomEncryptedDataBubbleCell.m index 37881a14f..3f7b8b918 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomEncryptedDataBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomEncryptedDataBubbleCell.m @@ -95,6 +95,12 @@ NSString *const kRoomEncryptedDataBubbleCellTapOnEncryptionIcon = @"kRoomEncrypt for (NSUInteger componentIndex = 0; componentIndex < bubbleComponents.count; componentIndex++) { component = bubbleComponents[componentIndex]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } UIImage *icon = [RoomEncryptedDataBubbleCell encryptionIconForEvent:component.event andSession:bubbleData.mxSession]; UIImageView *encryptStatusImageView = [[UIImageView alloc] initWithImage:icon]; diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.m index 11a486890..fc83e86da 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomIncomingEncryptedTextMsgBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m index 9d2c10c66..5497ff296 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomIncomingEncryptedTextMsgWithPaginationTitleBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m index 0e56d8794..51673332c 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomIncomingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m index c39cff80a..babc8f4af 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomIncomingEncryptedTextMsgWithoutSenderInfoBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m index 1767a3337..f08b38d90 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomIncomingEncryptedTextMsgWithoutSenderNameBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.m index 028b4c3b5..d99b16b8e 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomOutgoingEncryptedTextMsgBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; @@ -88,4 +102,4 @@ } } -@end \ No newline at end of file +@end diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m index 814a5fead..dedd93223 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomOutgoingEncryptedTextMsgWithPaginationTitleBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; @@ -88,4 +102,4 @@ } } -@end \ No newline at end of file +@end diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m index cb68f1d92..4dbbbe797 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomOutgoingEncryptedTextMsgWithPaginationTitleWithoutSenderNameBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; @@ -88,4 +102,4 @@ } } -@end \ No newline at end of file +@end diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m index 0d509a0c3..250dcc7fd 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomOutgoingEncryptedTextMsgWithoutSenderInfoBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; @@ -88,4 +102,4 @@ } } -@end \ No newline at end of file +@end diff --git a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m index 160f0afdb..63c38b5eb 100644 --- a/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m +++ b/Riot/Views/RoomBubbleList/Encryption/RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell.m @@ -18,6 +18,8 @@ #import "RoomEncryptedDataBubbleCell.h" +#import "RoomBubbleCellData.h" + @implementation RoomOutgoingEncryptedTextMsgWithoutSenderNameBubbleCell - (void)awakeFromNib @@ -64,15 +66,27 @@ // Check which bubble component is displayed in front of the tapped line. NSArray *bubbleComponents = bubbleData.bubbleComponents; - // Consider by default the first component - MXKRoomBubbleComponent *tappedComponent = bubbleComponents.firstObject; + // Consider by default the first display component + NSInteger firstComponentIndex = 0; + if ([bubbleData isKindOfClass:RoomBubbleCellData.class]) + { + firstComponentIndex = ((RoomBubbleCellData*)bubbleData).oldestComponentIndex; + } + MXKRoomBubbleComponent *tappedComponent = bubbleComponents[firstComponentIndex++]; CGPoint tapPoint = [sender locationInView:self.messageTextView]; - for (NSInteger index = 1; index < bubbleComponents.count; index++) + for (NSInteger index = firstComponentIndex; index < bubbleComponents.count; index++) { // Here the bubble is composed by multiple text messages MXKRoomBubbleComponent *component = bubbleComponents[index]; + + // Ignore components without display. + if (!component.attributedTextMessage) + { + continue; + } + if (tapPoint.y < component.position.y) { break; @@ -88,4 +102,4 @@ } } -@end \ No newline at end of file +@end From 9e033f1c3fafcf00b0dc9a0863bd9669d1033c25 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Thu, 29 Jun 2017 12:03:39 +0200 Subject: [PATCH 33/48] Bug Fix: blank pages with random "unread msgs" bars whilst they load. --- Riot/ViewController/RoomViewController.m | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 1394dce60..e38ac65c3 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -972,10 +972,14 @@ { [super setBubbleTableViewDisplayInTransition:bubbleTableViewDisplayInTransition]; - [self refreshActivitiesViewDisplay]; - - [self checkReadMarkerVisibility]; - [self refreshJumpToLastUnreadBannerDisplay]; + // Refresh additional displays when the table is ready. + if (!bubbleTableViewDisplayInTransition && !self.bubblesTableView.isHidden) + { + [self refreshActivitiesViewDisplay]; + + [self checkReadMarkerVisibility]; + [self refreshJumpToLastUnreadBannerDisplay]; + } } } @@ -3099,7 +3103,8 @@ { // Show "scroll to bottom" icon when the most recent message is not visible, // or when the timelime is not live (this icon is used to go back to live). - if (!self.roomDataSource.isLive || (!self.bubblesTableView.isHidden && [self isBubblesTableScrollViewAtTheBottom] == NO)) + // Note: we check if `currentEventIdAtTableBottom` is set to know whether the table has been rendered at least once. + if (!self.roomDataSource.isLive || (currentEventIdAtTableBottom && [self isBubblesTableScrollViewAtTheBottom] == NO)) { // Retrieve the unread messages count NSUInteger unreadCount = self.roomDataSource.room.summary.localUnreadEventCount; From e6f3d074e56faacb8f764b19348c7d650ef85f9e Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 29 Jun 2017 15:05:12 +0400 Subject: [PATCH 34/48] Handled the creation of the attributed receipt description, added in Vector.strings for translation --- Riot/Assets/en.lproj/Vector.strings | 1 + Riot/Model/Room/RoomDataSource.m | 1 - Riot/ViewController/ReadReceiptsViewController.h | 2 +- Riot/ViewController/ReadReceiptsViewController.m | 14 +++++++++++--- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index a0877f3e9..9001ccaaa 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -437,6 +437,7 @@ "rage_shake_prompt" = "You seem to be shaking the phone in frustration. Would you like to submit a bug report?"; "camera_access_not_granted" = "%@ doesn't have permission to use Camera, please change privacy settings"; "large_badge_value_k_format" = "%.1fK"; +"read_receipts_list" = "Read Receipts List"; // room display name "room_displayname_invite_from" = "Invite from %@"; diff --git a/Riot/Model/Room/RoomDataSource.m b/Riot/Model/Room/RoomDataSource.m index 6b0623e74..680c7803a 100644 --- a/Riot/Model/Room/RoomDataSource.m +++ b/Riot/Model/Room/RoomDataSource.m @@ -17,7 +17,6 @@ #import "RoomDataSource.h" -#import "AppDelegate.h" #import "EventFormatter.h" #import "RoomBubbleCellData.h" diff --git a/Riot/ViewController/ReadReceiptsViewController.h b/Riot/ViewController/ReadReceiptsViewController.h index 55557dcbf..aa49029b5 100644 --- a/Riot/ViewController/ReadReceiptsViewController.h +++ b/Riot/ViewController/ReadReceiptsViewController.h @@ -17,7 +17,7 @@ #import #import -@interface ReadReceiptsViewController : UIViewController +@interface ReadReceiptsViewController : MXKViewController + (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient session:(MXSession *)session withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receipts:(NSArray *)receipts; diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index 607f75d91..da3617482 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -16,6 +16,7 @@ #import "ReadReceiptsViewController.h" #import +#import "RiotDesignValues.h" @interface ReadReceiptsViewController () @@ -77,7 +78,7 @@ - (void)configureViews { self.containerView.layer.cornerRadius = 20; - self.titleLabel.text = @"Read Receipts List"; + self.titleLabel.text = NSLocalizedStringFromTable(@"read_receipts_list", @"Vector", nil); } - (void)configureReceiptsTableView @@ -141,8 +142,15 @@ } if (indexPath.row < self.receipts.count) { - NSString *receiptDescription = [(MXKEventFormatter*)self.session.roomSummaryUpdateDelegate dateStringFromTimestamp:self.receipts[indexPath.row].ts withTime:YES]; - cell.receiptDescriptionLabel.text = receiptDescription; + NSString *receiptReadText = [NSBundle mxk_localizedStringForKey:@"receipt_status_read"]; + NSString *receiptTimeText = [(MXKEventFormatter*)self.session.roomSummaryUpdateDelegate dateStringFromTimestamp:self.receipts[indexPath.row].ts withTime:YES]; + cell.receiptDescriptionLabel.text = receiptTimeText; + + NSMutableAttributedString *receiptDescription = [[NSMutableAttributedString alloc] initWithString:receiptReadText attributes:@{NSForegroundColorAttributeName : kRiotTextColorGray, NSFontAttributeName : [UIFont boldSystemFontOfSize:15]}]; + + [receiptDescription appendAttributedString:[[NSAttributedString alloc] initWithString:receiptTimeText attributes:@{NSForegroundColorAttributeName : kRiotTextColorGray, NSFontAttributeName : [UIFont systemFontOfSize:15]}]]; + + cell.receiptDescriptionLabel.attributedText = receiptDescription; } cell.selectionStyle = UITableViewCellSelectionStyleNone; From b19fa5c6568a8beda7b35cc5b59f05e63397d326 Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 29 Jun 2017 17:04:34 +0400 Subject: [PATCH 35/48] Decreased the parameter cound for ReadReceiptsViewController opening, now sending the container itself --- Riot/ViewController/ReadReceiptsViewController.h | 2 +- Riot/ViewController/ReadReceiptsViewController.m | 10 +++++----- Riot/ViewController/RoomViewController.m | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Riot/ViewController/ReadReceiptsViewController.h b/Riot/ViewController/ReadReceiptsViewController.h index aa49029b5..1e71353af 100644 --- a/Riot/ViewController/ReadReceiptsViewController.h +++ b/Riot/ViewController/ReadReceiptsViewController.h @@ -19,6 +19,6 @@ @interface ReadReceiptsViewController : MXKViewController -+ (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient session:(MXSession *)session withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receipts:(NSArray *)receipts; ++ (void)openInViewController:(UIViewController *)viewController fromContainer:(MXKReceiptSendersContainer *)receiptSendersContainer withsession:(MXSession *)session; @end diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index da3617482..8821a495c 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -38,15 +38,15 @@ #pragma mark - Public -+ (void)openInViewController:(UIViewController *)viewController withRestClient:(MXRestClient *)restClient session:(MXSession *)session withRoomMembers:(NSArray *)roomMembers placeholders:(NSArray *)placeholders receipts:(NSArray *)receipts ++ (void)openInViewController:(UIViewController *)viewController fromContainer:(MXKReceiptSendersContainer *)receiptSendersContainer withsession:(MXSession *)session { ReadReceiptsViewController *receiptsController = [[[self class] alloc] initWithNibName:NSStringFromClass([self class]) bundle:nil]; - receiptsController.restClient = restClient; + receiptsController.restClient = receiptSendersContainer.restClient; receiptsController.session = session; - receiptsController.roomMembers = roomMembers; - receiptsController.placeholders = placeholders; - receiptsController.receipts = receipts; + receiptsController.roomMembers = receiptSendersContainer.roomMembers; + receiptsController.placeholders = receiptSendersContainer.placeholders; + receiptsController.receipts = receiptSendersContainer.readReceipts; receiptsController.providesPresentationContextTransitionStyle = YES; receiptsController.definesPresentationContext = YES; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 0b66455e2..308f242bf 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -1691,7 +1691,7 @@ else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnReceiptsContainer]) { MXKReceiptSendersContainer *container = userInfo[kMXKRoomBubbleCellReceiptsContainerKey]; - [ReadReceiptsViewController openInViewController:self withRestClient:container.restClient session:self.mainSession withRoomMembers:container.roomMembers placeholders:container.placeholders receipts:container.readReceipts]; + [ReadReceiptsViewController openInViewController:self fromContainer:container withsession:self.mainSession]; } else { From 52fc3bbb1469b49a7c967ed9cd5c5ce9c132d808 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 29 Jun 2017 17:23:57 +0200 Subject: [PATCH 36/48] Bug report: Fix a crash when rotating + debackgrounding the app (#1362) --- Riot/ViewController/BugReportViewController.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Riot/ViewController/BugReportViewController.m b/Riot/ViewController/BugReportViewController.m index e1eacfe20..a69bb7d0b 100644 --- a/Riot/ViewController/BugReportViewController.m +++ b/Riot/ViewController/BugReportViewController.m @@ -127,6 +127,8 @@ { [super viewWillDisappear:animated]; + [self dismissKeyboard]; + if (screenShotFile) { [[NSFileManager defaultManager] removeItemAtURL:screenShotFile error:nil]; @@ -161,6 +163,12 @@ } #pragma mark - MXKViewController +- (void)dismissKeyboard +{ + // Hide the keyboard + [_bugReportDescriptionTextView resignFirstResponder]; +} + - (void)onKeyboardShowAnimationComplete { self.keyboardView = _bugReportDescriptionTextView.inputAccessoryView.superview; From d885c3f0bdc68824bf04b39d7a634163a0784888 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 29 Jun 2017 17:24:54 +0200 Subject: [PATCH 37/48] Bug report: Remove the old requirement for an existing email account --- Riot/API/RageShakeManager.m | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/Riot/API/RageShakeManager.m b/Riot/API/RageShakeManager.m index ea94c5909..30bf237f2 100644 --- a/Riot/API/RageShakeManager.m +++ b/Riot/API/RageShakeManager.m @@ -1,6 +1,7 @@ /* 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 @@ -61,9 +62,10 @@ static RageShakeManager* sharedInstance = nil; return self; } -- (void)promptCrashReportInViewController:(UIViewController*)viewController { - if ([MXLogger crashLog] && [MFMailComposeViewController canSendMail]) { - +- (void)promptCrashReportInViewController:(UIViewController*)viewController +{ + if ([MXLogger crashLog]) + { confirmationAlert = [[MXKAlert alloc] initWithTitle:NSLocalizedStringFromTable(@"bug_report_prompt", @"Vector", nil) message:nil style:MXKAlertStyleAlert]; __weak typeof(self) weakSelf = self; @@ -101,14 +103,15 @@ static RageShakeManager* sharedInstance = nil; } } -- (void)stopShaking:(UIResponder*)responder { - +- (void)stopShaking:(UIResponder*)responder +{ NSLog(@"[RageShakeManager] Stop shaking with [%@]", [responder class]); if (isShaking && [AppDelegate theDelegate].isAppForeground && !confirmationAlert - && (([[NSDate date] timeIntervalSince1970] - startShakingTimeStamp) > RAGESHAKEMANAGER_MINIMUM_SHAKING_DURATION)) { - - if ([responder isKindOfClass:[UIViewController class]] && [MFMailComposeViewController canSendMail]) { + && (([[NSDate date] timeIntervalSince1970] - startShakingTimeStamp) > RAGESHAKEMANAGER_MINIMUM_SHAKING_DURATION)) + { + if ([responder isKindOfClass:[UIViewController class]]) + { confirmationAlert = [[MXKAlert alloc] initWithTitle:NSLocalizedStringFromTable(@"rage_shake_prompt", @"Vector", nil) message:nil style:MXKAlertStyleAlert]; __weak typeof(self) weakSelf = self; From 04c17f7d6090c1ed3a2374ca7876f9a10eb15947 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 29 Jun 2017 17:33:08 +0200 Subject: [PATCH 38/48] Crash report: Do not loose what the user typed when debackgrounding the app --- Riot/AppDelegate.m | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 85e83064d..5e3c8ce4b 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -28,6 +28,8 @@ #import "SettingsViewController.h" #import "ContactDetailsViewController.h" +#import "BugReportViewController.h" + #import "NSBundle+MatrixKit.h" #import "MatrixSDK/MatrixSDK.h" @@ -824,6 +826,13 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN NSString *filePath = [MXLogger crashLog]; if (filePath) { + // Do not show the crash report dialog if it is already displayed + if ([self.window.rootViewController.childViewControllers[0] isKindOfClass:[BugReportViewController class]] + && [((UINavigationController*)self.window.rootViewController.childViewControllers[0]).visibleViewController isKindOfClass:[BugReportViewController class]]) + { + return; + } + NSString *description = [[NSString alloc] initWithContentsOfFile:filePath usedEncoding:nil error:nil]; From 1070fe1c7acecf8d23ce1f90654c5887c0ca770f Mon Sep 17 00:00:00 2001 From: Aram Sargsyan Date: Thu, 29 Jun 2017 19:40:07 +0400 Subject: [PATCH 39/48] Brought the key receipt_status_read to app level + some minor changes. Signed-off-by: Aram Sargsyan aram.sargsyan.1997@gmail.com --- Riot/Assets/en.lproj/Vector.strings | 5 ++++- Riot/ViewController/ReadReceiptsViewController.h | 2 +- Riot/ViewController/ReadReceiptsViewController.m | 5 ++--- Riot/ViewController/RoomViewController.m | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 9001ccaaa..874dab02e 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -414,6 +414,10 @@ "room_details_copy_room_address" = "Copy Room Address"; "room_details_copy_room_url" = "Copy Room URL"; +// Read Receipts +"read_receipts_list" = "Read Receipts List"; +"receipt_status_read" = "Read: "; + // Media picker "media_picker_library" = "Library"; "media_picker_select" = "Select"; @@ -437,7 +441,6 @@ "rage_shake_prompt" = "You seem to be shaking the phone in frustration. Would you like to submit a bug report?"; "camera_access_not_granted" = "%@ doesn't have permission to use Camera, please change privacy settings"; "large_badge_value_k_format" = "%.1fK"; -"read_receipts_list" = "Read Receipts List"; // room display name "room_displayname_invite_from" = "Invite from %@"; diff --git a/Riot/ViewController/ReadReceiptsViewController.h b/Riot/ViewController/ReadReceiptsViewController.h index 1e71353af..eca67c3ce 100644 --- a/Riot/ViewController/ReadReceiptsViewController.h +++ b/Riot/ViewController/ReadReceiptsViewController.h @@ -19,6 +19,6 @@ @interface ReadReceiptsViewController : MXKViewController -+ (void)openInViewController:(UIViewController *)viewController fromContainer:(MXKReceiptSendersContainer *)receiptSendersContainer withsession:(MXSession *)session; ++ (void)openInViewController:(UIViewController *)viewController fromContainer:(MXKReceiptSendersContainer *)receiptSendersContainer withSession:(MXSession *)session; @end diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index 8821a495c..b2c29ed75 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -38,7 +38,7 @@ #pragma mark - Public -+ (void)openInViewController:(UIViewController *)viewController fromContainer:(MXKReceiptSendersContainer *)receiptSendersContainer withsession:(MXSession *)session ++ (void)openInViewController:(UIViewController *)viewController fromContainer:(MXKReceiptSendersContainer *)receiptSendersContainer withSession:(MXSession *)session { ReadReceiptsViewController *receiptsController = [[[self class] alloc] initWithNibName:NSStringFromClass([self class]) bundle:nil]; receiptsController.restClient = receiptSendersContainer.restClient; @@ -142,9 +142,8 @@ } if (indexPath.row < self.receipts.count) { - NSString *receiptReadText = [NSBundle mxk_localizedStringForKey:@"receipt_status_read"]; + NSString *receiptReadText = NSLocalizedStringFromTable(@"receipt_status_read", @"Vector", nil); NSString *receiptTimeText = [(MXKEventFormatter*)self.session.roomSummaryUpdateDelegate dateStringFromTimestamp:self.receipts[indexPath.row].ts withTime:YES]; - cell.receiptDescriptionLabel.text = receiptTimeText; NSMutableAttributedString *receiptDescription = [[NSMutableAttributedString alloc] initWithString:receiptReadText attributes:@{NSForegroundColorAttributeName : kRiotTextColorGray, NSFontAttributeName : [UIFont boldSystemFontOfSize:15]}]; diff --git a/Riot/ViewController/RoomViewController.m b/Riot/ViewController/RoomViewController.m index 308f242bf..54c8265d6 100644 --- a/Riot/ViewController/RoomViewController.m +++ b/Riot/ViewController/RoomViewController.m @@ -1691,7 +1691,7 @@ else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnReceiptsContainer]) { MXKReceiptSendersContainer *container = userInfo[kMXKRoomBubbleCellReceiptsContainerKey]; - [ReadReceiptsViewController openInViewController:self fromContainer:container withsession:self.mainSession]; + [ReadReceiptsViewController openInViewController:self fromContainer:container withSession:self.mainSession]; } else { From ff6f4268a3d79749091f18b3e5c691172f6af7d1 Mon Sep 17 00:00:00 2001 From: manuroe Date: Thu, 29 Jun 2017 17:45:54 +0200 Subject: [PATCH 40/48] Crash report: Do not loose what the user typed when debackgrounding the app --- Riot/AppDelegate.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 5e3c8ce4b..268653e82 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -827,7 +827,7 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN if (filePath) { // Do not show the crash report dialog if it is already displayed - if ([self.window.rootViewController.childViewControllers[0] isKindOfClass:[BugReportViewController class]] + if ([self.window.rootViewController.childViewControllers[0] isKindOfClass:[UINavigationController class]] && [((UINavigationController*)self.window.rootViewController.childViewControllers[0]).visibleViewController isKindOfClass:[BugReportViewController class]]) { return; From 204717bfe5aed935a5c1b3a78e6b111dc1142c6c Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Fri, 30 Jun 2017 11:55:04 +0200 Subject: [PATCH 41/48] Update Authors --- AUTHORS.rst | 4 ++++ Riot.xcodeproj/project.pbxproj | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index adfe87b20..5a02bcaf8 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -22,4 +22,8 @@ Denis Morozov * Fix default room avatar for an empty room #1044 * PR #1090, PR #1113, PR #1123: UX improvements * PR #1132: Check email validity during reset password operation + * VoIP improvements + +Aram Sargsyan + * PR #1341: Read receipts details \ No newline at end of file diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index dc22970ad..a8e5d0451 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -2414,7 +2414,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ From 474fd7cf6df7a6b4bbe129abec385470f4f54428 Mon Sep 17 00:00:00 2001 From: manuroe Date: Fri, 30 Jun 2017 13:37:36 +0200 Subject: [PATCH 42/48] GA: Disable GA in debug as it pollutes stats and crashes --- Riot/AppDelegate.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 268653e82..e847c783c 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -792,6 +792,11 @@ NSString *const kAppDelegateNetworkStatusDidChangeNotification = @"kAppDelegateN // Set Google Analytics dispatch interval to e.g. 20 seconds. gai.dispatchInterval = 20; + +#ifdef DEBUG + // Disable GAI in debug as it pollutes stats and crashes in GA + gai.dryRun = YES; +#endif } else { From 64c9af8078d56d3cd80b04ea7eadd15e913df99f Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Fri, 30 Jun 2017 14:57:11 +0200 Subject: [PATCH 43/48] Read Receipts Details - Add close button --- Riot/Assets/en.lproj/Vector.strings | 2 ++ Riot/ViewController/BugReportViewController.m | 5 +++++ .../ReadReceiptsViewController.m | 9 ++++++++ .../ReadReceiptsViewController.xib | 22 ++++++++++++++----- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 874dab02e..ff69f3f29 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -474,3 +474,5 @@ "bug_report_send_screenshot" = "Send screenshot"; "bug_report_progress_zipping" = "Collecting logs"; "bug_report_progress_uploading" = "Uploading report"; +"bug_report_send" = "Send"; + diff --git a/Riot/ViewController/BugReportViewController.m b/Riot/ViewController/BugReportViewController.m index a69bb7d0b..b9087d541 100644 --- a/Riot/ViewController/BugReportViewController.m +++ b/Riot/ViewController/BugReportViewController.m @@ -89,6 +89,11 @@ // Allow to send empty description for crash report but not for bug report _sendButton.enabled = NO; } + + [_cancelButton setTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] forState:UIControlStateNormal]; + [_cancelButton setTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] forState:UIControlStateHighlighted]; + [_sendButton setTitle:NSLocalizedStringFromTable(@"bug_report_send", @"Vector", nil) forState:UIControlStateNormal]; + [_sendButton setTitle:NSLocalizedStringFromTable(@"bug_report_send", @"Vector", nil) forState:UIControlStateHighlighted]; _sendingContainer.hidden = YES; diff --git a/Riot/ViewController/ReadReceiptsViewController.m b/Riot/ViewController/ReadReceiptsViewController.m index b2c29ed75..298528ce5 100644 --- a/Riot/ViewController/ReadReceiptsViewController.m +++ b/Riot/ViewController/ReadReceiptsViewController.m @@ -31,6 +31,7 @@ @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @property (weak, nonatomic) IBOutlet UIView *containerView; @property (weak, nonatomic) IBOutlet UITableView *receiptsTableView; +@property (weak, nonatomic) IBOutlet UIButton *closeButton; @end @@ -79,6 +80,9 @@ { self.containerView.layer.cornerRadius = 20; self.titleLabel.text = NSLocalizedStringFromTable(@"read_receipts_list", @"Vector", nil); + + [_closeButton setTitle:[NSBundle mxk_localizedStringForKey:@"close"] forState:UIControlStateNormal]; + [_closeButton setTitle:[NSBundle mxk_localizedStringForKey:@"close"] forState:UIControlStateHighlighted]; } - (void)configureReceiptsTableView @@ -106,6 +110,11 @@ [self dismissViewControllerAnimated:YES completion:nil]; } +- (IBAction)onCloseButtonPress:(id)sender +{ + [self dismissViewControllerAnimated:YES completion:nil]; +} + #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section diff --git a/Riot/ViewController/ReadReceiptsViewController.xib b/Riot/ViewController/ReadReceiptsViewController.xib index 26b0926e7..a3a67dbf1 100644 --- a/Riot/ViewController/ReadReceiptsViewController.xib +++ b/Riot/ViewController/ReadReceiptsViewController.xib @@ -1,16 +1,17 @@ - + - + + @@ -48,19 +49,28 @@ - + + - + + + @@ -71,11 +81,11 @@ - + - + From 8e4bfeef5e84fa9ee9bb3a561ffe0cd331dab543 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Fri, 30 Jun 2017 17:06:23 +0200 Subject: [PATCH 44/48] Home: display the room name on 2 lines --- Riot/Views/RoomList/RoomCollectionViewCell.h | 2 + Riot/Views/RoomList/RoomCollectionViewCell.m | 23 ++++++++++- .../Views/RoomList/RoomCollectionViewCell.xib | 38 ++++++++++++++----- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.h b/Riot/Views/RoomList/RoomCollectionViewCell.h index c69d1e5b3..c3b48a372 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.h +++ b/Riot/Views/RoomList/RoomCollectionViewCell.h @@ -29,6 +29,8 @@ } @property (weak, nonatomic) IBOutlet UILabel *roomTitle; +@property (weak, nonatomic) IBOutlet UILabel *roomTitle1; +@property (weak, nonatomic) IBOutlet UILabel *roomTitle2; @property (weak, nonatomic) IBOutlet UIView *directRoomBorderView; @property (weak, nonatomic) IBOutlet MXKImageView *roomAvatar; diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index 59358e606..f7f5d9c8b 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -22,6 +22,8 @@ #import "MXRoom+Riot.h" +#import "MXTools.h" + @implementation RoomCollectionViewCell #pragma mark - Class methods @@ -66,7 +68,24 @@ if (roomCellData) { // Report computed values as is + self.roomTitle.hidden = NO; self.roomTitle.text = roomCellData.roomDisplayname; + self.roomTitle1.hidden = YES; + self.roomTitle2.hidden = YES; + + // Check whether the room display name is an alias to keep visible the HS. + if ([MXTools isMatrixRoomAlias:roomCellData.roomDisplayname]) + { + NSRange range = [roomCellData.roomDisplayname rangeOfString:@":" options:NSBackwardsSearch]; + if (range.location != NSNotFound) + { + self.roomTitle.hidden = YES; + self.roomTitle1.hidden = NO; + self.roomTitle1.text = [roomCellData.roomDisplayname substringToIndex:range.location + 1]; + self.roomTitle2.hidden = NO; + self.roomTitle2.text = [roomCellData.roomDisplayname substringFromIndex:range.location + 1]; + } + } // Notify unreads and bing if (roomCellData.hasUnread) @@ -143,12 +162,12 @@ + (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth { // The height is fixed - return 100; + return 120; } + (CGSize)defaultCellSize { - return CGSizeMake(80, 100); + return CGSizeMake(80, 120); } - (void)prepareForReuse diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.xib b/Riot/Views/RoomList/RoomCollectionViewCell.xib index 252747abf..e6a229c44 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.xib +++ b/Riot/Views/RoomList/RoomCollectionViewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -13,10 +13,10 @@ - + - + @@ -61,12 +61,23 @@ - - + + + + + + @@ -94,6 +110,8 @@ + + From d44d5cfeb9230540669d1be3103f005d840a7520 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Fri, 30 Jun 2017 17:37:43 +0200 Subject: [PATCH 45/48] Home: display the room name on 2 lines --- Riot/Views/RoomList/RoomCollectionViewCell.m | 18 +++++++++-------- .../Views/RoomList/RoomCollectionViewCell.xib | 20 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.m b/Riot/Views/RoomList/RoomCollectionViewCell.m index f7f5d9c8b..fc6c7e8ed 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.m +++ b/Riot/Views/RoomList/RoomCollectionViewCell.m @@ -41,6 +41,8 @@ _missedNotifAndUnreadBadgeBgViewWidthConstraint.constant = 0; self.roomTitle.textColor = kRiotTextColorBlack; + self.roomTitle1.textColor = kRiotTextColorBlack; + self.roomTitle2.textColor = kRiotTextColorBlack; self.missedNotifAndUnreadBadgeLabel.textColor = [UIColor whiteColor]; // Prepare direct room border @@ -104,11 +106,11 @@ // Use bold font for the room title if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) { - self.roomTitle.font = [UIFont systemFontOfSize:15 weight:UIFontWeightBold]; + self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold]; } else { - self.roomTitle.font = [UIFont boldSystemFontOfSize:15]; + self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont boldSystemFontOfSize:13]; } } else if (roomCellData.roomSummary.room.state.membership == MXMembershipInvite) @@ -124,11 +126,11 @@ // Use bold font for the room title if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) { - self.roomTitle.font = [UIFont systemFontOfSize:15 weight:UIFontWeightBold]; + self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightBold]; } else { - self.roomTitle.font = [UIFont boldSystemFontOfSize:15]; + self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont boldSystemFontOfSize:13]; } } else @@ -136,11 +138,11 @@ // The room title is not bold anymore if ([UIFont respondsToSelector:@selector(systemFontOfSize:weight:)]) { - self.roomTitle.font = [UIFont systemFontOfSize:15 weight:UIFontWeightMedium]; + self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium]; } else { - self.roomTitle.font = [UIFont systemFontOfSize:15]; + self.roomTitle.font = self.roomTitle1.font = self.roomTitle2.font = [UIFont systemFontOfSize:13]; } } @@ -162,12 +164,12 @@ + (CGFloat)heightForCellData:(MXKCellData *)cellData withMaximumWidth:(CGFloat)maxWidth { // The height is fixed - return 120; + return 115; } + (CGSize)defaultCellSize { - return CGSizeMake(80, 120); + return CGSizeMake(80, 115); } - (void)prepareForReuse diff --git a/Riot/Views/RoomList/RoomCollectionViewCell.xib b/Riot/Views/RoomList/RoomCollectionViewCell.xib index e6a229c44..ee75ecc0a 100644 --- a/Riot/Views/RoomList/RoomCollectionViewCell.xib +++ b/Riot/Views/RoomList/RoomCollectionViewCell.xib @@ -13,10 +13,10 @@ - + - + @@ -62,23 +62,23 @@ @@ -94,12 +94,16 @@ + + + + From 485ff69cc27c8b8e464ff8ab58494b68f0b260a7 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Fri, 30 Jun 2017 17:42:53 +0200 Subject: [PATCH 46/48] version++ --- CHANGES.rst | 18 ++++++++++++++++++ Podfile | 2 +- Podfile.lock | 14 +++++++------- Riot/Info.plist | 4 ++-- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ce7bf0c74..a077152a5 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,21 @@ +Changes in 0.4.2 (2017-06-30) +=============================================== + +Improvements: + * Upgrade MatrixKit version (v0.5.2). + * Chat: Expand read receipts when user taps on it #59, thanks to @aramsargsyan (#1341). + * GA: Disable GA in debug as it pollutes stats and crashes. + * Home: Display room name on 2 lines. + +Bug fixes: + * Fix: Crash when scrolling in the public rooms from Unified Search (#1355). + * Chat screen: the message overlaps its timestamp. + * Chat screen: several encryption icons are displayed on the same event. + * Blank pages with random "unread msgs" bars whilst they load. + * Fix a crash when rotating + debackgrounding the app (#1362). + * Bug report: Remove the old requirement for an existing email account. + * Crash report: Do not loose what the user typed when debackgrounding the app. + Changes in 0.4.1 (2017-06-23) =============================================== diff --git a/Podfile b/Podfile index fcbb3282c..dee294bb5 100644 --- a/Podfile +++ b/Podfile @@ -8,7 +8,7 @@ target "Riot" do # Different flavours of pods to MatrixKit # The tagged version on which this version of Riot has been built -pod 'MatrixKit', '0.5.1' +pod 'MatrixKit', '0.5.2' # The lastest release available on the CocoaPods repository #pod 'MatrixKit' diff --git a/Podfile.lock b/Podfile.lock index aa5f8fa43..34664183d 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -39,13 +39,13 @@ PODS: - GZIP (1.1.1) - HPGrowingTextView (1.1) - libPhoneNumber-iOS (0.9.10) - - MatrixKit (0.5.1): + - MatrixKit (0.5.2): - cmark (~> 0.24.1) - DTCoreText (~> 1.6.17) - HPGrowingTextView (~> 1.1) - libPhoneNumber-iOS (~> 0.9.10) - - MatrixSDK (= 0.8.1) - - MatrixSDK (0.8.1): + - MatrixSDK (= 0.8.2) + - MatrixSDK (0.8.2): - AFNetworking (~> 3.1.0) - GZIP (~> 1.1.1) - OLMKit (2.2.2): @@ -63,7 +63,7 @@ DEPENDENCIES: - DTCoreText - GBDeviceInfo (~> 4.3.0) - GoogleAnalytics - - MatrixKit (= 0.5.1) + - MatrixKit (= 0.5.2) - OLMKit - Realm (~> 2.8.1) - WebRTC (= 58.17.16937) @@ -78,12 +78,12 @@ SPEC CHECKSUMS: GZIP: f8beb59597f651e6970a45b816508a9c6d700b77 HPGrowingTextView: 88a716d97fb853bcb08a4a08e4727da17efc9b19 libPhoneNumber-iOS: f721ae4d5854bce60934f9fb9b0b28e8e68913cb - MatrixKit: 185b23b7ff54237d615cb69a6760b2596656475c - MatrixSDK: 0b7e63cd59a6344dcd503157f4bada4a69dc75bd + MatrixKit: daad23e5724d8f26a44a322f4d259a26b614ddc6 + MatrixSDK: 1f9b5fa6334485c431884d2d2ea9ac5f65557528 OLMKit: b9d8c0ffee9ea8c45bc0aaa9afb47f93fba7efbd Realm: 3601ef091c8c499a31101d8563b991e75546cdce WebRTC: 1e9a85bf75509eec44be6478c64e9de65ac82332 -PODFILE CHECKSUM: af8b159b66069db7b31001f090aba7c298bb56e4 +PODFILE CHECKSUM: ba9311a74b4af32f75db5053d87c9f7181a42d17 COCOAPODS: 1.2.1 diff --git a/Riot/Info.plist b/Riot/Info.plist index cdaf4b8c2..73ec6bffa 100644 --- a/Riot/Info.plist +++ b/Riot/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.4.1 + 0.4.2 CFBundleSignature ???? CFBundleVersion - 0.4.1 + 0.4.2 ITSAppUsesNonExemptEncryption ITSEncryptionExportComplianceCode From c4ecad6e2c7cd80b78647d1b6b13dbe29929d4a6 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Wed, 5 Jul 2017 11:58:18 +0200 Subject: [PATCH 47/48] Update the bundle display name with "Riot.im" Update Riot logo in Authentication screen --- Riot/Assets/Images/logo.png | Bin 9435 -> 7060 bytes Riot/Assets/Images/logo@2x.png | Bin 19532 -> 25066 bytes Riot/Assets/Images/logo@3x.png | Bin 29208 -> 38641 bytes Riot/Info.plist | 2 +- .../AuthenticationViewController.xib | 30 +++++++++--------- 5 files changed, 16 insertions(+), 16 deletions(-) mode change 100755 => 100644 Riot/Assets/Images/logo.png mode change 100755 => 100644 Riot/Assets/Images/logo@2x.png mode change 100755 => 100644 Riot/Assets/Images/logo@3x.png diff --git a/Riot/Assets/Images/logo.png b/Riot/Assets/Images/logo.png old mode 100755 new mode 100644 index ca7adc28c87f750fee868cb85d59ec216d8c57b2..93f2f59d64b0a7e33e9565f75970342cf4ebf70e GIT binary patch literal 7060 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 LNkvXXu0mjfii}_+ literal 19532 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 diff --git a/Riot/Assets/Images/logo@3x.png b/Riot/Assets/Images/logo@3x.png old mode 100755 new mode 100644 index de29dc2fd4cac2b0567114fdbb451fafea15f162..ed1628eed102f837daa49cc8a94d76a669ce2497 GIT binary patch literal 38641 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 z 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 N002ovPDHLkV1nFHqrU(E literal 29208 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 en CFBundleDisplayName - Riot + Riot.im CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/Riot/ViewController/AuthenticationViewController.xib b/Riot/ViewController/AuthenticationViewController.xib index e7eead8d3..4b4481795 100644 --- a/Riot/ViewController/AuthenticationViewController.xib +++ b/Riot/ViewController/AuthenticationViewController.xib @@ -1,11 +1,11 @@ - - + + - + @@ -48,11 +48,11 @@ - + - + @@ -71,31 +71,31 @@ - + - + - + - + - + From 9c2271feaa39f189a76519934c68025318646682 Mon Sep 17 00:00:00 2001 From: Giom Foret Date: Wed, 5 Jul 2017 12:05:33 +0200 Subject: [PATCH 48/48] version++ --- CHANGES.rst | 7 +++++++ Riot/Info.plist | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a077152a5..75251b160 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,3 +1,10 @@ +Changes in 0.4.3 (2017-07-05) +=============================================== + +Improvement: + * Update the application title with "Riot.im". + + Changes in 0.4.2 (2017-06-30) =============================================== diff --git a/Riot/Info.plist b/Riot/Info.plist index c8662b407..6c2e9faa9 100644 --- a/Riot/Info.plist +++ b/Riot/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.4.2 + 0.4.3 CFBundleSignature ???? CFBundleVersion - 0.4.2 + 0.4.3 ITSAppUsesNonExemptEncryption ITSEncryptionExportComplianceCode