diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index 0ad82c0d0..8b5073224 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -12,6 +12,7 @@ exports[`Storyshots Avatar Avatar by path 1`] = ` undefined, ] } + testID="avatar" > - - Read -  - - Favorite -  - - Hide - @@ -50501,13 +50573,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -50517,7 +50589,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -50563,421 +50637,15 @@ exports[`Storyshots Room Item Alerts 1`] = ` > rocket.cat - - - - - - - - - - - - -  - - - Read - - - - - - - -  - - - Favorite - - - - -  - - - Hide - - - - - - - - - - - - - -  - - - unread - - 1 + 10:00 @@ -51044,13 +50723,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -51330,13 +50960,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -51346,7 +50976,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -51394,25 +51026,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` - +999 + 10:00 + + + 1 + + @@ -51479,13 +51151,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -51765,13 +51388,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -51781,7 +51404,437 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, + ], + Object { + "color": "#cbced1", + }, + ], + ], + Object { + "fontFamily": "custom", + "fontStyle": "normal", + "fontWeight": "normal", + }, + Object {}, + ] + } + > +  + + + unread + + + + 10:00 + + + + +999 + + + + + + + + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + + + + + + + - 1 + 10:00 + + + 1 + + @@ -51914,13 +52007,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -52200,13 +52244,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -52216,7 +52260,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -52264,25 +52310,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` - 1 + 10:00 + + + 1 + + @@ -52349,13 +52435,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -52635,13 +52672,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -52651,7 +52688,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -52699,25 +52738,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` - 1 + 10:00 + + + 1 + + @@ -52784,13 +52863,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -53070,13 +53100,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -53086,7 +53116,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -53134,25 +53166,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` - 1 + 10:00 + + + 1 + + @@ -53219,13 +53291,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -53505,13 +53528,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -53521,7 +53544,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -53569,25 +53594,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` - 1 + 10:00 + + + 1 + + @@ -53654,13 +53719,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -53940,13 +53956,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -53956,7 +53972,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -54004,25 +54022,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` - 1 + 10:00 + + + 1 + + @@ -54089,13 +54147,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -54375,13 +54384,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -54391,7 +54400,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -54439,25 +54450,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` - 1 + 10:00 + + + 1 + + @@ -54524,13 +54575,16 @@ exports[`Storyshots Room Item Alerts 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -54810,13 +54812,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -54826,7 +54828,9 @@ exports[`Storyshots Room Item Alerts 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -54874,25 +54878,13 @@ exports[`Storyshots Room Item Alerts 1`] = ` - 1 + 10:00 + + + 1 + + @@ -54972,13 +55016,16 @@ exports[`Storyshots Room Item Basic 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -55258,13 +55253,13 @@ exports[`Storyshots Room Item Basic 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -55274,7 +55269,9 @@ exports[`Storyshots Room Item Basic 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -55315,6 +55312,3631 @@ exports[`Storyshots Room Item Basic 1`] = ` > rocket.cat + + + 10:00 + + + + + + + + + + +`; + +exports[`Storyshots Room Item Condensed Room Item 1`] = ` + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + + + + + + + +  + + + rocket.cat + + + + 10:00 + + + + 1 + + + + + + + + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + + + + + + + +  + + + unread + + + + 10:00 + + + + +999 + + + + + + + + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + + + + + + + +  + + + rocket.cat + + + + Auto-join + + + + + 10:00 + + + + + + + + + + +`; + +exports[`Storyshots Room Item Condensed Room Item without Avatar 1`] = ` + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + + +  + + + rocket.cat + + + + 10:00 + + + + 1 + + + + + + + + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + + +  + + + rocket.cat + + + + 10:00 + + + + + + + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + + +  + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + + Auto-join + + + + + 10:00 + + + + + + + + + + +`; + +exports[`Storyshots Room Item Expanded Room Item without Avatar 1`] = ` + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + +  + + + + + + rocket.cat + + + 10:00 + + + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + + 1 + + + + + + + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + +  + + + + + + rocket.cat + + + 10:00 + + + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + + 1 + + + + + + + + + + + + + + +  + + + + + + + +  + + + + +  + + + + + + + +  + + + + + + rocket.cat + + + 10:00 + + + + + Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + @@ -55374,13 +58996,16 @@ exports[`Storyshots Room Item Last Message 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -55773,6 +59346,7 @@ exports[`Storyshots Room Item Last Message 1`] = ` }, ] } + testID="room-item-last-message" > No Message @@ -55822,13 +59396,16 @@ exports[`Storyshots Room Item Last Message 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -56221,6 +59746,7 @@ exports[`Storyshots Room Item Last Message 1`] = ` }, ] } + testID="room-item-last-message" > 2 @@ -56270,13 +59796,16 @@ exports[`Storyshots Room Item Last Message 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -56669,6 +60146,7 @@ exports[`Storyshots Room Item Last Message 1`] = ` }, ] } + testID="room-item-last-message" > You: 1 @@ -56718,13 +60196,16 @@ exports[`Storyshots Room Item Last Message 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -57117,6 +60546,7 @@ exports[`Storyshots Room Item Last Message 1`] = ` }, ] } + testID="room-item-last-message" > Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries @@ -57166,13 +60596,16 @@ exports[`Storyshots Room Item Last Message 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -57580,6 +60961,7 @@ exports[`Storyshots Room Item Last Message 1`] = ` }, ] } + testID="room-item-last-message" > Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries @@ -57670,13 +61052,16 @@ exports[`Storyshots Room Item Last Message 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -58084,6 +61417,7 @@ exports[`Storyshots Room Item Last Message 1`] = ` }, ] } + testID="room-item-last-message" > Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries @@ -58174,13 +61508,16 @@ exports[`Storyshots Room Item Last Message 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -58588,6 +61873,7 @@ exports[`Storyshots Room Item Last Message 1`] = ` }, ] } + testID="room-item-last-message" > Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries @@ -58691,13 +61977,16 @@ exports[`Storyshots Room Item Tag 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -58977,13 +62214,13 @@ exports[`Storyshots Room Item Tag 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -58993,7 +62230,9 @@ exports[`Storyshots Room Item Tag 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -59070,6 +62309,36 @@ exports[`Storyshots Room Item Tag 1`] = ` Auto-join + + + 10:00 + + @@ -59116,13 +62385,16 @@ exports[`Storyshots Room Item Tag 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -59552,6 +62772,7 @@ exports[`Storyshots Room Item Tag 1`] = ` }, ] } + testID="room-item-last-message" > No Message @@ -59601,13 +62822,16 @@ exports[`Storyshots Room Item Tag 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -59887,13 +63059,13 @@ exports[`Storyshots Room Item Tag 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -59903,7 +63075,9 @@ exports[`Storyshots Room Item Tag 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -59980,6 +63154,36 @@ exports[`Storyshots Room Item Tag 1`] = ` Auto-join + + + 10:00 + + @@ -60026,13 +63230,16 @@ exports[`Storyshots Room Item Tag 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -60462,6 +63617,7 @@ exports[`Storyshots Room Item Tag 1`] = ` }, ] } + testID="room-item-last-message" > No Message @@ -60524,13 +63680,16 @@ exports[`Storyshots Room Item Touch 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -60810,13 +63917,13 @@ exports[`Storyshots Room Item Touch 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -60826,7 +63933,9 @@ exports[`Storyshots Room Item Touch 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -60867,6 +63976,36 @@ exports[`Storyshots Room Item Touch 1`] = ` > rocket.cat + + + 10:00 + + @@ -60926,13 +64065,16 @@ exports[`Storyshots Room Item Type 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -61212,13 +64302,13 @@ exports[`Storyshots Room Item Type 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -61228,7 +64318,9 @@ exports[`Storyshots Room Item Type 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -61269,6 +64361,36 @@ exports[`Storyshots Room Item Type 1`] = ` > rocket.cat + + + 10:00 + + @@ -61315,13 +64437,16 @@ exports[`Storyshots Room Item Type 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -61601,7 +64674,7 @@ exports[`Storyshots Room Item Type 1`] = ` Array [ Object { "color": undefined, - "fontSize": 16, + "fontSize": 22, }, Array [ Object { @@ -61610,7 +64683,9 @@ exports[`Storyshots Room Item Type 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "fontFamily": "custom", @@ -61646,6 +64721,36 @@ exports[`Storyshots Room Item Type 1`] = ` > rocket.cat + + + 10:00 + + @@ -61692,13 +64797,16 @@ exports[`Storyshots Room Item Type 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -61978,7 +65034,7 @@ exports[`Storyshots Room Item Type 1`] = ` Array [ Object { "color": undefined, - "fontSize": 16, + "fontSize": 22, }, Array [ Object { @@ -61987,7 +65043,9 @@ exports[`Storyshots Room Item Type 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "fontFamily": "custom", @@ -62023,6 +65081,36 @@ exports[`Storyshots Room Item Type 1`] = ` > rocket.cat + + + 10:00 + + @@ -62069,13 +65157,16 @@ exports[`Storyshots Room Item Type 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -62355,7 +65394,7 @@ exports[`Storyshots Room Item Type 1`] = ` Array [ Object { "color": undefined, - "fontSize": 16, + "fontSize": 22, }, Array [ Object { @@ -62364,7 +65403,9 @@ exports[`Storyshots Room Item Type 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "fontFamily": "custom", @@ -62400,6 +65441,36 @@ exports[`Storyshots Room Item Type 1`] = ` > rocket.cat + + + 10:00 + + @@ -62446,13 +65517,16 @@ exports[`Storyshots Room Item Type 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -62732,7 +65754,7 @@ exports[`Storyshots Room Item Type 1`] = ` Array [ Object { "color": undefined, - "fontSize": 16, + "fontSize": 22, }, Array [ Object { @@ -62741,7 +65763,9 @@ exports[`Storyshots Room Item Type 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "fontFamily": "custom", @@ -62777,6 +65801,36 @@ exports[`Storyshots Room Item Type 1`] = ` > rocket.cat + + + 10:00 + + @@ -62823,13 +65877,16 @@ exports[`Storyshots Room Item Type 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -63109,7 +66114,7 @@ exports[`Storyshots Room Item Type 1`] = ` Array [ Object { "color": undefined, - "fontSize": 16, + "fontSize": 22, }, Array [ Object { @@ -63118,7 +66123,9 @@ exports[`Storyshots Room Item Type 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "fontFamily": "custom", @@ -63154,6 +66161,36 @@ exports[`Storyshots Room Item Type 1`] = ` > rocket.cat + + + 10:00 + + @@ -63200,13 +66237,16 @@ exports[`Storyshots Room Item Type 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -63486,7 +66474,7 @@ exports[`Storyshots Room Item Type 1`] = ` Array [ Object { "color": undefined, - "fontSize": 16, + "fontSize": 22, }, Array [ Object { @@ -63495,7 +66483,9 @@ exports[`Storyshots Room Item Type 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "fontFamily": "custom", @@ -63531,6 +66521,36 @@ exports[`Storyshots Room Item Type 1`] = ` > rocket.cat + + + 10:00 + + @@ -63590,13 +66610,16 @@ exports[`Storyshots Room Item User 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -63876,13 +66847,13 @@ exports[`Storyshots Room Item User 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -63892,7 +66863,9 @@ exports[`Storyshots Room Item User 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -63933,6 +66906,36 @@ exports[`Storyshots Room Item User 1`] = ` > diego.mello + + + 10:00 + + @@ -63979,13 +66982,16 @@ exports[`Storyshots Room Item User 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -64265,13 +67219,13 @@ exports[`Storyshots Room Item User 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -64281,7 +67235,9 @@ exports[`Storyshots Room Item User 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -64322,6 +67278,36 @@ exports[`Storyshots Room Item User 1`] = ` > Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industrys standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries + + + 10:00 + + @@ -64381,13 +67367,16 @@ exports[`Storyshots Room Item User status 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -64667,13 +67604,13 @@ exports[`Storyshots Room Item User status 1`] = ` Array [ Object { "color": "#2de0a5", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -64683,7 +67620,9 @@ exports[`Storyshots Room Item User status 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#2de0a5", @@ -64724,6 +67663,36 @@ exports[`Storyshots Room Item User status 1`] = ` > rocket.cat + + + 10:00 + + @@ -64770,13 +67739,16 @@ exports[`Storyshots Room Item User status 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -65056,13 +67976,13 @@ exports[`Storyshots Room Item User status 1`] = ` Array [ Object { "color": "#ffd21f", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -65072,7 +67992,9 @@ exports[`Storyshots Room Item User status 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#ffd21f", @@ -65113,6 +68035,36 @@ exports[`Storyshots Room Item User status 1`] = ` > rocket.cat + + + 10:00 + + @@ -65159,13 +68111,16 @@ exports[`Storyshots Room Item User status 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -65445,13 +68348,13 @@ exports[`Storyshots Room Item User status 1`] = ` Array [ Object { "color": "#f5455c", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -65461,7 +68364,9 @@ exports[`Storyshots Room Item User status 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#f5455c", @@ -65502,6 +68407,36 @@ exports[`Storyshots Room Item User status 1`] = ` > rocket.cat + + + 10:00 + + @@ -65548,13 +68483,16 @@ exports[`Storyshots Room Item User status 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -65834,13 +68720,13 @@ exports[`Storyshots Room Item User status 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -65850,7 +68736,9 @@ exports[`Storyshots Room Item User status 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -65891,6 +68779,36 @@ exports[`Storyshots Room Item User status 1`] = ` > rocket.cat + + + 10:00 + + @@ -65937,13 +68855,16 @@ exports[`Storyshots Room Item User status 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -66223,13 +69092,13 @@ exports[`Storyshots Room Item User status 1`] = ` Array [ Object { "color": "#9ea2a8", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -66239,7 +69108,9 @@ exports[`Storyshots Room Item User status 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#9ea2a8", @@ -66280,6 +69151,36 @@ exports[`Storyshots Room Item User status 1`] = ` > rocket.cat + + + 10:00 + + @@ -66326,13 +69227,16 @@ exports[`Storyshots Room Item User status 1`] = ` > - - Read -  - - Favorite -  - - Hide - @@ -66612,13 +69464,13 @@ exports[`Storyshots Room Item User status 1`] = ` Array [ Object { "color": "#cbced1", - "fontSize": 16, + "fontSize": 22, }, Array [ Object { - "height": 16, + "height": 22, "textAlignVertical": "center", - "width": 16, + "width": 22, }, Array [ Array [ @@ -66628,7 +69480,9 @@ exports[`Storyshots Room Item User status 1`] = ` Object { "color": "#0d0e12", }, - undefined, + Object { + "marginRight": 8, + }, ], Object { "color": "#cbced1", @@ -66669,6 +69523,36 @@ exports[`Storyshots Room Item User status 1`] = ` > rocket.cat + + + 10:00 + + @@ -72243,6 +75127,7 @@ exports[`Storyshots Thread Messages.Item badge 1`] = ` }, ] } + testID="avatar" > - afterEvaluate { if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { android { - compileSdkVersion 29 + compileSdkVersion 30 buildToolsVersion "29.0.3" defaultConfig { - targetSdkVersion 29 + targetSdkVersion 30 } variantFilter { variant -> def names = variant.flavors*.name diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index fcf881fd3..852ce83ea 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -18,8 +18,6 @@ export const ROOMS = createRequestTypes('ROOMS', [ 'SET_SEARCH', 'CLOSE_SERVER_DROPDOWN', 'TOGGLE_SERVER_DROPDOWN', - 'CLOSE_SORT_DROPDOWN', - 'TOGGLE_SORT_DROPDOWN', 'OPEN_SEARCH_HEADER', 'CLOSE_SEARCH_HEADER' ]); diff --git a/app/actions/rooms.js b/app/actions/rooms.js index 63e095cea..0f708f8cf 100644 --- a/app/actions/rooms.js +++ b/app/actions/rooms.js @@ -45,18 +45,6 @@ export function toggleServerDropdown() { }; } -export function closeSortDropdown() { - return { - type: types.ROOMS.CLOSE_SORT_DROPDOWN - }; -} - -export function toggleSortDropdown() { - return { - type: types.ROOMS.TOGGLE_SORT_DROPDOWN - }; -} - export function openSearchHeader() { return { type: types.ROOMS.OPEN_SEARCH_HEADER diff --git a/app/constants/constantDisplayMode.js b/app/constants/constantDisplayMode.js new file mode 100644 index 000000000..d7d7e1d53 --- /dev/null +++ b/app/constants/constantDisplayMode.js @@ -0,0 +1,2 @@ +export const DISPLAY_MODE_CONDENSED = 'condensed'; +export const DISPLAY_MODE_EXPANDED = 'expanded'; diff --git a/app/containers/Avatar/Avatar.tsx b/app/containers/Avatar/Avatar.tsx index e35f0f589..286bcc060 100644 --- a/app/containers/Avatar/Avatar.tsx +++ b/app/containers/Avatar/Avatar.tsx @@ -85,7 +85,7 @@ const Avatar = React.memo( } return ( - + {image} {children} diff --git a/app/containers/FormContainer.tsx b/app/containers/FormContainer.tsx index 522bf489e..46522508b 100644 --- a/app/containers/FormContainer.tsx +++ b/app/containers/FormContainer.tsx @@ -22,11 +22,11 @@ const styles = StyleSheet.create({ } }); -export const FormContainerInner = ({ children }: { children: JSX.Element }) => ( +export const FormContainerInner = ({ children }: { children: React.ReactNode }): JSX.Element => ( {children} ); -const FormContainer = ({ children, theme, testID, ...props }: IFormContainer) => ( +const FormContainer = ({ children, theme, testID, ...props }: IFormContainer): JSX.Element => ( // @ts-ignore ); }; @@ -151,6 +157,8 @@ const mapStateToProps = state => ({ isMasterDetail: state.app.isMasterDetail, server: state.server.server, useRealName: state.settings.UI_Use_Real_Name, - queued: getInquiryQueueSelector(state) + queued: getInquiryQueueSelector(state), + showAvatar: state.sortPreferences.showAvatar, + displayMode: state.sortPreferences.displayMode }); export default connect(mapStateToProps)(withDimensions(withTheme(QueueListView))); diff --git a/app/i18n/locales/ar.json b/app/i18n/locales/ar.json index d48c9ccb1..231b8154a 100644 --- a/app/i18n/locales/ar.json +++ b/app/i18n/locales/ar.json @@ -79,7 +79,6 @@ "error-user-registration-secret": "التسجيل مسموح به عبر عنوان الويب السري فقط", "error-you-are-last-owner": "أنت المالك الأخير. يرجى تعيين مالك جديد قبل مغادرة الغرفة", "Actions": "الإجراءات", - "activity": "نشاط", "Activity": "النشاط", "Add_Reaction": "إضافة تفاعل", "Add_Server": "إضافة خادم", @@ -224,7 +223,6 @@ "Everyone_can_access_this_channel": "يمكن للجميع الوصول إلى هذه القناة", "Error_uploading": "خطأ في الرفع", "Expiration_Days": "انتهاء (أيام)", - "Favorite": "مفضل", "Favorites": "مفضلات", "Files": "ملفات", "File_description": "وصف الملف", @@ -241,9 +239,6 @@ "Forward_to_user": "إعادة توجيه لمستخدم", "Full_table": "انقر لرؤية الجدول كاملاً", "Generate_New_Link": "إنشاء رابط جديد", - "Group_by_favorites": "جمع حسب المفضلة", - "Group_by_type": "جمع حسب النوع", - "Hide": "إخفاء", "Has_joined_the_channel": "انضم إلى القناة", "Has_joined_the_conversation": "انضم إلى المحادثة", "Has_left_the_channel": "غادر القناة", @@ -321,7 +316,6 @@ "My_servers": "الخوادم", "N_people_reacted": "{{n}} تفاعل الناس", "N_users": "{{n}} مستخدمين", - "name": "اسم", "Name": "اسم", "Navigation_history": "تاريخ التصفح", "Never": "أبداً", @@ -398,7 +392,6 @@ "Reactions_are_disabled": "التفاعل معطل", "Reactions_are_enabled": "التفاعل مفعل", "Reactions": "التفاعلات", - "Read": "قراءة", "Read_External_Permission_Message": "يحتاج Rocket.chat للوصول إلى الصور والملفات الموجودة على الجهاز", "Read_External_Permission": "صلاحية قراءة الوسائط", "Read_Only_Channel": "قناة للقراءة فقط", @@ -490,7 +483,6 @@ "Sign_in_your_server": "تسجيل الدخول إلى الخادم الخاص بك", "Sign_Up": "تسجيل جديد", "Some_field_is_invalid_or_empty": "بعض الحقول غير صالحة أو فارغة", - "Sorting_by": "فرز حسب {{key}}", "Sound": "الصوت", "Star_room": "تمييز الغرفة", "Star": "تمييز", @@ -529,7 +521,6 @@ "unarchive": "إلغاء الأرشفة", "UNARCHIVE": "إلغاء الأرشفة", "Unblock_user": "إلغاء حظر عن مستخدم", - "Unfavorite": "إزالة من المفضلة", "Unfollowed_thread": "موضوع غير متابع", "Unmute": "إلغاء كتم", "unmuted": "إلغاء كتم", diff --git a/app/i18n/locales/de.json b/app/i18n/locales/de.json index 55b32c1fe..affe28905 100644 --- a/app/i18n/locales/de.json +++ b/app/i18n/locales/de.json @@ -81,7 +81,6 @@ "error-you-are-last-owner": "Du bist der letzte Besitzer. Bitte setze einen neuen Besitzer, bevor du den Raum verlässt.", "error-status-not-allowed": "Unsichtbar-Status ist deaktiviert", "Actions": "Aktionen", - "activity": "Aktivität", "Activity": "Aktivität", "Add_Reaction": "Reaktion hinzufügen", "Add_Server": "Server hinzufügen", @@ -232,7 +231,6 @@ "Everyone_can_access_this_team": "Jeder kann auf dieses Team zugreifen", "Error_uploading": "Fehler beim Hochladen", "Expiration_Days": "läuft ab (Tage)", - "Favorite": "Favorisieren", "Favorites": "Favoriten", "Files": "Dateien", "File_description": "Dateibeschreibung", @@ -249,9 +247,6 @@ "Forward_to_user": "Weiterleiten an Benutzer", "Full_table": "Klicken um die ganze Tabelle anzuzeigen", "Generate_New_Link": "Neuen Link erstellen", - "Group_by_favorites": "Nach Favoriten gruppieren", - "Group_by_type": "Gruppieren nach Typ", - "Hide": "Ausblenden", "Has_joined_the_channel": "Ist dem Kanal beigetreten", "Has_joined_the_conversation": "Hat sich dem Gespräch angeschlossen", "Has_left_the_channel": "Hat den Kanal verlassen", @@ -334,7 +329,6 @@ "N_people_reacted": "{{n}} Leute haben reagiert", "N_users": "{{n}} Benutzer", "N_channels": "{{n}} Kanäle", - "name": "Name", "Name": "Name", "Navigation_history": "Navigations-Verlauf", "Never": "Niemals", @@ -412,7 +406,6 @@ "Reactions_are_disabled": "Reaktionen sind deaktiviert", "Reactions_are_enabled": "Reaktionen sind aktiviert", "Reactions": "Reaktionen", - "Read": "Gelesen", "Read_External_Permission_Message": "Rocket.Chat benötigt Zugriff auf deine Fotos, Medien und Dateien auf deinem Gerät", "Read_External_Permission": "Lese-Zugriff auf Medien", "Read_Only_Channel": "Nur-Lese-Kanal", @@ -507,7 +500,6 @@ "Sign_in_your_server": "Melde dich bei deinem Server an", "Sign_Up": "Anmelden", "Some_field_is_invalid_or_empty": "Ein Feld ist ungültig oder leer", - "Sorting_by": "Sortierung nach {{key}}", "Sound": "Ton", "Star_room": "Favorisierter Raum", "Star": "Favoriten", @@ -546,7 +538,6 @@ "unarchive": "wiederherstellen", "UNARCHIVE": "WIEDERHERSTELLEN", "Unblock_user": "Benutzer entsperren", - "Unfavorite": "Nicht mehr favorisieren", "Unfollowed_thread": "Thread nicht mehr folgen", "Unmute": "Stummschaltung aufheben", "unmuted": "Stummschaltung aufgehoben", diff --git a/app/i18n/locales/en.json b/app/i18n/locales/en.json index 8d409c1cc..f8982490d 100644 --- a/app/i18n/locales/en.json +++ b/app/i18n/locales/en.json @@ -81,7 +81,6 @@ "error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.", "error-status-not-allowed": "Invisible status is disabled", "Actions": "Actions", - "activity": "activity", "Activity": "Activity", "Add_Reaction": "Add Reaction", "Add_Server": "Add Server", @@ -232,7 +231,6 @@ "Everyone_can_access_this_team": "Everyone can access this team", "Error_uploading": "Error uploading", "Expiration_Days": "Expiration (Days)", - "Favorite": "Favorite", "Favorites": "Favorites", "Files": "Files", "File_description": "File description", @@ -249,9 +247,6 @@ "Forward_to_user": "Forward to user", "Full_table": "Click to see full table", "Generate_New_Link": "Generate New Link", - "Group_by_favorites": "Group favorites", - "Group_by_type": "Group by type", - "Hide": "Hide", "Has_joined_the_channel": "has joined the channel", "Has_joined_the_conversation": "has joined the conversation", "Has_left_the_channel": "has left the channel", @@ -334,7 +329,6 @@ "N_people_reacted": "{{n}} people reacted", "N_users": "{{n}} users", "N_channels": "{{n}} channels", - "name": "name", "Name": "Name", "Navigation_history": "Navigation history", "Never": "Never", @@ -412,7 +406,6 @@ "Reactions_are_disabled": "Reactions are disabled", "Reactions_are_enabled": "Reactions are enabled", "Reactions": "Reactions", - "Read": "Read", "Read_External_Permission_Message": "Rocket.Chat needs to access photos, media, and files on your device", "Read_External_Permission": "Read Media Permission", "Read_Only_Channel": "Read Only Channel", @@ -507,7 +500,6 @@ "Sign_in_your_server": "Sign in your server", "Sign_Up": "Sign Up", "Some_field_is_invalid_or_empty": "Some field is invalid or empty", - "Sorting_by": "Sorting by {{key}}", "Sound": "Sound", "Star_room": "Star room", "Star": "Star", @@ -546,7 +538,6 @@ "unarchive": "unarchive", "UNARCHIVE": "UNARCHIVE", "Unblock_user": "Unblock user", - "Unfavorite": "Unfavorite", "Unfollowed_thread": "Unfollowed thread", "Unmute": "Unmute", "unmuted": "unmuted", @@ -772,6 +763,13 @@ "Converting_Team_To_Channel": "Converting Team to Channel", "Select_Team_Channels_To_Delete": "Select the Team’s Channels you would like to delete, the ones you do not select will be moved to the Workspace. \n\nNotice that public Channels will be public and visible to everyone.", "You_are_converting_the_team": "You are converting this Team to a Channel", + "Display": "Display", + "Avatars": "Avatars", + "Sort_by": "Sort by", + "Group_by": "Group by", + "Types": "Types", + "Expanded": "Expanded", + "Condensed": "Condensed", "creating_discussion": "creating discussion", "Canned_Responses": "Canned Responses", "No_match_found": "No match found.", diff --git a/app/i18n/locales/es-ES.json b/app/i18n/locales/es-ES.json index faf9bca29..d4bff085b 100644 --- a/app/i18n/locales/es-ES.json +++ b/app/i18n/locales/es-ES.json @@ -76,7 +76,6 @@ "error-user-registration-secret": "El registro de usuarios sólo está permitido por URL secretas", "error-you-are-last-owner": "Eres el único propietario existente. Debes establecer un nuevo propietario antes de abandonar la sala.", "Actions": "Acciones", - "activity": "actividad", "Activity": "Actividad", "Add_Reaction": "Añadir reacción", "Add_Server": "Añadir servidor", @@ -176,7 +175,6 @@ "Enable_notifications": "Permitir notificaciones", "Everyone_can_access_this_channel": "Todos los usuarios pueden acceder a este canal", "Error_uploading": "Error en la subida", - "Favorite": "Favorito", "Favorites": "Favoritos", "Files": "Archivos", "File_description": "Descripción del archivo", @@ -188,9 +186,6 @@ "Forgot_password": "¿Ha olvidado su contraseña?", "Forgot_Password": "Olvidé la contraseña", "Full_table": "Click para ver la tabla completa", - "Group_by_favorites": "Agrupar por favoritos", - "Group_by_type": "Agrupar por tipo", - "Hide": "Ocultar", "Has_joined_the_channel": "se ha unido al canal", "Has_joined_the_conversation": "se ha unido a la conversación", "Has_left_the_channel": "ha dejado el canal", @@ -237,7 +232,6 @@ "My_servers": "Mis servidores", "N_people_reacted": "Han reaccionado {{n}} personas", "N_users": "{{n}} usuarios", - "name": "nombre", "Name": "Nombre", "New_Message": "Nuevo mensaje", "New_Password": "Nueva contraseña", @@ -292,7 +286,6 @@ "Reactions_are_disabled": "Las reacciones están desactivadas", "Reactions_are_enabled": "Las reacciones están activadas", "Reactions": "Reacciones", - "Read": "Leer", "Read_Only_Channel": "Canal de sólo lectura", "Read_Only": "Sólo lectura ", "Read_Receipt": "Comprobante de lectura", @@ -356,7 +349,6 @@ "Sign_in_your_server": "Accede a tu servidor", "Sign_Up": "Registrarse", "Some_field_is_invalid_or_empty": "Algún campo no es correcto o está vacío", - "Sorting_by": "Ordenado por {{key}}", "Sound": "Sonido", "Star_room": "Destacar sala", "Star": "Destacar", @@ -390,7 +382,6 @@ "unarchive": "desarchivar", "UNARCHIVE": "DESARCHIVAR", "Unblock_user": "Desbloquear usuario", - "Unfavorite": "Quitar favorito", "Unfollowed_thread": "Dejar de seguir el hilo", "Unmute": "Desmutear", "unmuted": "Desmuteado", diff --git a/app/i18n/locales/fr.json b/app/i18n/locales/fr.json index 242a3e277..ff24ace66 100644 --- a/app/i18n/locales/fr.json +++ b/app/i18n/locales/fr.json @@ -81,7 +81,6 @@ "error-you-are-last-owner": "Vous êtes le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de quitter le salon.", "error-status-not-allowed": "Le statut invisible est désactivé", "Actions": "Actions", - "activity": "activité", "Activity": "Activité", "Add_Reaction": "Ajouter une réaction", "Add_Server": "Ajouter un serveur", @@ -232,7 +231,6 @@ "Everyone_can_access_this_team": "Tout le monde peut accéder à cette équipe", "Error_uploading": "Erreur lors de l'envoi", "Expiration_Days": "Expiration (Jours)", - "Favorite": "Favori", "Favorites": "Favoris", "Files": "Fichiers", "File_description": "Description du fichier", @@ -249,9 +247,6 @@ "Forward_to_user": "Transmettre à l'utilisateur", "Full_table": "Cliquez pour voir le tableau complet", "Generate_New_Link": "Générer un nouveau lien", - "Group_by_favorites": "Grouper par favoris", - "Group_by_type": "Grouper par type", - "Hide": "Cacher", "Has_joined_the_channel": "a rejoint le canal", "Has_joined_the_conversation": "a rejoint la conversation", "Has_left_the_channel": "a quitté le canal", @@ -334,7 +329,6 @@ "N_people_reacted": "{{n}} personnes ont réagi", "N_users": "{{n}} utilisateurs", "N_channels": "{{n}} canaux", - "name": "nom", "Name": "Nom", "Navigation_history": "Historique de navigation", "Never": "Jamais", @@ -412,7 +406,6 @@ "Reactions_are_disabled": "Les réactions sont désactivées", "Reactions_are_enabled": "Les réactions sont activées", "Reactions": "Réactions", - "Read": "Lecture", "Read_External_Permission_Message": "Rocket.Chat doit accéder aux photos, aux médias et aux fichiers sur votre appareil", "Read_External_Permission": "Permission de lecture des fichiers", "Read_Only_Channel": "Canal en lecture seule", @@ -507,7 +500,6 @@ "Sign_in_your_server": "Connectez-vous à votre serveur", "Sign_Up": "S'inscrire", "Some_field_is_invalid_or_empty": "Certains champs sont invalides ou vides", - "Sorting_by": "Tri par {{key}}", "Sound": "Son", "Star_room": "Canal favoris", "Star": "Mettre en favoris", @@ -546,7 +538,6 @@ "unarchive": "désarchiver", "UNARCHIVE": "DÉSARCHIVER", "Unblock_user": "Débloquer l'utilisateur", - "Unfavorite": "Supprimer des favoris", "Unfollowed_thread": "Ne plus suivre ce fil", "Unmute": "Rendre la parole", "unmuted": "rendu la parole", diff --git a/app/i18n/locales/it.json b/app/i18n/locales/it.json index 87447244c..c9e1cdfc5 100644 --- a/app/i18n/locales/it.json +++ b/app/i18n/locales/it.json @@ -79,7 +79,6 @@ "error-user-registration-secret": "Registrazione utente permessa solo via URL segreto", "error-you-are-last-owner": "Sei l'ultimo proprietario rimasto. Imposta un nuovo proprietario prima di lasciare la stanza.", "Actions": "Azioni", - "activity": "attività", "Activity": "Attività", "Add_Reaction": "Aggiungi reazione", "Add_Server": "Aggiungi server", @@ -227,7 +226,6 @@ "Everyone_can_access_this_channel": "Tutti hanno accesso a questo canale", "Error_uploading": "Errore nel caricamento di", "Expiration_Days": "Scadenza (giorni)", - "Favorite": "Preferito", "Favorites": "Preferiti", "Files": "File", "File_description": "Descrizione file", @@ -244,9 +242,6 @@ "Forward_to_user": "Inoltra ad udente", "Full_table": "Clicca per la tabella completa", "Generate_New_Link": "Genera nuovo link", - "Group_by_favorites": "Raggruppa per preferiti", - "Group_by_type": "Raggruppa per tipo", - "Hide": "Nascondi", "Has_joined_the_channel": "si è unito al canale", "Has_joined_the_conversation": "si è unito alla conversazione", "Has_left_the_channel": "ha lasciato il canale", @@ -326,7 +321,6 @@ "My_servers": "I miei server", "N_people_reacted": "{{n}} persone hanno reagito", "N_users": "{{n}} utenti", - "name": "nome", "Name": "Nome", "Navigation_history": "Cronologia di navigazione", "Never": "Mai", @@ -404,7 +398,6 @@ "Reactions_are_disabled": "Le reazioni sono disabilitate", "Reactions_are_enabled": "Le reazioni sono abilitate", "Reactions": "Reazioni", - "Read": "Letto", "Read_External_Permission_Message": "Rocket.Chat deve accedere alle foto, media, e documenti sul tuo dispositivo", "Read_External_Permission": "Permesso di Lettura della Memoria", "Read_Only_Channel": "Canale in sola lettura", @@ -498,7 +491,6 @@ "Sign_in_your_server": "Accedi al tuo server", "Sign_Up": "Registrati", "Some_field_is_invalid_or_empty": "Un campo non è valido o è vuoto", - "Sorting_by": "Ordina per {{key}}", "Sound": "Suono", "Star_room": "Aggiungi stanza ai preferiti", "Star": "Aggiungi ai preferiti", @@ -537,7 +529,6 @@ "unarchive": "rimuovi dall'archivio", "UNARCHIVE": "RIMUOVI DALL'ARCHIVIO", "Unblock_user": "Sblocca utente", - "Unfavorite": "Rimuovi preferito", "Unfollowed_thread": "Non segui più il thread", "Unmute": "Attiva notifiche", "unmuted": "notifiche attivate", diff --git a/app/i18n/locales/ja.json b/app/i18n/locales/ja.json index 48d0ef1be..4f52473c8 100644 --- a/app/i18n/locales/ja.json +++ b/app/i18n/locales/ja.json @@ -77,7 +77,6 @@ "error-user-registration-secret": "ユーザーの登録は登録用URLからのみ許可されています", "error-you-are-last-owner": "あなたは最後のオーナーです。ルームを退出する前に別のオーナーを設定してください。", "Actions": "アクション", - "activity": "アクティビティ", "Activity": "アクティビティ順", "Add_Reaction": "リアクションを追加", "Add_Server": "サーバーを追加", @@ -183,7 +182,6 @@ "Everyone_can_access_this_channel": "全員このチャンネルにアクセスできます", "Error_uploading": "アップロードエラー", "Expiration_Days": "期限切れ (日)", - "Favorite": "お気に入り", "Favorites": "お気に入り", "Files": "ファイル", "File_description": "ファイルの説明", @@ -196,9 +194,6 @@ "Forgot_Password": "パスワードを忘れた", "Full_table": "クリックしてテーブル全体を見る", "Generate_New_Link": "新しいリンクを生成", - "Group_by_favorites": "お気に入りをグループ化", - "Group_by_type": "タイプ別にグループ化", - "Hide": "隠す", "Has_joined_the_channel": "はチャンネルに参加しました", "Has_joined_the_conversation": "は会話に参加しました", "Has_left_the_channel": "はチャンネルを退出しました", @@ -251,7 +246,6 @@ "My_servers": "自分のサーバー", "N_people_reacted": "{{n}}人がリアクションしました", "N_users": "{{n}}人", - "name": "アルファベット", "Name": "名前", "Never": "ずっと受け取らない", "New_Message": "メッセージ", @@ -308,7 +302,6 @@ "Reactions_are_disabled": "リアクションは無効化されています", "Reactions_are_enabled": "リアクションは有効化されています", "Reactions": "リアクション", - "Read": "読む", "Read_Only_Channel": "読み取り専用チャンネル", "Read_Only": "読み取り専用", "Read_Receipt": "レシートを見る", @@ -382,7 +375,6 @@ "Sign_in_your_server": "サーバーに接続", "Sign_Up": "登録", "Some_field_is_invalid_or_empty": "不正、または空の入力欄があります。", - "Sorting_by": "{{key}}順", "Sound": "音", "Star_room": "お気に入りルーム", "Star": "お気に入り", @@ -416,7 +408,6 @@ "unarchive": "アーカイブ解除", "UNARCHIVE": "アーカイブ解除", "Unblock_user": "ブロックを解除", - "Unfavorite": "お気に入り解除", "Unfollowed_thread": "スレッド更新時に通知しない", "Unmute": "ミュート解除", "unmuted": "ミュート解除しました", diff --git a/app/i18n/locales/nl.json b/app/i18n/locales/nl.json index 33acb243b..dcd9f6cfa 100644 --- a/app/i18n/locales/nl.json +++ b/app/i18n/locales/nl.json @@ -81,7 +81,6 @@ "error-you-are-last-owner": "Je bent de laatste eigenaar. Stel een nieuwe eigenaar in voordat je de kamer verlaat.", "error-status-not-allowed": "Onzichtbare status is uitgeschakeld", "Actions": "Acties", - "activity": "activiteit", "Activity": "Activiteit", "Add_Reaction": "Reactie toevoegen", "Add_Server": "Server toevoegen", @@ -232,7 +231,6 @@ "Everyone_can_access_this_team": "Iedereen heeft toegang tot dit team", "Error_uploading": "Fout bij uploaden", "Expiration_Days": "Vervaldatum (Dagen)", - "Favorite": "Favoriet", "Favorites": "Favorieten", "Files": "Bestanden", "File_description": "Bestandsbeschrijving", @@ -249,9 +247,6 @@ "Forward_to_user": "Doorsturen naar gebruiker", "Full_table": "Klik om de volledige tabel te zien", "Generate_New_Link": "Nieuwe link genereren", - "Group_by_favorites": "Groepeer favorieten", - "Group_by_type": "Groeperen op type", - "Hide": "Verberg", "Has_joined_the_channel": "is bij het kanaal gekomen", "Has_joined_the_conversation": "heeft zich bij het gesprek aangesloten", "Has_left_the_channel": "heeft het kanaal verlaten", @@ -334,7 +329,6 @@ "N_people_reacted": "{{n}} mensen hebben gereageerd", "N_users": "{{n}} gebruikers", "N_channels": "{{n}} kanalen", - "name": "naam", "Name": "Naam", "Navigation_history": "Navigatie geschiedenis", "Never": "Nooit", @@ -412,7 +406,6 @@ "Reactions_are_disabled": "Reacties zijn uitgeschakeld", "Reactions_are_enabled": "Reacties zijn ingeschakeld", "Reactions": "Reacties", - "Read": "Lezen", "Read_External_Permission_Message": "Rocket.Chat heeft toegang nodig tot foto's, media en bestanden op je apparaat", "Read_External_Permission": "Lees toestemming voor media", "Read_Only_Channel": "Alleen-lezen kanaal", @@ -507,7 +500,6 @@ "Sign_in_your_server": "Log in op je server", "Sign_Up": "Registreren", "Some_field_is_invalid_or_empty": "Sommige velden zijn ongeldig of leeg", - "Sorting_by": "Sorteren op {{key}}", "Sound": "Geluid", "Star_room": "Favoriete kanalen", "Star": "Ster", @@ -546,7 +538,6 @@ "unarchive": "dearchiveren", "UNARCHIVE": "DEARCHIVEREN", "Unblock_user": "Deblokkeer gebruiker", - "Unfavorite": "Uit favorieten halen", "Unfollowed_thread": "Draad ontvolgd", "Unmute": "Dempen opheffen", "unmuted": "ongedempt", diff --git a/app/i18n/locales/pt-BR.json b/app/i18n/locales/pt-BR.json index 57c31c067..957d6ceca 100644 --- a/app/i18n/locales/pt-BR.json +++ b/app/i18n/locales/pt-BR.json @@ -77,7 +77,6 @@ "error-you-are-last-owner": "Você é o último proprietário da sala. Por favor defina um novo proprietário antes de sair.", "error-status-not-allowed": "O status invisível está desativado", "Actions": "Ações", - "activity": "atividade", "Activity": "Atividade", "Add_Reaction": "Reagir", "Add_Server": "Adicionar servidor", @@ -219,7 +218,6 @@ "Everyone_can_access_this_channel": "Todos podem acessar este canal", "Error_uploading": "Erro subindo", "Expiration_Days": "Expira em (dias)", - "Favorite": "Adicionar aos Favoritos", "Favorites": "Favoritos", "Files": "Arquivos", "File_description": "Descrição do arquivo", @@ -236,9 +234,6 @@ "Forward_to_user": "Encaminhar para usuário", "Full_table": "Clique para ver a tabela completa", "Generate_New_Link": "Gerar novo convite", - "Group_by_favorites": "Agrupar favoritos", - "Group_by_type": "Agrupar por tipo", - "Hide": "Ocultar", "Has_joined_the_channel": "entrou no canal", "Has_joined_the_conversation": "entrou na conversa", "Has_left_the_channel": "saiu da conversa", @@ -307,7 +302,6 @@ "muted": "mudo", "N_people_reacted": "{{n}} pessoas reagiram", "N_users": "{{n}} usuários", - "name": "nome", "Name": "Nome", "Navigation_history": "Histórico de navegação", "Never": "Nunca", @@ -466,7 +460,6 @@ "Sign_in_your_server": "Entrar no seu servidor", "Sign_Up": "Registrar", "Some_field_is_invalid_or_empty": "Algum campo está inválido ou vazio", - "Sorting_by": "Ordenando por {{key}}", "Sound": "Som da notificação", "Star_room": "Favoritar sala", "Star": "Favorito", @@ -502,7 +495,6 @@ "unarchive": "desarquivar", "UNARCHIVE": "DESARQUIVAR", "Unblock_user": "Desbloquear usuário", - "Unfavorite": "Remover dos Favoritos", "Unfollowed_thread": "Parou de seguir tópico", "Unmute": "Permitir que o usuário fale", "unmuted": "permitiu que o usuário fale", @@ -673,6 +665,13 @@ "Deleted_The_Team_Successfully": "Time deletado com sucesso", "Deleted_The_Room_Successfully": "Sala deletada com sucesso", "Convert_to_Channel": "Converter para um Canal", + "Display": "Display", + "Avatars": "Avatars", + "Sort_by": "Ordenar por", + "Group_by": "Agrupar por", + "Types": "Tipos", + "Expanded": "Estendido", + "Condensed": "Condensado", "Canned_Responses": "Respostas Predefinidas", "No_match_found": "Nenhum resultado encontrado", "Check_canned_responses": "Verifique nas respostas predefinidas", diff --git a/app/i18n/locales/pt-PT.json b/app/i18n/locales/pt-PT.json index fb96c42e7..6ac16d862 100644 --- a/app/i18n/locales/pt-PT.json +++ b/app/i18n/locales/pt-PT.json @@ -81,7 +81,6 @@ "error-you-are-last-owner": "Você é o último proprietário. Por favor, defina novo proprietário antes de sair da sala.", "error-status-not-allowed": "O estado invisível está desactivado", "Actions": "Acções", - "activity": "actividade", "Activity": "Actividade", "Add_Reaction": "Adicionar Reacção", "Add_Server": "Adicionar Servidor", @@ -232,7 +231,6 @@ "Everyone_can_access_this_team": "Todos podem aceder a esta equipa", "Error_uploading": "Erro ao fazer o envio", "Expiration_Days": "Validade (Dias)", - "Favorite": "Favorito", "Favorites": "Favoritos", "Files": "Ficheiros", "File_description": "Descrição do ficheiro", @@ -249,9 +247,6 @@ "Forward_to_user": "Reencaminhar para o utilizador", "Full_table": "Clique para ver a tabela completa", "Generate_New_Link": "Gerar Novo Link", - "Group_by_favorites": "Agrupar por favoritos", - "Group_by_type": "Agrupar por tipo", - "Hide": "Esconder", "Has_joined_the_channel": "entrou no canal", "Has_joined_the_conversation": "entrou na conversa", "Has_left_the_channel": "saiu do canal", @@ -333,7 +328,6 @@ "N_people_reacted": "{{n}} pessoas reagiram", "N_users": "{{n}} utilizadores", "N_channels": "{{n}} canais", - "name": "nome", "Name": "Nome", "Navigation_history": "Histórico de navegação", "Never": "Nunca", @@ -411,7 +405,6 @@ "Reactions_are_disabled": "Reacções desactivadas", "Reactions_are_enabled": "Reacções activadas", "Reactions": "Reacções", - "Read": "Ler", "Read_External_Permission_Message": "Rocket.Chat precisa acessar fotos, média e arquivos em seu dispositivo", "Read_External_Permission": "Permissão de leitura da média", "Read_Only_Channel": "Canal só de leitura", @@ -458,7 +451,6 @@ "Sign_in_your_server": "Entre no seu servidor", "Sign_Up": "Inscreva-se", "Some_field_is_invalid_or_empty": "Algum campo é inválido ou está vazio", - "Sorting_by": "Ordenar por {{key}}", "Star_room": "Marcar como favorito", "Star": "Dar estrela", "Starred_Messages": "Mensagens com estrela", diff --git a/app/i18n/locales/ru.json b/app/i18n/locales/ru.json index 359aadf99..d3102060a 100644 --- a/app/i18n/locales/ru.json +++ b/app/i18n/locales/ru.json @@ -81,7 +81,6 @@ "error-you-are-last-owner": "Вы последний владелец. Пожалуйста, назначьте нового владельца, прежде чем покинуть чат.", "error-status-not-allowed": "Статус Невидимый отключён", "Actions": "Действия", - "activity": "активности", "Activity": "По активности", "Add_Reaction": "Добавить реакцию", "Add_Server": "Добавить сервер", @@ -232,7 +231,6 @@ "Everyone_can_access_this_team": "Каждый может получить доступ к этой Команде", "Error_uploading": "Ошибка загрузки", "Expiration_Days": "Срок действия (Дни)", - "Favorite": "Избранное", "Favorites": "Избранные", "Files": "Файлы", "File_description": "Описание файла", @@ -249,9 +247,6 @@ "Forward_to_user": "Перенаправить пользователю", "Full_table": "Нажмите, чтобы увидеть полную таблицу", "Generate_New_Link": "Сгенерировать Новую Ссылку", - "Group_by_favorites": "По избранным", - "Group_by_type": "По типу", - "Hide": "Скрыть", "Has_joined_the_channel": "присоединился к каналу", "Has_joined_the_conversation": "присоединился к беседе", "Has_left_the_channel": "покинул канал", @@ -334,7 +329,6 @@ "N_people_reacted": "отреагировало {{n}} человек", "N_users": "{{n}} пользователи", "N_channels": "{{n}} каналов", - "name": "имя", "Name": "Имя", "Navigation_history": "История навигации", "Never": "Никогда", @@ -412,7 +406,6 @@ "Reactions_are_disabled": "Реакции отключены", "Reactions_are_enabled": "Реакции активированы", "Reactions": "Реакции", - "Read": "Читать", "Read_External_Permission_Message": "Rocket.Chat необходим доступ к фотографиям, медиа и другим файлам на вашем устройстве", "Read_External_Permission": "Разрешение на Чтение Медиа", "Read_Only_Channel": "Канал только для чтения", @@ -507,7 +500,6 @@ "Sign_in_your_server": "Войдите на ваш сервер", "Sign_Up": "Регистрация", "Some_field_is_invalid_or_empty": "Некоторые поля недопустимы или пусты", - "Sorting_by": "Сортировка по {{key}}", "Sound": "Звук", "Star_room": "В избранное", "Star": "Отметить", @@ -546,7 +538,6 @@ "unarchive": "разархивировать", "UNARCHIVE": "РАЗАРХИВИРОВАТЬ", "Unblock_user": "Разблокировать пользователя", - "Unfavorite": "Удалить из избранного", "Unfollowed_thread": "Не следить", "Unmute": "Отменить заглушивание", "unmuted": "Заглушивание отменено", diff --git a/app/i18n/locales/tr.json b/app/i18n/locales/tr.json index 81a06449b..dfe44fdef 100644 --- a/app/i18n/locales/tr.json +++ b/app/i18n/locales/tr.json @@ -79,7 +79,6 @@ "error-user-registration-secret": "Kullanıcı kaydına yalnızca Gizli URL aracılığıyla izin verilir!", "error-you-are-last-owner": "Son sahibi sizsiniz. Lütfen odadan ayrılmadan önce yeni bir sahip belirleyin.", "Actions": "İşlemler", - "activity": "etkinlik", "Activity": "Etkinlik", "Add_Reaction": "Tepki ekle", "Add_Server": "Sunucu ekle", @@ -227,7 +226,6 @@ "Everyone_can_access_this_channel": "Bu kanala herkes erişebilir", "Error_uploading": "Yükleme hatası", "Expiration_Days": "Geçerlilik Süresi (Gün)", - "Favorite": "Favori", "Favorites": "Favoriler", "Files": "Dosyalar", "File_description": "Dosya açıklaması", @@ -244,9 +242,6 @@ "Forward_to_user": "Kullanıcıya İlet", "Full_table": "Tam tabloyu görmek için tıklayın", "Generate_New_Link": "Yeni Bağlantı Oluştur", - "Group_by_favorites": "Favorilere göre grupla", - "Group_by_type": "Türe göre grupla", - "Hide": "Gizle", "Has_joined_the_channel": "kanala katıldı", "Has_joined_the_conversation": "sohbete katıldı", "Has_left_the_channel": "kanaldan ayrıldı", @@ -327,7 +322,6 @@ "My_servers": "Sunucularım", "N_people_reacted": "{{n}} kişi tepki verdi", "N_users": "{{n}} kullanıcı", - "name": "isim", "Name": "İsim", "Navigation_history": "Gezinti geçmişi", "Never": "Asla", @@ -405,7 +399,6 @@ "Reactions_are_disabled": "Tepkiler devre dışı bırakıldı", "Reactions_are_enabled": "Tepkiler etkinleştirildi", "Reactions": "Tepkiler", - "Read": "Oku", "Read_External_Permission_Message": "Rocket.Chat'in cihazınızdaki fotoğraflara, medyaya ve dosyalara erişmesi gerekiyor", "Read_External_Permission": "Medya Okuma İzni ", "Read_Only_Channel": "Yazma Kısıtlı Kanal", @@ -498,7 +491,6 @@ "Sign_in_your_server": "Sunucunuzda oturum açın", "Sign_Up": "Kaydol", "Some_field_is_invalid_or_empty": "Bazı alanlar geçersiz veya boş", - "Sorting_by": "{{key}} göre sıralanıyor", "Sound": "Ses", "Star_room": "Odayı Yıldızla", "Star": "Yıldızla", @@ -537,7 +529,6 @@ "unarchive": "arşivden çıkar", "UNARCHIVE": "ARŞİVDEN ÇIKAR", "Unblock_user": "Kullanıcının engelini kaldır", - "Unfavorite": "Favorilerden Çıkar", "Unfollowed_thread": "Takip edilmeyen başlık", "Unmute": "Sesi Aç", "unmuted": "Sesi Açıldı", diff --git a/app/i18n/locales/zh-CN.json b/app/i18n/locales/zh-CN.json index fd026bfdd..a304b3926 100644 --- a/app/i18n/locales/zh-CN.json +++ b/app/i18n/locales/zh-CN.json @@ -79,7 +79,6 @@ "error-user-registration-secret": "只能透过加密网址进行用戶注册", "error-you-are-last-owner": "您是最后的拥有者。请删除此人之前设置一个新的拥有者。", "Actions": "操作", - "activity": "活动时间", "Activity": "按活动时间排列", "Add_Reaction": "增加表情貼", "Add_Server": "創建服务器", @@ -227,7 +226,6 @@ "Everyone_can_access_this_channel": "每个人都可以访问此频道", "Error_uploading": "错误上传", "Expiration_Days": "到期 (日)", - "Favorite": "收藏", "Favorites": "收藏", "Files": "文件", "File_description": "文件描述", @@ -244,9 +242,6 @@ "Forward_to_user": "转发给用戶", "Full_table": "点击以查看完整表格", "Generate_New_Link": "产生新的链接", - "Group_by_favorites": "收藏优先", - "Group_by_type": "以类型分组", - "Hide": "隐藏", "Has_joined_the_channel": "已加入频道", "Has_joined_the_conversation": "已经加入此对话", "Has_left_the_channel": "已离开频道", @@ -324,7 +319,6 @@ "My_servers": "我的服务器", "N_people_reacted": "{{n}} 人回复", "N_users": "{{n}} 位用户", - "name": "名称", "Name": "名称", "Navigation_history": "浏览历史记录", "Never": "从不", @@ -402,7 +396,6 @@ "Reactions_are_disabled": "表情貼被禁用", "Reactions_are_enabled": "表情貼被启用", "Reactions": "表情貼", - "Read": "读取", "Read_External_Permission_Message": "Rocket.Chat 需要存取您装置上的相片、多媒体及文件", "Read_External_Permission": "读取媒体权限", "Read_Only_Channel": "只读频道", @@ -496,7 +489,6 @@ "Sign_in_your_server": "登录你的服务器", "Sign_Up": "注册", "Some_field_is_invalid_or_empty": "某些字段无效或为空", - "Sorting_by": "按{{key}}排序", "Sound": "声音", "Star_room": "将聊天室标记", "Star": "标记", @@ -535,7 +527,6 @@ "unarchive": "取消封存", "UNARCHIVE": "取消封存", "Unblock_user": "解除屏蔽", - "Unfavorite": "取消收藏", "Unfollowed_thread": "取消追踪讨论", "Unmute": "取消静音", "unmuted": "静音状态", diff --git a/app/i18n/locales/zh-TW.json b/app/i18n/locales/zh-TW.json index 0725a12b4..3bcf48b3d 100644 --- a/app/i18n/locales/zh-TW.json +++ b/app/i18n/locales/zh-TW.json @@ -79,7 +79,6 @@ "error-user-registration-secret": "只能透過加密網址進行使用者註冊", "error-you-are-last-owner": "您是最後的擁有者。請刪除此人之前設置一個新的擁有者。", "Actions": "操作", - "activity": "活動時間", "Activity": "以活動時間排序", "Add_Reaction": "增加表情貼", "Add_Server": "新增伺服器", @@ -227,7 +226,6 @@ "Everyone_can_access_this_channel": "所有人皆可存取此頻道", "Error_uploading": "錯誤上傳", "Expiration_Days": "到期 (日)", - "Favorite": "我的最愛", "Favorites": "我的最愛", "Files": "檔案", "File_description": "檔案描述", @@ -244,9 +242,6 @@ "Forward_to_user": "轉發給使用者", "Full_table": "點擊以查看完整表格", "Generate_New_Link": "產生新的連結", - "Group_by_favorites": "我的最愛優先", - "Group_by_type": "以類型分組", - "Hide": "隱藏", "Has_joined_the_channel": "已加入頻道", "Has_joined_the_conversation": "已經加入此對話", "Has_left_the_channel": "已離開頻道", @@ -325,7 +320,6 @@ "My_servers": "我的伺服器", "N_people_reacted": "{{n}} 人回复", "N_users": "{{n}} 位使用者", - "name": "名稱", "Name": "名稱", "Navigation_history": "瀏覽歷史記錄", "Never": "從不", @@ -403,7 +397,6 @@ "Reactions_are_disabled": "表情貼被禁用", "Reactions_are_enabled": "表情貼被啟用", "Reactions": "表情貼", - "Read": "讀取", "Read_External_Permission_Message": "Rocket.Chat 需要存取您裝置上的相片、多媒體及檔案", "Read_External_Permission": "讀取媒體權限", "Read_Only_Channel": "唯讀頻道", @@ -497,7 +490,6 @@ "Sign_in_your_server": "登錄你的伺服器", "Sign_Up": "註冊", "Some_field_is_invalid_or_empty": "某些字段無效或為空", - "Sorting_by": "以{{key}}排序", "Sound": "聲音", "Star_room": "標記聊天室", "Star": "標記", @@ -536,7 +528,6 @@ "unarchive": "取消封存", "UNARCHIVE": "取消封存", "Unblock_user": "解除封鎖", - "Unfavorite": "取消最愛", "Unfollowed_thread": "取消追蹤討論", "Unmute": "取消靜音", "unmuted": "靜音狀態", diff --git a/app/lib/methods/getPermissions.js b/app/lib/methods/getPermissions.js index d23268f39..589aa6bd1 100644 --- a/app/lib/methods/getPermissions.js +++ b/app/lib/methods/getPermissions.js @@ -18,6 +18,10 @@ const PERMISSIONS = [ 'archive-room', 'auto-translate', 'create-invite-links', + 'create-c', + 'create-p', + 'create-d', + 'start-discussion', 'create-team', 'delete-c', 'delete-message', diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 4fd4bdb5a..40722348f 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -1380,17 +1380,19 @@ const RocketChat = { * Returns an array of boolean for each permission from permissions arg */ async hasPermission(permissions, rid) { - const db = database.active; - const subsCollection = db.get('subscriptions'); let roomRoles = []; - try { - // get the room from database - const room = await subsCollection.find(rid); - // get room roles - roomRoles = room.roles || []; - } catch (error) { - console.log('hasPermission -> Room not found'); - return permissions.map(() => false); + if (rid) { + const db = database.active; + const subsCollection = db.get('subscriptions'); + try { + // get the room from database + const room = await subsCollection.find(rid); + // get room roles + roomRoles = room.roles || []; + } catch (error) { + console.log('hasPermission -> Room not found'); + return permissions.map(() => false); + } } try { @@ -1537,11 +1539,13 @@ const RocketChat = { messageId }); }, - searchMessages(roomId, searchText) { + searchMessages(roomId, searchText, count, offset) { // RC 0.60.0 return this.sdk.get('chat.search', { roomId, - searchText + searchText, + count, + offset }); }, toggleFollowMessage(mid, follow) { diff --git a/app/presentation/RoomItem/Actions.tsx b/app/presentation/RoomItem/Actions.tsx index 92fbf98b5..19c63baaf 100644 --- a/app/presentation/RoomItem/Actions.tsx +++ b/app/presentation/RoomItem/Actions.tsx @@ -1,11 +1,12 @@ import React from 'react'; -import { Animated, Text, View } from 'react-native'; +import { Animated, View } from 'react-native'; import { RectButton } from 'react-native-gesture-handler'; -import I18n, { isRTL } from '../../i18n'; -import styles, { ACTION_WIDTH, LONG_SWIPE } from './styles'; +import { isRTL } from '../../i18n'; import { CustomIcon } from '../../lib/Icons'; import { themes } from '../../constants/colors'; +import { DISPLAY_MODE_CONDENSED } from '../../constants/constantDisplayMode'; +import styles, { ACTION_WIDTH, LONG_SWIPE, ROW_HEIGHT_CONDENSED } from './styles'; interface ILeftActions { theme: string; @@ -13,6 +14,7 @@ interface ILeftActions { isRead: boolean; width: number; onToggleReadPress(): void; + displayMode: string; } interface IRightActions { @@ -22,11 +24,14 @@ interface IRightActions { width: number; toggleFav(): void; onHidePress(): void; + displayMode: string; } const reverse = new Animated.Value(isRTL() ? -1 : 1); +const CONDENSED_ICON_SIZE = 24; +const EXPANDED_ICON_SIZE = 28; -export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress }: ILeftActions) => { +export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleReadPress, displayMode }: ILeftActions) => { const translateX = Animated.multiply( transX.interpolate({ inputRange: [0, ACTION_WIDTH], @@ -34,6 +39,10 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR }), reverse ); + + const isCondensed = displayMode === DISPLAY_MODE_CONDENSED; + const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null; + return ( - + - <> - - {I18n.t(isRead ? 'Unread' : 'Read')} - + @@ -59,64 +70,64 @@ export const LeftActions = React.memo(({ theme, transX, isRead, width, onToggleR ); }); -export const RightActions = React.memo(({ transX, favorite, width, toggleFav, onHidePress, theme }: IRightActions) => { - const translateXFav = Animated.multiply( - transX.interpolate({ - inputRange: [-width / 2, -ACTION_WIDTH * 2, 0], - outputRange: [width / 2, width - ACTION_WIDTH * 2, width] - }), - reverse - ); - const translateXHide = Animated.multiply( - transX.interpolate({ - inputRange: [-width, -LONG_SWIPE, -ACTION_WIDTH * 2, 0], - outputRange: [0, width - LONG_SWIPE, width - ACTION_WIDTH, width] - }), - reverse - ); - return ( - - - - <> - - - {I18n.t(favorite ? 'Unfavorite' : 'Favorite')} - - - - - - - <> - - {I18n.t('Hide')} - - - - - ); -}); +export const RightActions = React.memo( + ({ transX, favorite, width, toggleFav, onHidePress, theme, displayMode }: IRightActions) => { + const translateXFav = Animated.multiply( + transX.interpolate({ + inputRange: [-width / 2, -ACTION_WIDTH * 2, 0], + outputRange: [width / 2, width - ACTION_WIDTH * 2, width] + }), + reverse + ); + const translateXHide = Animated.multiply( + transX.interpolate({ + inputRange: [-width, -LONG_SWIPE, -ACTION_WIDTH * 2, 0], + outputRange: [0, width - LONG_SWIPE, width - ACTION_WIDTH, width] + }), + reverse + ); + + const isCondensed = displayMode === DISPLAY_MODE_CONDENSED; + const viewHeight = isCondensed ? { height: ROW_HEIGHT_CONDENSED } : null; + + return ( + + + + + + + + + + + + + ); + } +); diff --git a/app/presentation/RoomItem/IconOrAvatar.js b/app/presentation/RoomItem/IconOrAvatar.js new file mode 100644 index 000000000..cedd3b0ff --- /dev/null +++ b/app/presentation/RoomItem/IconOrAvatar.js @@ -0,0 +1,63 @@ +import React from 'react'; +import { View } from 'react-native'; +import PropTypes from 'prop-types'; + +import Avatar from '../../containers/Avatar'; +import { DISPLAY_MODE_CONDENSED, DISPLAY_MODE_EXPANDED } from '../../constants/constantDisplayMode'; +import TypeIcon from './TypeIcon'; +import styles from './styles'; + +const IconOrAvatar = ({ + avatar, + type, + rid, + showAvatar, + prid, + status, + isGroupChat, + teamMain, + showLastMessage, + theme, + displayMode +}) => { + if (showAvatar) { + return ( + + ); + } + + if (displayMode === DISPLAY_MODE_EXPANDED && showLastMessage) { + return ( + + + + ); + } + + return null; +}; + +IconOrAvatar.propTypes = { + avatar: PropTypes.string, + type: PropTypes.string, + theme: PropTypes.string, + rid: PropTypes.string, + showAvatar: PropTypes.bool, + displayMode: PropTypes.string, + prid: PropTypes.string, + status: PropTypes.string, + isGroupChat: PropTypes.bool, + teamMain: PropTypes.bool, + showLastMessage: PropTypes.bool +}; + +export default IconOrAvatar; diff --git a/app/presentation/RoomItem/LastMessage.tsx b/app/presentation/RoomItem/LastMessage.tsx index a6ae19513..1f0a0a486 100644 --- a/app/presentation/RoomItem/LastMessage.tsx +++ b/app/presentation/RoomItem/LastMessage.tsx @@ -80,6 +80,7 @@ const LastMessage = React.memo( numberOfLines={2} preview theme={theme} + testID='room-item-last-message' /> ), arePropsEqual diff --git a/app/presentation/RoomItem/RoomItem.tsx b/app/presentation/RoomItem/RoomItem.tsx index 36f8921fd..fcd529c81 100644 --- a/app/presentation/RoomItem/RoomItem.tsx +++ b/app/presentation/RoomItem/RoomItem.tsx @@ -11,6 +11,7 @@ import UpdatedAt from './UpdatedAt'; import Touchable from './Touchable'; import Tag from './Tag'; import I18n from '../../i18n'; +import { DISPLAY_MODE_EXPANDED } from '../../constants/constantDisplayMode'; interface IRoomItem { rid: string; @@ -57,6 +58,8 @@ interface IRoomItem { hideChannel(): void; autoJoin: boolean; size?: number; + showAvatar: boolean; + displayMode: string; } const RoomItem = ({ @@ -95,7 +98,9 @@ const RoomItem = ({ toggleRead, hideChannel, teamMain, - autoJoin + autoJoin, + showAvatar, + displayMode }: IRoomItem) => ( - - {showLastMessage ? ( + swipeEnabled={swipeEnabled} + displayMode={displayMode}> + + {showLastMessage && displayMode === DISPLAY_MODE_EXPANDED ? ( <> - + {showAvatar ? ( + + ) : null} {autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null} <UpdatedAt date={date} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} /> @@ -143,17 +164,29 @@ const RoomItem = ({ </> ) : ( <View style={[styles.titleContainer, styles.flex]}> - <TypeIcon type={type} prid={prid} status={status} isGroupChat={isGroupChat} theme={theme} teamMain={teamMain} /> + <TypeIcon + type={type} + prid={prid} + status={status} + isGroupChat={isGroupChat} + theme={theme} + teamMain={teamMain} + size={22} + style={{ marginRight: 8 }} + /> <Title name={name} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} /> {autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null} - <UnreadBadge - unread={unread} - userMentions={userMentions} - groupMentions={groupMentions} - tunread={tunread} - tunreadUser={tunreadUser} - tunreadGroup={tunreadGroup} - /> + <View style={styles.wrapUpdatedAndBadge}> + <UpdatedAt date={date} theme={theme} hideUnreadStatus={hideUnreadStatus} alert={alert} /> + <UnreadBadge + unread={unread} + userMentions={userMentions} + groupMentions={groupMentions} + tunread={tunread} + tunreadUser={tunreadUser} + tunreadGroup={tunreadGroup} + /> + </View> </View> )} </Wrapper> diff --git a/app/presentation/RoomItem/Touchable.tsx b/app/presentation/RoomItem/Touchable.tsx index 84071bbef..b40436aaa 100644 --- a/app/presentation/RoomItem/Touchable.tsx +++ b/app/presentation/RoomItem/Touchable.tsx @@ -24,6 +24,7 @@ interface ITouchableProps { theme: string; isFocused: boolean; swipeEnabled: boolean; + displayMode: string; } class Touchable extends React.Component<ITouchableProps, any> { @@ -227,7 +228,7 @@ class Touchable extends React.Component<ITouchableProps, any> { }; render() { - const { testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled } = this.props; + const { testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled, displayMode } = this.props; return ( <LongPressGestureHandler onHandlerStateChange={this.onLongPressHandlerStateChange}> @@ -244,6 +245,7 @@ class Touchable extends React.Component<ITouchableProps, any> { width={width} onToggleReadPress={this.onToggleReadPress} theme={theme} + displayMode={displayMode} /> <RightActions transX={this.transXReverse} @@ -252,6 +254,7 @@ class Touchable extends React.Component<ITouchableProps, any> { toggleFav={this.toggleFav} onHidePress={this.onHidePress} theme={theme} + displayMode={displayMode} /> <Animated.View style={{ diff --git a/app/presentation/RoomItem/TypeIcon.tsx b/app/presentation/RoomItem/TypeIcon.tsx index 414c16724..53659fbde 100644 --- a/app/presentation/RoomItem/TypeIcon.tsx +++ b/app/presentation/RoomItem/TypeIcon.tsx @@ -9,10 +9,19 @@ interface ITypeIcon { isGroupChat: boolean; teamMain: boolean; theme?: string; + size?: number; + style?: object; } -const TypeIcon = React.memo(({ type, prid, status, isGroupChat, teamMain }: ITypeIcon) => ( - <RoomTypeIcon type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} teamMain={teamMain} /> +const TypeIcon = React.memo(({ type, prid, status, isGroupChat, teamMain, size, style }: ITypeIcon) => ( + <RoomTypeIcon + type={prid ? 'discussion' : type} + isGroupChat={isGroupChat} + status={status} + teamMain={teamMain} + size={size} + style={style} + /> )); export default TypeIcon; diff --git a/app/presentation/RoomItem/Wrapper.tsx b/app/presentation/RoomItem/Wrapper.tsx index 903a5edca..cb4d6e1bd 100644 --- a/app/presentation/RoomItem/Wrapper.tsx +++ b/app/presentation/RoomItem/Wrapper.tsx @@ -1,9 +1,10 @@ import React from 'react'; import { View } from 'react-native'; -import styles from './styles'; import { themes } from '../../constants/colors'; -import Avatar from '../../containers/Avatar'; +import { DISPLAY_MODE_CONDENSED } from '../../constants/constantDisplayMode'; +import IconOrAvatar from './IconOrAvatar'; +import styles from './styles'; interface IWrapper { accessibilityLabel: string; @@ -13,17 +14,27 @@ interface IWrapper { theme: string; rid: string; children: JSX.Element; + displayMode: string; + prid: string; + showLastMessage: boolean; + status: string; + isGroupChat: boolean; + teamMain: boolean; + showAvatar: boolean; } -const Wrapper = ({ accessibilityLabel, avatar, avatarSize, type, theme, rid, children }: IWrapper) => ( - <View style={styles.container} accessibilityLabel={accessibilityLabel}> - <Avatar text={avatar} size={avatarSize} type={type} style={styles.avatar} rid={rid} /> +const Wrapper = ({ accessibilityLabel, theme, children, displayMode, ...props }: IWrapper) => ( + <View + style={[styles.container, displayMode === DISPLAY_MODE_CONDENSED && styles.containerCondensed]} + accessibilityLabel={accessibilityLabel}> + <IconOrAvatar theme={theme} displayMode={displayMode} {...props} /> <View style={[ styles.centerContainer, { borderColor: themes[theme].separatorColor - } + }, + displayMode === DISPLAY_MODE_CONDENSED && styles.condensedPaddingVertical ]}> {children} </View> diff --git a/app/presentation/RoomItem/index.tsx b/app/presentation/RoomItem/index.tsx index eedc1f4ff..7e88fe6c1 100644 --- a/app/presentation/RoomItem/index.tsx +++ b/app/presentation/RoomItem/index.tsx @@ -2,12 +2,11 @@ import React from 'react'; import { connect } from 'react-redux'; import I18n from '../../i18n'; -import { ROW_HEIGHT } from './styles'; +import { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from './styles'; import { formatDate } from '../../utils/room'; import RoomItem from './RoomItem'; -export { ROW_HEIGHT }; - +export { ROW_HEIGHT, ROW_HEIGHT_CONDENSED }; interface IRoomItemContainerProps { item: any; showLastMessage: boolean; @@ -32,9 +31,22 @@ interface IRoomItemContainerProps { getIsRead: Function; swipeEnabled: boolean; autoJoin: boolean; + showAvatar: boolean; + displayMode: string; } -const attrs = ['width', 'status', 'connected', 'theme', 'isFocused', 'forceUpdate', 'showLastMessage', 'autoJoin']; +const attrs = [ + 'width', + 'status', + 'connected', + 'theme', + 'isFocused', + 'forceUpdate', + 'showLastMessage', + 'autoJoin', + 'showAvatar', + 'displayMode' +]; class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> { private mounted: boolean; @@ -137,7 +149,9 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> { username, useRealName, swipeEnabled, - autoJoin + autoJoin, + showAvatar, + displayMode } = this.props; const name = getRoomTitle(item); const testID = `rooms-list-view-item-${name}`; @@ -200,6 +214,8 @@ class RoomItemContainer extends React.Component<IRoomItemContainerProps, any> { swipeEnabled={swipeEnabled} teamMain={item.teamMain} autoJoin={autoJoin} + showAvatar={showAvatar} + displayMode={displayMode} /> ); } diff --git a/app/presentation/RoomItem/styles.ts b/app/presentation/RoomItem/styles.ts index 953217a27..d3ce81413 100644 --- a/app/presentation/RoomItem/styles.ts +++ b/app/presentation/RoomItem/styles.ts @@ -3,6 +3,7 @@ import { PixelRatio, StyleSheet } from 'react-native'; import sharedStyles from '../../views/Styles'; export const ROW_HEIGHT = 75 * PixelRatio.getFontScale(); +export const ROW_HEIGHT_CONDENSED = 60 * PixelRatio.getFontScale(); export const ACTION_WIDTH = 80; export const SMALL_SWIPE = ACTION_WIDTH / 2; export const LONG_SWIPE = ACTION_WIDTH * 3; @@ -17,6 +18,12 @@ export default StyleSheet.create<any>({ paddingLeft: 14, height: ROW_HEIGHT }, + containerCondensed: { + height: ROW_HEIGHT_CONDENSED + }, + condensedPaddingVertical: { + paddingVertical: 20 + }, centerContainer: { flex: 1, paddingVertical: 10, @@ -37,6 +44,9 @@ export default StyleSheet.create<any>({ flexDirection: 'row', alignItems: 'flex-start' }, + wrapUpdatedAndBadge: { + alignItems: 'flex-end' + }, titleContainer: { width: '100%', flexDirection: 'row', @@ -72,11 +82,12 @@ export default StyleSheet.create<any>({ right: 0, height: ROW_HEIGHT }, - actionText: { - fontSize: 15, - justifyContent: 'center', - marginTop: 4, - ...sharedStyles.textSemibold + actionsLeftContainer: { + flexDirection: 'row', + position: 'absolute', + left: 0, + right: 0, + height: ROW_HEIGHT }, actionLeftButtonContainer: { position: 'absolute', @@ -107,5 +118,9 @@ export default StyleSheet.create<any>({ fontSize: 13, paddingHorizontal: 4, ...sharedStyles.textSemibold + }, + typeIcon: { + height: ROW_HEIGHT, + justifyContent: 'center' } }); diff --git a/app/reducers/rooms.js b/app/reducers/rooms.js index 6b6914fe9..660f5f1e7 100644 --- a/app/reducers/rooms.js +++ b/app/reducers/rooms.js @@ -8,7 +8,6 @@ const initialState = { searchText: '', showServerDropdown: false, closeServerDropdown: false, - showSortDropdown: false, showSearchHeader: false }; @@ -56,16 +55,6 @@ export default function login(state = initialState, action) { ...state, showServerDropdown: !state.showServerDropdown }; - case types.ROOMS.CLOSE_SORT_DROPDOWN: - return { - ...state, - closeSortDropdown: !state.closeSortDropdown - }; - case types.ROOMS.TOGGLE_SORT_DROPDOWN: - return { - ...state, - showSortDropdown: !state.showSortDropdown - }; case types.ROOMS.OPEN_SEARCH_HEADER: return { ...state, diff --git a/app/reducers/sortPreferences.js b/app/reducers/sortPreferences.js index 1e7c0b2dd..31b501852 100644 --- a/app/reducers/sortPreferences.js +++ b/app/reducers/sortPreferences.js @@ -1,10 +1,13 @@ import { SORT_PREFERENCES } from '../actions/actionsTypes'; +import { DISPLAY_MODE_EXPANDED } from '../constants/constantDisplayMode'; const initialState = { sortBy: 'activity', groupByType: false, showFavorites: false, - showUnread: false + showUnread: false, + showAvatar: true, + displayMode: DISPLAY_MODE_EXPANDED }; export default (state = initialState, action) => { diff --git a/app/stacks/InsideStack.js b/app/stacks/InsideStack.js index 95e6020db..b3de1b610 100644 --- a/app/stacks/InsideStack.js +++ b/app/stacks/InsideStack.js @@ -38,6 +38,9 @@ import ProfileView from '../views/ProfileView'; import UserPreferencesView from '../views/UserPreferencesView'; import UserNotificationPrefView from '../views/UserNotificationPreferencesView'; +// Display Preferences View +import DisplayPrefsView from '../views/DisplayPrefsView'; + // Settings Stack import SettingsView from '../views/SettingsView'; import SecurityPrivacyView from '../views/SecurityPrivacyView'; @@ -220,6 +223,18 @@ const AdminPanelStackNavigator = () => { ); }; +// DisplayPreferenceNavigator +const DisplayPrefStack = createStackNavigator(); +const DisplayPrefStackNavigator = () => { + const { theme } = React.useContext(ThemeContext); + + return ( + <DisplayPrefStack.Navigator screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...StackAnimation }}> + <DisplayPrefStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} /> + </DisplayPrefStack.Navigator> + ); +}; + // DrawerNavigator const Drawer = createDrawerNavigator(); const DrawerNavigator = () => { @@ -236,6 +251,7 @@ const DrawerNavigator = () => { <Drawer.Screen name='ProfileStackNavigator' component={ProfileStackNavigator} /> <Drawer.Screen name='SettingsStackNavigator' component={SettingsStackNavigator} /> <Drawer.Screen name='AdminPanelStackNavigator' component={AdminPanelStackNavigator} /> + <Drawer.Screen name='DisplayPrefStackNavigator' component={DisplayPrefStackNavigator} /> </Drawer.Navigator> ); }; diff --git a/app/stacks/MasterDetailStack/index.js b/app/stacks/MasterDetailStack/index.js index e1ba84957..71828a6a2 100644 --- a/app/stacks/MasterDetailStack/index.js +++ b/app/stacks/MasterDetailStack/index.js @@ -33,6 +33,7 @@ import TeamChannelsView from '../../views/TeamChannelsView'; import MarkdownTableView from '../../views/MarkdownTableView'; import ReadReceiptsView from '../../views/ReadReceiptView'; import ProfileView from '../../views/ProfileView'; +import DisplayPrefsView from '../../views/DisplayPrefsView'; import SettingsView from '../../views/SettingsView'; import LanguageView from '../../views/LanguageView'; import ThemeView from '../../views/ThemeView'; @@ -204,6 +205,7 @@ const ModalStackNavigator = React.memo(({ navigation }) => { component={ProfileView} options={props => ProfileView.navigationOptions({ ...props, isMasterDetail: true })} /> + <ModalStack.Screen name='DisplayPrefsView' component={DisplayPrefsView} /> <ModalStack.Screen name='AdminPanelView' component={AdminPanelView} diff --git a/app/utils/log/events.js b/app/utils/log/events.js index 4a3ec38df..4d2564b0c 100644 --- a/app/utils/log/events.js +++ b/app/utils/log/events.js @@ -66,15 +66,18 @@ export default { RL_TOGGLE_READ_F: 'rl_toggle_read_f', RL_HIDE_CHANNEL: 'rl_hide_channel', RL_HIDE_CHANNEL_F: 'rl_hide_channel_f', - RL_TOGGLE_SORT_DROPDOWN: 'rl_toggle_sort_dropdown', - RL_SORT_CHANNELS_BY_NAME: 'rl_sort_channels_by_name', - RL_SORT_CHANNELS_BY_ACTIVITY: 'rl_sort_channels_by_activity', - RL_SORT_CHANNELS_F: 'rl_sort_channels_f', - RL_GROUP_CHANNELS_BY_TYPE: 'rl_group_channels_by_type', - RL_GROUP_CHANNELS_BY_FAVORITE: 'rl_group_channels_by_favorite', - RL_GROUP_CHANNELS_BY_UNREAD: 'rl_group_channels_by_unread', RL_CREATE_NEW_WORKSPACE: 'rl_create_new_workspace', + // DISPLAY PREFERENCES VIEW + DP_SORT_CHANNELS_BY_NAME: 'dp_sort_channels_by_name', + DP_SORT_CHANNELS_BY_ACTIVITY: 'dp_sort_channels_by_activity', + DP_GROUP_CHANNELS_BY_TYPE: 'dp_group_channels_by_type', + DP_GROUP_CHANNELS_BY_FAVORITE: 'dp_group_channels_by_favorite', + DP_GROUP_CHANNELS_BY_UNREAD: 'dp_group_channels_by_unread', + DP_TOGGLE_AVATAR: 'dp_toggle_avatar', + DP_DISPLAY_EXPANDED: 'dp_display_expanded', + DP_DISPLAY_CONDENSED: 'dp_display_condensed', + // QUEUE LIST VIEW QL_GO_ROOM: 'ql_go_room', diff --git a/app/views/AuthLoadingView.js b/app/views/AuthLoadingView.tsx similarity index 81% rename from app/views/AuthLoadingView.js rename to app/views/AuthLoadingView.tsx index e97c10d94..ef6e7b943 100644 --- a/app/views/AuthLoadingView.js +++ b/app/views/AuthLoadingView.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'; -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import I18n from '../i18n'; @@ -23,25 +22,25 @@ const styles = StyleSheet.create({ } }); -const AuthLoadingView = React.memo(({ theme, text }) => ( +interface IAuthLoadingView { + theme: string; + text: string; +} + +const AuthLoadingView = React.memo(({ theme, text }: IAuthLoadingView) => ( <View style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}> <StatusBar /> - {text && ( + {text ? ( <> <ActivityIndicator color={themes[theme].auxiliaryText} size='large' /> <Text style={[styles.text, { color: themes[theme].bodyText }]}>{`${text}\n${I18n.t('Please_wait')}`}</Text> </> - )} + ) : null} </View> )); -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ text: state.app.text }); -AuthLoadingView.propTypes = { - theme: PropTypes.string, - text: PropTypes.string -}; - export default connect(mapStateToProps)(withTheme(AuthLoadingView)); diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js index af17961fb..0aa3c7035 100644 --- a/app/views/CreateChannelView.js +++ b/app/views/CreateChannelView.js @@ -21,6 +21,7 @@ import { Review } from '../utils/review'; import { getUserSelector } from '../selectors/login'; import { events, logEvent } from '../utils/log'; import SafeAreaView from '../containers/SafeAreaView'; +import RocketChat from '../lib/rocketchat'; import sharedStyles from './Styles'; const styles = StyleSheet.create({ @@ -79,10 +80,13 @@ class CreateChannelView extends React.Component { users: PropTypes.array.isRequired, user: PropTypes.shape({ id: PropTypes.string, - token: PropTypes.string + token: PropTypes.string, + roles: PropTypes.array }), theme: PropTypes.string, - teamId: PropTypes.string + teamId: PropTypes.string, + createPublicChannelPermission: PropTypes.array, + createPrivateChannelPermission: PropTypes.array }; constructor(props) { @@ -96,14 +100,20 @@ class CreateChannelView extends React.Component { readOnly: false, encrypted: false, broadcast: false, - isTeam + isTeam, + permissions: [] }; this.setHeader(); } + componentDidMount() { + this.handleHasPermission(); + } + shouldComponentUpdate(nextProps, nextState) { - const { channelName, type, readOnly, broadcast, encrypted } = this.state; - const { users, isFetching, encryptionEnabled, theme } = this.props; + const { channelName, type, readOnly, broadcast, encrypted, permissions } = this.state; + const { users, isFetching, encryptionEnabled, theme, createPublicChannelPermission, createPrivateChannelPermission } = + this.props; if (nextProps.theme !== theme) { return true; } @@ -122,18 +132,37 @@ class CreateChannelView extends React.Component { if (nextState.broadcast !== broadcast) { return true; } + if (nextState.permissions !== permissions) { + return true; + } if (nextProps.isFetching !== isFetching) { return true; } if (nextProps.encryptionEnabled !== encryptionEnabled) { return true; } + if (!dequal(nextProps.createPublicChannelPermission, createPublicChannelPermission)) { + return true; + } + if (!dequal(nextProps.createPrivateChannelPermission, createPrivateChannelPermission)) { + return true; + } if (!dequal(nextProps.users, users)) { return true; } return false; } + componentDidUpdate(prevProps) { + const { createPublicChannelPermission, createPrivateChannelPermission } = this.props; + if ( + !dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) || + !dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) + ) { + this.handleHasPermission(); + } + } + setHeader = () => { const { navigation } = this.props; const { isTeam } = this.state; @@ -208,12 +237,21 @@ class CreateChannelView extends React.Component { ); }; + handleHasPermission = async () => { + const { createPublicChannelPermission, createPrivateChannelPermission } = this.props; + const permissions = [createPublicChannelPermission, createPrivateChannelPermission]; + const permissionsToCreate = await RocketChat.hasPermission(permissions); + this.setState({ permissions: permissionsToCreate }); + }; + renderType() { - const { type, isTeam } = this.state; + const { type, isTeam, permissions } = this.state; + const isDisabled = permissions.filter(r => r === true).length <= 1; return this.renderSwitch({ id: 'type', - value: type, + value: permissions[1] ? type : false, + disabled: isDisabled, label: isTeam ? 'Private_Team' : 'Private_Channel', onValueChange: value => { logEvent(events.CR_TOGGLE_TYPE); @@ -373,7 +411,9 @@ const mapStateToProps = state => ({ isFetching: state.createChannel.isFetching, encryptionEnabled: state.encryption.enabled, users: state.selectedUsers.users, - user: getUserSelector(state) + user: getUserSelector(state), + createPublicChannelPermission: state.permissions['create-c'], + createPrivateChannelPermission: state.permissions['create-p'] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/DisplayPrefsView.js b/app/views/DisplayPrefsView.js new file mode 100644 index 000000000..d1e29d139 --- /dev/null +++ b/app/views/DisplayPrefsView.js @@ -0,0 +1,192 @@ +import React, { useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { Switch } from 'react-native'; +import { RadioButton } from 'react-native-ui-lib'; +import { useDispatch, useSelector } from 'react-redux'; + +import { setPreference } from '../actions/sortPreferences'; +import RocketChat from '../lib/rocketchat'; +import StatusBar from '../containers/StatusBar'; +import I18n from '../i18n'; +import * as List from '../containers/List'; +import { useTheme } from '../theme'; +import { themes } from '../constants/colors'; +import * as HeaderButton from '../containers/HeaderButton'; +import SafeAreaView from '../containers/SafeAreaView'; +import { ICON_SIZE } from '../containers/List/constants'; +import log, { events, logEvent } from '../utils/log'; +import { DISPLAY_MODE_CONDENSED, DISPLAY_MODE_EXPANDED } from '../constants/constantDisplayMode'; + +const DisplayPrefsView = props => { + const { theme } = useTheme(); + + const { sortBy, groupByType, showFavorites, showUnread, showAvatar, displayMode } = useSelector(state => state.sortPreferences); + const dispatch = useDispatch(); + + useEffect(() => { + const { navigation, isMasterDetail } = props; + navigation.setOptions({ + title: I18n.t('Display'), + headerLeft: () => + isMasterDetail ? ( + <HeaderButton.CloseModal navigation={navigation} testID='display-view-close' /> + ) : ( + <HeaderButton.Drawer navigation={navigation} testID='display-view-drawer' /> + ) + }); + }, []); + + const setSortPreference = async param => { + try { + dispatch(setPreference(param)); + await RocketChat.saveSortPreference(param); + } catch (e) { + log(e); + } + }; + + const sortByName = async () => { + logEvent(events.DP_SORT_CHANNELS_BY_NAME); + await setSortPreference({ sortBy: 'alphabetical' }); + }; + + const sortByActivity = async () => { + logEvent(events.DP_SORT_CHANNELS_BY_ACTIVITY); + await setSortPreference({ sortBy: 'activity' }); + }; + + const toggleGroupByType = async () => { + logEvent(events.DP_GROUP_CHANNELS_BY_TYPE); + await setSortPreference({ groupByType: !groupByType }); + }; + + const toggleGroupByFavorites = async () => { + logEvent(events.DP_GROUP_CHANNELS_BY_FAVORITE); + await setSortPreference({ showFavorites: !showFavorites }); + }; + + const toggleUnread = async () => { + logEvent(events.DP_GROUP_CHANNELS_BY_UNREAD); + await setSortPreference({ showUnread: !showUnread }); + }; + + const toggleAvatar = async () => { + logEvent(events.DP_TOGGLE_AVATAR); + await setSortPreference({ showAvatar: !showAvatar }); + }; + + const displayExpanded = async () => { + logEvent(events.DP_DISPLAY_EXPANDED); + await setSortPreference({ displayMode: DISPLAY_MODE_EXPANDED }); + }; + + const displayCondensed = async () => { + logEvent(events.DP_DISPLAY_CONDENSED); + await setSortPreference({ displayMode: DISPLAY_MODE_CONDENSED }); + }; + + const renderCheckBox = value => ( + <List.Icon name={value ? 'checkbox-checked' : 'checkbox-unchecked'} color={value ? themes[theme].actionTintColor : null} /> + ); + + const renderAvatarSwitch = value => ( + <Switch value={value} onValueChange={() => toggleAvatar()} testID='display-pref-view-avatar-switch' /> + ); + + const renderRadio = value => ( + <RadioButton + selected={!!value} + color={value ? themes[theme].actionTintColor : themes[theme].auxiliaryText} + size={ICON_SIZE} + /> + ); + + return ( + <SafeAreaView> + <StatusBar /> + <List.Container testID='display-view-list'> + <List.Section title='Display'> + <List.Separator /> + <List.Item + left={() => <List.Icon name='view-extended' />} + title='Expanded' + testID='display-pref-view-expanded' + right={() => renderRadio(displayMode === DISPLAY_MODE_EXPANDED)} + onPress={displayExpanded} + /> + <List.Separator /> + <List.Item + left={() => <List.Icon name='view-medium' />} + title='Condensed' + testID='display-pref-view-condensed' + right={() => renderRadio(displayMode === DISPLAY_MODE_CONDENSED)} + onPress={displayCondensed} + /> + <List.Separator /> + <List.Item + left={() => <List.Icon name='avatar' />} + title='Avatars' + testID='display-pref-view-avatars' + right={() => renderAvatarSwitch(showAvatar)} + /> + <List.Separator /> + </List.Section> + + <List.Section title='Sort_by'> + <List.Separator /> + <List.Item + title='Activity' + testID='display-pref-view-activity' + left={() => <List.Icon name='clock' />} + onPress={sortByActivity} + right={() => renderRadio(sortBy === 'activity')} + /> + <List.Separator /> + <List.Item + title='Name' + testID='display-pref-view-name' + left={() => <List.Icon name='sort-az' />} + onPress={sortByName} + right={() => renderRadio(sortBy === 'alphabetical')} + /> + <List.Separator /> + </List.Section> + + <List.Section title='Group_by'> + <List.Separator /> + <List.Item + title='Unread_on_top' + testID='display-pref-view-unread' + left={() => <List.Icon name='flag' />} + onPress={toggleUnread} + right={() => renderCheckBox(showUnread)} + /> + <List.Separator /> + <List.Item + title='Favorites' + testID='display-pref-view-favorites' + left={() => <List.Icon name='star' />} + onPress={toggleGroupByFavorites} + right={() => renderCheckBox(showFavorites)} + /> + <List.Separator /> + <List.Item + title='Types' + testID='display-pref-view-types' + left={() => <List.Icon name='group-by-type' />} + onPress={toggleGroupByType} + right={() => renderCheckBox(groupByType)} + /> + <List.Separator /> + </List.Section> + </List.Container> + </SafeAreaView> + ); +}; + +DisplayPrefsView.propTypes = { + navigation: PropTypes.object, + isMasterDetail: PropTypes.bool +}; + +export default DisplayPrefsView; diff --git a/app/views/LegalView.js b/app/views/LegalView.tsx similarity index 77% rename from app/views/LegalView.js rename to app/views/LegalView.tsx index b9db90572..02ac172d9 100644 --- a/app/views/LegalView.js +++ b/app/views/LegalView.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { StackNavigationOptions } from '@react-navigation/stack'; import I18n from '../i18n'; import StatusBar from '../containers/StatusBar'; @@ -9,13 +9,17 @@ import { withTheme } from '../theme'; import SafeAreaView from '../containers/SafeAreaView'; import * as List from '../containers/List'; -class LegalView extends React.Component { - static propTypes = { - server: PropTypes.string, - theme: PropTypes.string +interface ILegalView { + server: string; + theme: string; +} + +class LegalView extends React.Component<ILegalView, any> { + static navigationOptions: StackNavigationOptions = { + title: I18n.t('Legal') }; - onPressItem = ({ route }) => { + onPressItem = ({ route }: { route: string }) => { const { server, theme } = this.props; if (!server) { return; @@ -51,12 +55,8 @@ class LegalView extends React.Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ server: state.server.server }); -LegalView.navigationOptions = { - title: I18n.t('Legal') -}; - export default connect(mapStateToProps)(withTheme(LegalView)); diff --git a/app/views/NewMessageView.js b/app/views/NewMessageView.js index 4b210e7cb..020588ffa 100644 --- a/app/views/NewMessageView.js +++ b/app/views/NewMessageView.js @@ -3,8 +3,9 @@ import PropTypes from 'prop-types'; import { FlatList, StyleSheet, Text, View } from 'react-native'; import { connect } from 'react-redux'; import { Q } from '@nozbe/watermelondb'; - +import { dequal } from 'dequal'; import * as List from '../containers/List'; + import Touch from '../utils/touch'; import database from '../lib/database'; import RocketChat from '../lib/rocketchat'; @@ -57,13 +58,19 @@ class NewMessageView extends React.Component { baseUrl: PropTypes.string, user: PropTypes.shape({ id: PropTypes.string, - token: PropTypes.string + token: PropTypes.string, + roles: PropTypes.array }), create: PropTypes.func, maxUsers: PropTypes.number, theme: PropTypes.string, isMasterDetail: PropTypes.bool, - serverVersion: PropTypes.string + serverVersion: PropTypes.string, + createTeamPermission: PropTypes.array, + createDirectMessagePermission: PropTypes.array, + createPublicChannelPermission: PropTypes.array, + createPrivateChannelPermission: PropTypes.array, + createDiscussionPermission: PropTypes.array }; constructor(props) { @@ -71,7 +78,8 @@ class NewMessageView extends React.Component { this.init(); this.state = { search: [], - chats: [] + chats: [], + permissions: [] }; } @@ -90,6 +98,30 @@ class NewMessageView extends React.Component { } }; + componentDidMount() { + this.handleHasPermission(); + } + + componentDidUpdate(prevProps) { + const { + createTeamPermission, + createPublicChannelPermission, + createPrivateChannelPermission, + createDirectMessagePermission, + createDiscussionPermission + } = this.props; + + if ( + !dequal(createTeamPermission, prevProps.createTeamPermission) || + !dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) || + !dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) || + !dequal(createDirectMessagePermission, prevProps.createDirectMessagePermission) || + !dequal(createDiscussionPermission, prevProps.createDiscussionPermission) + ) { + this.handleHasPermission(); + } + } + onSearchChangeText(text) { this.search(text); } @@ -161,20 +193,43 @@ class NewMessageView extends React.Component { Navigation.navigate('CreateDiscussionView'); }; + handleHasPermission = async () => { + const { + createTeamPermission, + createDirectMessagePermission, + createPublicChannelPermission, + createPrivateChannelPermission, + createDiscussionPermission + } = this.props; + const permissions = [ + createPublicChannelPermission, + createPrivateChannelPermission, + createTeamPermission, + createDirectMessagePermission, + createDiscussionPermission + ]; + const permissionsToCreate = await RocketChat.hasPermission(permissions); + this.setState({ permissions: permissionsToCreate }); + }; + renderHeader = () => { const { maxUsers, theme, serverVersion } = this.props; + const { permissions } = this.state; + return ( <View style={{ backgroundColor: themes[theme].auxiliaryBackground }}> <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='new-message-view-search' /> <View style={styles.buttonContainer}> - {this.renderButton({ - onPress: this.createChannel, - title: I18n.t('Create_Channel'), - icon: 'channel-public', - testID: 'new-message-view-create-channel', - first: true - })} - {compareServerVersion(serverVersion, '3.13.0', methods.greaterThanOrEqualTo) + {permissions[0] || permissions[1] + ? this.renderButton({ + onPress: this.createChannel, + title: I18n.t('Create_Channel'), + icon: 'channel-public', + testID: 'new-message-view-create-channel', + first: true + }) + : null} + {compareServerVersion(serverVersion, '3.13.0', methods.greaterThanOrEqualTo) && permissions[2] ? this.renderButton({ onPress: this.createTeam, title: I18n.t('Create_Team'), @@ -182,7 +237,7 @@ class NewMessageView extends React.Component { testID: 'new-message-view-create-team' }) : null} - {maxUsers > 2 + {maxUsers > 2 && permissions[3] ? this.renderButton({ onPress: this.createGroupChat, title: I18n.t('Create_Direct_Messages'), @@ -190,12 +245,14 @@ class NewMessageView extends React.Component { testID: 'new-message-view-create-direct-message' }) : null} - {this.renderButton({ - onPress: this.createDiscussion, - title: I18n.t('Create_Discussion'), - icon: 'discussions', - testID: 'new-message-view-create-discussion' - })} + {permissions[4] + ? this.renderButton({ + onPress: this.createDiscussion, + title: I18n.t('Create_Discussion'), + icon: 'discussions', + testID: 'new-message-view-create-discussion' + }) + : null} </View> </View> ); @@ -261,7 +318,12 @@ const mapStateToProps = state => ({ isMasterDetail: state.app.isMasterDetail, baseUrl: state.server.server, maxUsers: state.settings.DirectMesssage_maxUsers || 1, - user: getUserSelector(state) + user: getUserSelector(state), + createTeamPermission: state.permissions['create-team'], + createDirectMessagePermission: state.permissions['create-d'], + createPublicChannelPermission: state.permissions['create-c'], + createPrivateChannelPermission: state.permissions['create-p'], + createDiscussionPermission: state.permissions['start-discussion'] }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/RoomView/RightButtons.js b/app/views/RoomView/RightButtons.js index 70e18bd68..9bbf717fd 100644 --- a/app/views/RoomView/RightButtons.js +++ b/app/views/RoomView/RightButtons.js @@ -20,7 +20,8 @@ class RightButtonsContainer extends Component { navigation: PropTypes.object, isMasterDetail: PropTypes.bool, toggleFollowThread: PropTypes.func, - joined: PropTypes.bool + joined: PropTypes.bool, + encrypted: PropTypes.bool }; constructor(props) { @@ -137,11 +138,14 @@ class RightButtonsContainer extends Component { goSearchView = () => { logEvent(events.ROOM_GO_SEARCH); - const { rid, t, navigation, isMasterDetail } = this.props; + const { rid, t, navigation, isMasterDetail, encrypted } = this.props; if (isMasterDetail) { - navigation.navigate('ModalStackNavigator', { screen: 'SearchMessagesView', params: { rid, showCloseModal: true } }); + navigation.navigate('ModalStackNavigator', { + screen: 'SearchMessagesView', + params: { rid, showCloseModal: true, encrypted } + }); } else { - navigation.navigate('SearchMessagesView', { rid, t }); + navigation.navigate('SearchMessagesView', { rid, t, encrypted }); } }; diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 3aa389467..a94af5f4e 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -362,6 +362,7 @@ class RoomView extends React.Component { const t = room?.t; const teamMain = room?.teamMain; const teamId = room?.teamId; + const encrypted = room?.encrypted; const { id: userId, token } = user; const avatar = room?.name; const visitor = room?.visitor; @@ -424,6 +425,7 @@ class RoomView extends React.Component { teamMain={teamMain} joined={joined} t={t} + encrypted={encrypted} navigation={navigation} toggleFollowThread={this.toggleFollowThread} /> diff --git a/app/views/RoomsListView/Header/index.js b/app/views/RoomsListView/Header/index.js index 4557b66dc..b97f65e15 100644 --- a/app/views/RoomsListView/Header/index.js +++ b/app/views/RoomsListView/Header/index.js @@ -2,12 +2,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { - closeServerDropdown, - closeSortDropdown, - setSearch as setSearchAction, - toggleServerDropdown -} from '../../../actions/rooms'; +import { toggleServerDropdown, closeServerDropdown, setSearch as setSearchAction } from '../../../actions/rooms'; import { withTheme } from '../../../theme'; import EventEmitter from '../../../utils/events'; import { KEY_COMMAND, handleCommandOpenServerDropdown } from '../../../commands'; @@ -18,7 +13,6 @@ import Header from './Header'; class RoomsListHeaderView extends PureComponent { static propTypes = { showServerDropdown: PropTypes.bool, - showSortDropdown: PropTypes.bool, showSearchHeader: PropTypes.bool, serverName: PropTypes.string, connecting: PropTypes.bool, @@ -28,7 +22,6 @@ class RoomsListHeaderView extends PureComponent { server: PropTypes.string, open: PropTypes.func, close: PropTypes.func, - closeSort: PropTypes.func, setSearch: PropTypes.func }; @@ -58,14 +51,9 @@ class RoomsListHeaderView extends PureComponent { onPress = () => { logEvent(events.RL_TOGGLE_SERVER_DROPDOWN); - const { showServerDropdown, showSortDropdown, close, open, closeSort } = this.props; + const { showServerDropdown, close, open } = this.props; if (showServerDropdown) { close(); - } else if (showSortDropdown) { - closeSort(); - setTimeout(() => { - open(); - }, 300); } else { open(); } @@ -93,7 +81,6 @@ class RoomsListHeaderView extends PureComponent { const mapStateToProps = state => ({ showServerDropdown: state.rooms.showServerDropdown, - showSortDropdown: state.rooms.showSortDropdown, showSearchHeader: state.rooms.showSearchHeader, connecting: state.meteor.connecting || state.server.loading, connected: state.meteor.connected, @@ -105,7 +92,6 @@ const mapStateToProps = state => ({ const mapDispatchtoProps = dispatch => ({ close: () => dispatch(closeServerDropdown()), open: () => dispatch(toggleServerDropdown()), - closeSort: () => dispatch(closeSortDropdown()), setSearch: searchText => dispatch(setSearchAction(searchText)) }); diff --git a/app/views/RoomsListView/ListHeader/index.js b/app/views/RoomsListView/ListHeader/index.js index d7d4d2304..b1aced177 100644 --- a/app/views/RoomsListView/ListHeader/index.js +++ b/app/views/RoomsListView/ListHeader/index.js @@ -2,16 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import { withTheme } from '../../../theme'; -import I18n from '../../../i18n'; import * as List from '../../../containers/List'; import { E2E_BANNER_TYPE } from '../../../lib/encryption/constants'; import { themes } from '../../../constants/colors'; import OmnichannelStatus from '../../../ee/omnichannel/containers/OmnichannelStatus'; const ListHeader = React.memo( - ({ searching, sortBy, toggleSort, goEncryption, goQueue, queueSize, inquiryEnabled, encryptionBanner, user, theme }) => { - const sortTitle = I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') }); - + ({ searching, goEncryption, goQueue, queueSize, inquiryEnabled, encryptionBanner, user, theme }) => { if (searching) { return null; } @@ -36,13 +33,6 @@ const ListHeader = React.memo( <List.Separator /> </> ) : null} - <List.Item - title={sortTitle} - left={() => <List.Icon name='sort' />} - color={themes[theme].auxiliaryText} - onPress={toggleSort} - translateTitle={false} - /> <List.Separator /> <OmnichannelStatus searching={searching} @@ -58,8 +48,6 @@ const ListHeader = React.memo( ListHeader.propTypes = { searching: PropTypes.bool, - sortBy: PropTypes.string, - toggleSort: PropTypes.func, goEncryption: PropTypes.func, goQueue: PropTypes.func, queueSize: PropTypes.number, diff --git a/app/views/RoomsListView/SortDropdown/index.js b/app/views/RoomsListView/SortDropdown/index.js deleted file mode 100644 index 6a945ff67..000000000 --- a/app/views/RoomsListView/SortDropdown/index.js +++ /dev/null @@ -1,207 +0,0 @@ -import React, { PureComponent } from 'react'; -import { Animated, Easing, TouchableWithoutFeedback } from 'react-native'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { withSafeAreaInsets } from 'react-native-safe-area-context'; - -import styles from '../styles'; -import * as List from '../../../containers/List'; -import RocketChat from '../../../lib/rocketchat'; -import { setPreference } from '../../../actions/sortPreferences'; -import log, { events, logEvent } from '../../../utils/log'; -import I18n from '../../../i18n'; -import { withTheme } from '../../../theme'; -import { themes } from '../../../constants/colors'; -import { headerHeight } from '../../../containers/Header'; - -const ANIMATION_DURATION = 200; - -class Sort extends PureComponent { - static propTypes = { - closeSortDropdown: PropTypes.bool, - close: PropTypes.func, - sortBy: PropTypes.string, - groupByType: PropTypes.bool, - showFavorites: PropTypes.bool, - showUnread: PropTypes.bool, - isMasterDetail: PropTypes.bool, - theme: PropTypes.string, - insets: PropTypes.object, - setSortPreference: PropTypes.func - }; - - constructor(props) { - super(props); - this.animatedValue = new Animated.Value(0); - } - - componentDidMount() { - Animated.timing(this.animatedValue, { - toValue: 1, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(); - } - - componentDidUpdate(prevProps) { - const { closeSortDropdown } = this.props; - if (prevProps.closeSortDropdown !== closeSortDropdown) { - this.close(); - } - } - - setSortPreference = param => { - const { setSortPreference } = this.props; - - try { - setSortPreference(param); - RocketChat.saveSortPreference(param); - } catch (e) { - logEvent(events.RL_SORT_CHANNELS_F); - log(e); - } - }; - - sortByName = () => { - logEvent(events.RL_SORT_CHANNELS_BY_NAME); - this.setSortPreference({ sortBy: 'alphabetical' }); - this.close(); - }; - - sortByActivity = () => { - logEvent(events.RL_SORT_CHANNELS_BY_ACTIVITY); - this.setSortPreference({ sortBy: 'activity' }); - this.close(); - }; - - toggleGroupByType = () => { - logEvent(events.RL_GROUP_CHANNELS_BY_TYPE); - const { groupByType } = this.props; - this.setSortPreference({ groupByType: !groupByType }); - }; - - toggleGroupByFavorites = () => { - logEvent(events.RL_GROUP_CHANNELS_BY_FAVORITE); - const { showFavorites } = this.props; - this.setSortPreference({ showFavorites: !showFavorites }); - }; - - toggleUnread = () => { - logEvent(events.RL_GROUP_CHANNELS_BY_UNREAD); - const { showUnread } = this.props; - this.setSortPreference({ showUnread: !showUnread }); - }; - - close = () => { - const { close } = this.props; - Animated.timing(this.animatedValue, { - toValue: 0, - duration: ANIMATION_DURATION, - easing: Easing.inOut(Easing.quad), - useNativeDriver: true - }).start(() => close()); - }; - - renderCheck = () => { - const { theme } = this.props; - return <List.Icon name='check' color={themes[theme].tintColor} />; - }; - - render() { - const { isMasterDetail, insets } = this.props; - const statusBarHeight = insets?.top ?? 0; - const heightDestination = isMasterDetail ? headerHeight + statusBarHeight : 0; - const translateY = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [-326, heightDestination] - }); - const { sortBy, groupByType, showFavorites, showUnread, theme } = this.props; - const backdropOpacity = this.animatedValue.interpolate({ - inputRange: [0, 1], - outputRange: [0, themes[theme].backdropOpacity] - }); - - return ( - <> - <TouchableWithoutFeedback onPress={this.close}> - <Animated.View - style={[ - styles.backdrop, - { - backgroundColor: themes[theme].backdropColor, - opacity: backdropOpacity, - top: heightDestination - } - ]} - /> - </TouchableWithoutFeedback> - <Animated.View - style={[ - styles.dropdownContainer, - { - transform: [{ translateY }], - backgroundColor: themes[theme].backgroundColor, - borderColor: themes[theme].separatorColor - } - ]}> - <List.Item - title={I18n.t('Sorting_by', { key: I18n.t(sortBy === 'alphabetical' ? 'name' : 'activity') })} - left={() => <List.Icon name='sort' />} - color={themes[theme].auxiliaryText} - onPress={this.close} - translateTitle={false} - /> - <List.Separator /> - <List.Item - title='Alphabetical' - left={() => <List.Icon name='sort-az' />} - color={themes[theme].auxiliaryText} - onPress={this.sortByName} - right={() => (sortBy === 'alphabetical' ? this.renderCheck() : null)} - /> - <List.Item - title='Activity' - left={() => <List.Icon name='clock' />} - color={themes[theme].auxiliaryText} - onPress={this.sortByActivity} - right={() => (sortBy === 'activity' ? this.renderCheck() : null)} - /> - <List.Separator /> - <List.Item - title='Group_by_type' - left={() => <List.Icon name='group-by-type' />} - color={themes[theme].auxiliaryText} - onPress={this.toggleGroupByType} - right={() => (groupByType ? this.renderCheck() : null)} - /> - <List.Item - title='Group_by_favorites' - left={() => <List.Icon name='star' />} - color={themes[theme].auxiliaryText} - onPress={this.toggleGroupByFavorites} - right={() => (showFavorites ? this.renderCheck() : null)} - /> - <List.Item - title='Unread_on_top' - left={() => <List.Icon name='unread-on-top-disabled' />} - color={themes[theme].auxiliaryText} - onPress={this.toggleUnread} - right={() => (showUnread ? this.renderCheck() : null)} - /> - </Animated.View> - </> - ); - } -} - -const mapStateToProps = state => ({ - closeSortDropdown: state.rooms.closeSortDropdown, - isMasterDetail: state.app.isMasterDetail -}); - -const mapDispatchToProps = dispatch => ({ - setSortPreference: preference => dispatch(setPreference(preference)) -}); - -export default connect(mapStateToProps, mapDispatchToProps)(withSafeAreaInsets(withTheme(Sort))); diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index 5f4d12553..41da6dd80 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -9,15 +9,14 @@ import { withSafeAreaInsets } from 'react-native-safe-area-context'; import database from '../../lib/database'; import RocketChat from '../../lib/rocketchat'; -import RoomItem, { ROW_HEIGHT } from '../../presentation/RoomItem'; -import log, { events, logEvent } from '../../utils/log'; +import RoomItem, { ROW_HEIGHT, ROW_HEIGHT_CONDENSED } from '../../presentation/RoomItem'; +import log, { logEvent, events } from '../../utils/log'; import I18n from '../../i18n'; import { closeSearchHeader as closeSearchHeaderAction, closeServerDropdown as closeServerDropdownAction, openSearchHeader as openSearchHeaderAction, - roomsRequest as roomsRequestAction, - toggleSortDropdown as toggleSortDropdownAction + roomsRequest as roomsRequestAction } from '../../actions/rooms'; import { appStart as appStartAction, ROOT_OUTSIDE } from '../../actions/app'; import debounce from '../../utils/debounce'; @@ -50,11 +49,11 @@ import { showConfirmationAlert, showErrorAlert } from '../../utils/info'; import { E2E_BANNER_TYPE } from '../../lib/encryption/constants'; import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry'; import { changeLivechatStatus, isOmnichannelStatusAvailable } from '../../ee/omnichannel/lib'; +import { DISPLAY_MODE_CONDENSED } from '../../constants/constantDisplayMode'; +import styles from './styles'; +import ServerDropdown from './ServerDropdown'; import ListHeader from './ListHeader'; import RoomsListHeaderView from './Header'; -import ServerDropdown from './ServerDropdown'; -import SortDropdown from './SortDropdown'; -import styles from './styles'; const INITIAL_NUM_TO_RENDER = isTablet ? 20 : 12; const CHATS_HEADER = 'Chats'; @@ -77,11 +76,6 @@ const shouldUpdateProps = [ 'searchText', 'loadingServer', 'showServerDropdown', - 'showSortDropdown', - 'sortBy', - 'groupByType', - 'showFavorites', - 'showUnread', 'useRealName', 'StoreLastMessage', 'theme', @@ -89,11 +83,21 @@ const shouldUpdateProps = [ 'refreshing', 'queueSize', 'inquiryEnabled', - 'encryptionBanner' + 'encryptionBanner', + 'createTeamPermission', + 'createDirectMessagePermission', + 'createPublicChannelPermission', + 'createPrivateChannelPermission', + 'createDiscussionPermission' ]; -const getItemLayout = (data, index) => ({ - length: ROW_HEIGHT, - offset: ROW_HEIGHT * index, + +const sortPreferencesShouldUpdate = ['sortBy', 'groupByType', 'showFavorites', 'showUnread']; + +const displayPropsShouldUpdate = ['showAvatar', 'displayMode']; + +const getItemLayout = (data, index, height) => ({ + length: height, + offset: height * index, index }); const keyExtractor = item => item.rid; @@ -106,14 +110,13 @@ class RoomsListView extends React.Component { username: PropTypes.string, token: PropTypes.string, statusLivechat: PropTypes.string, - roles: PropTypes.object + roles: PropTypes.array }), server: PropTypes.string, searchText: PropTypes.string, changingServer: PropTypes.bool, loadingServer: PropTypes.bool, showServerDropdown: PropTypes.bool, - showSortDropdown: PropTypes.bool, sortBy: PropTypes.string, groupByType: PropTypes.bool, showFavorites: PropTypes.bool, @@ -121,7 +124,6 @@ class RoomsListView extends React.Component { refreshing: PropTypes.bool, StoreLastMessage: PropTypes.bool, theme: PropTypes.string, - toggleSortDropdown: PropTypes.func, openSearchHeader: PropTypes.func, closeSearchHeader: PropTypes.func, appStart: PropTypes.func, @@ -135,6 +137,13 @@ class RoomsListView extends React.Component { queueSize: PropTypes.number, inquiryEnabled: PropTypes.bool, encryptionBanner: PropTypes.string, + showAvatar: PropTypes.bool, + displayMode: PropTypes.string, + createTeamPermission: PropTypes.array, + createDirectMessagePermission: PropTypes.array, + createPublicChannelPermission: PropTypes.array, + createPrivateChannelPermission: PropTypes.array, + createDiscussionPermission: PropTypes.array, initAdd: PropTypes.func }; @@ -152,7 +161,8 @@ class RoomsListView extends React.Component { loading: true, chatsUpdate: [], chats: [], - item: {} + item: {}, + canCreateRoom: false }; this.setHeader(); this.getSubscriptions(); @@ -160,6 +170,7 @@ class RoomsListView extends React.Component { componentDidMount() { const { navigation, closeServerDropdown } = this.props; + this.handleHasPermission(); this.mounted = true; if (isTablet) { @@ -168,6 +179,11 @@ class RoomsListView extends React.Component { this.unsubscribeFocus = navigation.addListener('focus', () => { Orientation.unlockAllOrientations(); this.animated = true; + // Check if there were changes with sort preference, then call getSubscription to remount the list + if (this.sortPreferencesChanged) { + this.getSubscriptions(); + this.sortPreferencesChanged = false; + } // Check if there were changes while not focused (it's set on sCU) if (this.shouldUpdate) { this.forceUpdate(); @@ -203,13 +219,27 @@ class RoomsListView extends React.Component { } shouldComponentUpdate(nextProps, nextState) { - const { chatsUpdate, searching, item } = this.state; + const { chatsUpdate, searching, item, canCreateRoom } = this.state; // eslint-disable-next-line react/destructuring-assignment const propsUpdated = shouldUpdateProps.some(key => nextProps[key] !== this.props[key]); if (propsUpdated) { return true; } + // check if some display props are changed to force update when focus this view again + // eslint-disable-next-line react/destructuring-assignment + const displayUpdated = displayPropsShouldUpdate.some(key => nextProps[key] !== this.props[key]); + if (displayUpdated) { + this.shouldUpdate = true; + } + + // check if some sort preferences are changed to getSubscription() when focus this view again + // eslint-disable-next-line react/destructuring-assignment + const sortPreferencesUpdate = sortPreferencesShouldUpdate.some(key => nextProps[key] !== this.props[key]); + if (sortPreferencesUpdate) { + this.sortPreferencesChanged = true; + } + // Compare changes only once const chatsNotEqual = !dequal(nextState.chatsUpdate, chatsUpdate); @@ -222,6 +252,10 @@ class RoomsListView extends React.Component { return true; } + if (nextState.canCreateRoom !== canCreateRoom) { + return true; + } + if (nextState.item?.rid !== item?.rid) { return true; } @@ -257,7 +291,22 @@ class RoomsListView extends React.Component { } componentDidUpdate(prevProps) { - const { sortBy, groupByType, showFavorites, showUnread, rooms, isMasterDetail, insets } = this.props; + const { + sortBy, + groupByType, + showFavorites, + showUnread, + rooms, + isMasterDetail, + insets, + createTeamPermission, + createPublicChannelPermission, + createPrivateChannelPermission, + createDirectMessagePermission, + createDiscussionPermission, + showAvatar, + displayMode + } = this.props; const { item } = this.state; if ( @@ -265,7 +314,9 @@ class RoomsListView extends React.Component { prevProps.sortBy === sortBy && prevProps.groupByType === groupByType && prevProps.showFavorites === showFavorites && - prevProps.showUnread === showUnread + prevProps.showUnread === showUnread && + prevProps.showAvatar === showAvatar && + prevProps.displayMode === displayMode ) ) { this.getSubscriptions(); @@ -278,6 +329,17 @@ class RoomsListView extends React.Component { if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) { this.setHeader(); } + + if ( + !dequal(createTeamPermission, prevProps.createTeamPermission) || + !dequal(createPublicChannelPermission, prevProps.createPublicChannelPermission) || + !dequal(createPrivateChannelPermission, prevProps.createPrivateChannelPermission) || + !dequal(createDirectMessagePermission, prevProps.createDirectMessagePermission) || + !dequal(createDiscussionPermission, prevProps.createDiscussionPermission) + ) { + this.handleHasPermission(); + this.setHeader(); + } } componentWillUnmount() { @@ -297,10 +359,31 @@ class RoomsListView extends React.Component { console.countReset(`${this.constructor.name}.render calls`); } + handleHasPermission = async () => { + const { + createTeamPermission, + createDirectMessagePermission, + createPublicChannelPermission, + createPrivateChannelPermission, + createDiscussionPermission + } = this.props; + const permissions = [ + createPublicChannelPermission, + createPrivateChannelPermission, + createTeamPermission, + createDirectMessagePermission, + createDiscussionPermission + ]; + const permissionsToCreate = await RocketChat.hasPermission(permissions); + const canCreateRoom = permissionsToCreate.filter(r => r === true).length > 0; + this.setState({ canCreateRoom }, () => this.setHeader()); + }; + getHeader = () => { - const { searching } = this.state; + const { searching, canCreateRoom } = this.state; const { navigation, isMasterDetail, insets } = this.props; const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight: searching ? 0 : 3 }); + return { headerTitleAlign: 'left', headerLeft: () => @@ -327,7 +410,9 @@ class RoomsListView extends React.Component { headerRight: () => searching ? null : ( <HeaderButton.Container> - <HeaderButton.Item iconName='create' onPress={this.goToNewMessage} testID='rooms-list-view-create-channel' /> + {canCreateRoom ? ( + <HeaderButton.Item iconName='create' onPress={this.goToNewMessage} testID='rooms-list-view-create-channel' /> + ) : null} <HeaderButton.Item iconName='search' onPress={this.initSearching} testID='rooms-list-view-search' /> <HeaderButton.Item iconName='directory' onPress={this.goDirectory} testID='rooms-list-view-directory' /> </HeaderButton.Container> @@ -553,16 +638,6 @@ class RoomsListView extends React.Component { } }; - toggleSort = () => { - logEvent(events.RL_TOGGLE_SORT_DROPDOWN); - const { toggleSortDropdown } = this.props; - - this.scrollToTop(); - setTimeout(() => { - toggleSortDropdown(); - }, 100); - }; - toggleFav = async (rid, favorite) => { logEvent(favorite ? events.RL_UNFAVORITE_CHANNEL : events.RL_FAVORITE_CHANNEL); try { @@ -811,12 +886,10 @@ class RoomsListView extends React.Component { renderListHeader = () => { const { searching } = this.state; - const { sortBy, queueSize, inquiryEnabled, encryptionBanner, user } = this.props; + const { queueSize, inquiryEnabled, encryptionBanner, user } = this.props; return ( <ListHeader searching={searching} - sortBy={sortBy} - toggleSort={this.toggleSort} goEncryption={this.goEncryption} goQueue={this.goQueue} queueSize={queueSize} @@ -850,7 +923,9 @@ class RoomsListView extends React.Component { useRealName, theme, isMasterDetail, - width + width, + showAvatar, + displayMode } = this.props; const id = this.getUidDirectMessage(item); @@ -875,6 +950,8 @@ class RoomsListView extends React.Component { getIsRead={this.isRead} visitor={item.visitor} isFocused={currentItem?.rid === item.rid} + showAvatar={showAvatar} + displayMode={displayMode} /> ); }; @@ -890,7 +967,9 @@ class RoomsListView extends React.Component { renderScroll = () => { const { loading, chats, search, searching } = this.state; - const { theme, refreshing } = this.props; + const { theme, refreshing, displayMode } = this.props; + + const height = displayMode === DISPLAY_MODE_CONDENSED ? ROW_HEIGHT_CONDENSED : ROW_HEIGHT; if (loading) { return <ActivityIndicator theme={theme} />; @@ -905,7 +984,7 @@ class RoomsListView extends React.Component { style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]} renderItem={this.renderItem} ListHeaderComponent={this.renderListHeader} - getItemLayout={getItemLayout} + getItemLayout={(data, index) => getItemLayout(data, index, height)} removeClippedSubviews={isIOS} keyboardShouldPersistTaps='always' initialNumToRender={INITIAL_NUM_TO_RENDER} @@ -921,23 +1000,13 @@ class RoomsListView extends React.Component { render = () => { console.count(`${this.constructor.name}.render calls`); - const { sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown, theme, navigation } = - this.props; + const { showServerDropdown, theme, navigation } = this.props; return ( <SafeAreaView testID='rooms-list-view' style={{ backgroundColor: themes[theme].backgroundColor }}> <StatusBar /> {this.renderHeader()} {this.renderScroll()} - {showSortDropdown ? ( - <SortDropdown - close={this.toggleSort} - sortBy={sortBy} - groupByType={groupByType} - showFavorites={showFavorites} - showUnread={showUnread} - /> - ) : null} {showServerDropdown ? <ServerDropdown navigation={navigation} /> : null} </SafeAreaView> ); @@ -952,7 +1021,6 @@ const mapStateToProps = state => ({ searchText: state.rooms.searchText, loadingServer: state.server.loading, showServerDropdown: state.rooms.showServerDropdown, - showSortDropdown: state.rooms.showSortDropdown, refreshing: state.rooms.refreshing, sortBy: state.sortPreferences.sortBy, groupByType: state.sortPreferences.groupByType, @@ -963,11 +1031,17 @@ const mapStateToProps = state => ({ rooms: state.room.rooms, queueSize: getInquiryQueueSelector(state).length, inquiryEnabled: state.inquiry.enabled, - encryptionBanner: state.encryption.banner + encryptionBanner: state.encryption.banner, + showAvatar: state.sortPreferences.showAvatar, + displayMode: state.sortPreferences.displayMode, + createTeamPermission: state.permissions['create-team'], + createDirectMessagePermission: state.permissions['create-d'], + createPublicChannelPermission: state.permissions['create-c'], + createPrivateChannelPermission: state.permissions['create-p'], + createDiscussionPermission: state.permissions['start-discussion'] }); const mapDispatchToProps = dispatch => ({ - toggleSortDropdown: () => dispatch(toggleSortDropdownAction()), openSearchHeader: () => dispatch(openSearchHeaderAction()), closeSearchHeader: () => dispatch(closeSearchHeaderAction()), roomsRequest: params => dispatch(roomsRequestAction(params)), diff --git a/app/views/SearchMessagesView/index.js b/app/views/SearchMessagesView/index.js index f8131ec1d..c36425edd 100644 --- a/app/views/SearchMessagesView/index.js +++ b/app/views/SearchMessagesView/index.js @@ -24,8 +24,11 @@ import database from '../../lib/database'; import { sanitizeLikeString } from '../../lib/database/utils'; import getThreadName from '../../lib/methods/getThreadName'; import getRoomInfo from '../../lib/methods/getRoomInfo'; +import { isIOS } from '../../utils/deviceInfo'; +import { compareServerVersion, methods } from '../../lib/utils'; import styles from './styles'; +const QUERY_SIZE = 50; class SearchMessagesView extends React.Component { static navigationOptions = ({ navigation, route }) => { const options = { @@ -43,6 +46,7 @@ class SearchMessagesView extends React.Component { route: PropTypes.object, user: PropTypes.object, baseUrl: PropTypes.string, + serverVersion: PropTypes.string, customEmojis: PropTypes.object, theme: PropTypes.string, useRealName: PropTypes.bool @@ -55,6 +59,7 @@ class SearchMessagesView extends React.Component { messages: [], searchText: '' }; + this.offset = 0; this.rid = props.route.params?.rid; this.t = props.route.params?.t; this.encrypted = props.route.params?.encrypted; @@ -88,6 +93,9 @@ class SearchMessagesView extends React.Component { // Handle encrypted rooms search messages searchMessages = async searchText => { + if (!searchText) { + return []; + } // If it's a encrypted, room we'll search only on the local stored messages if (this.encrypted) { const db = database.active; @@ -103,25 +111,33 @@ class SearchMessagesView extends React.Component { .fetch(); } // If it's not a encrypted room, search messages on the server - const result = await RocketChat.searchMessages(this.rid, searchText); + const result = await RocketChat.searchMessages(this.rid, searchText, QUERY_SIZE, this.offset); if (result.success) { return result.messages; } }; - search = debounce(async searchText => { - this.setState({ searchText, loading: true, messages: [] }); - + getMessages = async (searchText, debounced) => { try { const messages = await this.searchMessages(searchText); - this.setState({ - messages: messages || [], + this.setState(prevState => ({ + messages: debounced ? messages : [...prevState.messages, ...messages], loading: false - }); + })); } catch (e) { this.setState({ loading: false }); log(e); } + }; + + search = searchText => { + this.offset = 0; + this.setState({ searchText, loading: true, messages: [] }); + this.searchDebounced(searchText); + }; + + searchDebounced = debounce(async searchText => { + await this.getMessages(searchText, true); }, 1000); getCustomEmoji = name => { @@ -168,6 +184,23 @@ class SearchMessagesView extends React.Component { } }; + onEndReached = async () => { + const { serverVersion } = this.props; + const { searchText, messages, loading } = this.state; + if ( + messages.length < this.offset || + this.encrypted || + loading || + compareServerVersion(serverVersion, '3.17.0', methods.lowerThan) + ) { + return; + } + this.setState({ loading: true }); + this.offset += QUERY_SIZE; + + await this.getMessages(searchText); + }; + renderEmpty = () => { const { theme } = this.props; return ( @@ -212,8 +245,10 @@ class SearchMessagesView extends React.Component { renderItem={this.renderItem} style={[styles.list, { backgroundColor: themes[theme].backgroundColor }]} keyExtractor={item => item._id} - onEndReached={this.load} + onEndReached={this.onEndReached} ListFooterComponent={loading ? <ActivityIndicator theme={theme} /> : null} + onEndReachedThreshold={0.5} + removeClippedSubviews={isIOS} {...scrollPersistTaps} /> ); @@ -243,6 +278,7 @@ class SearchMessagesView extends React.Component { } const mapStateToProps = state => ({ + serverVersion: state.server.version, baseUrl: state.server.server, user: getUserSelector(state), useRealName: state.settings.UI_Use_Real_Name, diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js index 4c62e19bc..897d86a6e 100644 --- a/app/views/SettingsView/index.js +++ b/app/views/SettingsView/index.js @@ -185,6 +185,8 @@ class SettingsView extends React.Component { <List.Separator /> </List.Section> <List.Section> + <List.Separator /> + <List.Item title='Display' onPress={() => this.navigateToScreen('DisplayPrefsView')} showActionIndicator /> <List.Separator /> <List.Item title='Profile' diff --git a/app/views/SidebarView/index.js b/app/views/SidebarView/index.js index 975d8a896..5f27e7062 100644 --- a/app/views/SidebarView/index.js +++ b/app/views/SidebarView/index.js @@ -193,6 +193,13 @@ class Sidebar extends Component { testID='sidebar-profile' current={this.currentItemKey === 'ProfileStackNavigator'} /> + <SidebarItem + text={I18n.t('Display')} + left={<CustomIcon name='sort' size={20} color={themes[theme].titleText} />} + onPress={() => this.sidebarNavigate('DisplayPrefStackNavigator')} + testID='sidebar-display' + current={this.currentItemKey === 'DisplayPrefStackNavigator'} + /> <SidebarItem text={I18n.t('Settings')} left={<CustomIcon name='administration' size={20} color={themes[theme].titleText} />} diff --git a/app/views/TeamChannelsView.js b/app/views/TeamChannelsView.js index 869d5e4c3..13546703e 100644 --- a/app/views/TeamChannelsView.js +++ b/app/views/TeamChannelsView.js @@ -61,7 +61,9 @@ class TeamChannelsView extends React.Component { deleteCPermission: PropTypes.array, deletePPermission: PropTypes.array, showActionSheet: PropTypes.func, - deleteRoom: PropTypes.func + deleteRoom: PropTypes.func, + showAvatar: PropTypes.bool, + displayMode: PropTypes.string }; constructor(props) { @@ -463,7 +465,7 @@ class TeamChannelsView extends React.Component { }; renderItem = ({ item }) => { - const { StoreLastMessage, useRealName, theme, width } = this.props; + const { StoreLastMessage, useRealName, theme, width, showAvatar, displayMode } = this.props; return ( <RoomItem item={item} @@ -478,6 +480,8 @@ class TeamChannelsView extends React.Component { getRoomAvatar={this.getRoomAvatar} swipeEnabled={false} autoJoin={item.teamDefault} + showAvatar={showAvatar} + displayMode={displayMode} /> ); }; @@ -540,7 +544,9 @@ const mapStateToProps = state => ({ editTeamChannelPermission: state.permissions[PERMISSION_EDIT_TEAM_CHANNEL], removeTeamChannelPermission: state.permissions[PERMISSION_REMOVE_TEAM_CHANNEL], deleteCPermission: state.permissions[PERMISSION_DELETE_C], - deletePPermission: state.permissions[PERMISSION_DELETE_P] + deletePPermission: state.permissions[PERMISSION_DELETE_P], + showAvatar: state.sortPreferences.showAvatar, + displayMode: state.sortPreferences.displayMode }); const mapDispatchToProps = dispatch => ({ diff --git a/app/views/WorkspaceView/ServerAvatar.js b/app/views/WorkspaceView/ServerAvatar.tsx similarity index 80% rename from app/views/WorkspaceView/ServerAvatar.js rename to app/views/WorkspaceView/ServerAvatar.tsx index 749c4b10e..54b90b9bb 100644 --- a/app/views/WorkspaceView/ServerAvatar.js +++ b/app/views/WorkspaceView/ServerAvatar.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; -import PropTypes from 'prop-types'; import { createImageProgress } from 'react-native-image-progress'; import * as Progress from 'react-native-progress'; import FastImage from '@rocket.chat/react-native-fast-image'; @@ -41,15 +40,24 @@ const styles = StyleSheet.create({ } }); -const getInitial = url => url && url.replace(/http(s?):\/\//, '').slice(0, 1); +const getInitial = (url: string) => url && url.replace(/http(s?):\/\//, '').slice(0, 1); -const Fallback = ({ theme, initial }) => ( +interface IFallback { + theme: string; + initial: string; +} +const Fallback = ({ theme, initial }: IFallback) => ( <View style={[styles.container, styles.fallback, { backgroundColor: themes[theme].dangerColor }]}> <Text style={[styles.initial, { color: themes[theme].buttonText }]}>{initial}</Text> </View> ); -const ServerAvatar = React.memo(({ theme, url, image }) => ( +interface IServerAvatar { + theme: string; + url: string; + image: string; +} +const ServerAvatar = React.memo(({ theme, url, image }: IServerAvatar) => ( <View style={styles.container}> {image && ( <ImageProgress @@ -66,16 +74,6 @@ const ServerAvatar = React.memo(({ theme, url, image }) => ( </View> )); -ServerAvatar.propTypes = { - theme: PropTypes.string, - url: PropTypes.string, - image: PropTypes.string -}; ServerAvatar.displayName = 'ServerAvatar'; -Fallback.propTypes = { - theme: PropTypes.string, - initial: PropTypes.string -}; - export default ServerAvatar; diff --git a/app/views/WorkspaceView/index.js b/app/views/WorkspaceView/index.tsx similarity index 81% rename from app/views/WorkspaceView/index.js rename to app/views/WorkspaceView/index.tsx index dbd1c69e0..68ac5157b 100644 --- a/app/views/WorkspaceView/index.js +++ b/app/views/WorkspaceView/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Text, View } from 'react-native'; -import PropTypes from 'prop-types'; +import { StackNavigationProp, StackNavigationOptions } from '@react-navigation/stack'; import { connect } from 'react-redux'; import I18n from '../../i18n'; @@ -12,23 +12,27 @@ import { getShowLoginButton } from '../../selectors/login'; import ServerAvatar from './ServerAvatar'; import styles from './styles'; -class WorkspaceView extends React.Component { - static navigationOptions = () => ({ - title: I18n.t('Your_workspace') - }); +interface IWorkSpaceProp { + // TODO: waiting for the RootStackParamList https://reactnavigation.org/docs/typescript/#type-checking-screens + navigation: StackNavigationProp<any, 'WorkspaceView'>; + theme: string; + Site_Name: string; + Site_Url: string; + server: string; + Assets_favicon_512: { + url?: string; + defaultUrl: string; + }; + registrationForm: string; + registrationText: string; + showLoginButton: boolean; + Accounts_iframe_enabled: boolean; + inviteLinkToken: string; +} - static propTypes = { - navigation: PropTypes.object, - theme: PropTypes.string, - Site_Name: PropTypes.string, - Site_Url: PropTypes.string, - server: PropTypes.string, - Assets_favicon_512: PropTypes.object, - registrationForm: PropTypes.string, - registrationText: PropTypes.string, - showLoginButton: PropTypes.bool, - Accounts_iframe_enabled: PropTypes.bool, - inviteLinkToken: PropTypes.string +class WorkspaceView extends React.Component<IWorkSpaceProp, any> { + static navigationOptions: StackNavigationOptions = { + title: I18n.t('Your_workspace') }; get showRegistrationButton() { @@ -94,7 +98,7 @@ class WorkspaceView extends React.Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: any) => ({ server: state.server.server, Site_Name: state.settings.Site_Name, Site_Url: state.settings.Site_Url, diff --git a/app/views/WorkspaceView/styles.js b/app/views/WorkspaceView/styles.ts similarity index 100% rename from app/views/WorkspaceView/styles.js rename to app/views/WorkspaceView/styles.ts diff --git a/e2e/tests/assorted/13-display-pref.spec.js b/e2e/tests/assorted/13-display-pref.spec.js new file mode 100644 index 000000000..602838a82 --- /dev/null +++ b/e2e/tests/assorted/13-display-pref.spec.js @@ -0,0 +1,96 @@ +const { login, navigateToLogin } = require('../../helpers/app'); +const data = require('../../data'); + +const goToDisplayPref = async () => { + await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible(); + await element(by.id('rooms-list-view-sidebar')).tap(); + await expect(element(by.id('sidebar-display'))).toBeVisible(); + await element(by.id('sidebar-display')).tap(); +}; +const goToRoomList = async () => { + await expect(element(by.id('display-view-drawer'))).toBeVisible(); + await element(by.id('display-view-drawer')).tap(); + await expect(element(by.id('sidebar-chats'))).toBeVisible(); + await element(by.id('sidebar-chats')).tap(); +}; + +describe('Rooms list screen', () => { + before(async () => { + await device.launchApp({ permissions: { notifications: 'YES' }, newInstance: true, delete: true }); + await navigateToLogin(); + await login(data.users.regular.username, data.users.regular.password); + }); + + describe('Render', () => { + it('should have rooms list screen', async () => { + await expect(element(by.id('rooms-list-view'))).toBeVisible(); + }); + + it('should have room item', async () => { + await expect(element(by.id('rooms-list-view-item-general'))).toExist(); + }); + + // Render - Header + describe('Header', () => { + it('should have create channel button', async () => { + await expect(element(by.id('rooms-list-view-create-channel'))).toBeVisible(); + }); + + it('should have sidebar button', async () => { + await expect(element(by.id('rooms-list-view-sidebar'))).toBeVisible(); + }); + }); + + describe('DisplayPrefView', () => { + it('should go to Display Preferences', async () => { + await goToDisplayPref(); + }); + + it('should have Displays button, expanded, condensed, avatars', async () => { + await expect(element(by.id('display-pref-view-expanded'))).toBeVisible(); + await expect(element(by.id('display-pref-view-condensed'))).toBeVisible(); + await expect(element(by.id('display-pref-view-avatars'))).toBeVisible(); + }); + + it('should have Sort By button', async () => { + await expect(element(by.id('display-pref-view-activity'))).toBeVisible(); + await expect(element(by.id('display-pref-view-name'))).toBeVisible(); + }); + + it('should have Group by button', async () => { + await expect(element(by.id('display-pref-view-unread'))).toBeVisible(); + await expect(element(by.id('display-pref-view-favorites'))).toBeVisible(); + await expect(element(by.id('display-pref-view-types'))).toBeVisible(); + }); + }); + + describe('Change display', () => { + it('should appear the last message in RoomList when is Expanded', async () => { + await element(by.id('display-pref-view-expanded')).tap(); + await goToRoomList(); + await expect(element(by.id('room-item-last-message')).atIndex(0)).toBeVisible(); + }); + + it('should not appear the last message in RoomList when is Condensed', async () => { + await goToDisplayPref(); + await element(by.id('display-pref-view-condensed')).tap(); + await goToRoomList(); + await expect(element(by.id('room-item-last-message'))).not.toBeVisible(); + }); + }); + + describe('Change the avatar visible', () => { + it('should have avatar as default in room list', async () => { + await expect(element(by.id('avatar')).atIndex(0)).toExist(); + }); + + it('should hide the avatar', async () => { + await goToDisplayPref(); + await expect(element(by.id('display-pref-view-avatar-switch'))).toBeVisible(); + await element(by.id('display-pref-view-avatar-switch')).tap(); + await goToRoomList(); + await expect(element(by.id('avatar'))).not.toBeVisible(); + }); + }); + }); +}); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 98abdd502..8b21c0edf 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -530,12 +530,12 @@ PODS: - RNFBApp - RNGestureHandler (1.10.3): - React-Core - - RNImageCropPicker (0.31.1): + - RNImageCropPicker (0.36.3): - React-Core - React-RCTImage - - RNImageCropPicker/QBImagePickerController (= 0.31.1) + - RNImageCropPicker/QBImagePickerController (= 0.36.3) - TOCropViewController - - RNImageCropPicker/QBImagePickerController (0.31.1): + - RNImageCropPicker/QBImagePickerController (0.36.3): - React-Core - React-RCTImage - TOCropViewController @@ -977,7 +977,7 @@ SPEC CHECKSUMS: EXVideoThumbnails: cd257fc6e07884a704a5674d362a6410933acb68 EXWebBrowser: d37a5ffdea1b65947352bc001dd9f732463725d4 FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b - FBReactNativeSpec: b427d2f482828b9533661dbcf9edf846cb60dc7b + FBReactNativeSpec: 110d69378fce79af38271c39894b59fec7890221 Firebase: 919186c8e119dd9372a45fd1dd17a8a942bc1892 FirebaseAnalytics: 5fa308e1b13f838d0f6dc74719ac2a72e8c5afc4 FirebaseCore: 8cd4f8ea22075e0ee582849b1cf79d8816506085 @@ -1061,7 +1061,7 @@ SPEC CHECKSUMS: RNFBApp: 6fd8a7e757135d4168bf033a8812c241af7363a0 RNFBCrashlytics: 88de72c2476b5868a892d9523b89b86c527c540e RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211 - RNImageCropPicker: 38865ab4af1b0b2146ad66061196bc0184946855 + RNImageCropPicker: 97289cd94fb01ab79db4e5c92938be4d0d63415d RNLocalize: 82a569022724d35461e2dc5b5d015a13c3ca995b RNReanimated: 241c586663f44f19a53883c63375fdd041253960 RNRootView: 895a4813dedeaca82db2fa868ca1c333d790e494 diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index a2835d109..4c54b0d3e 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -1689,7 +1689,7 @@ INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 4.20.0; + MARKETING_VERSION = 4.21.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; @@ -1726,7 +1726,7 @@ INFOPLIST_FILE = NotificationService/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 4.20.0; + MARKETING_VERSION = 4.21.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = chat.rocket.reactnative.NotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index 4c7ca4e19..fa56e9b07 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -26,7 +26,7 @@ <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> - <string>4.20.0</string> + <string>4.21.0</string> <key>CFBundleSignature</key> <string>????</string> <key>CFBundleURLTypes</key> diff --git a/ios/ShareRocketChatRN/Info.plist b/ios/ShareRocketChatRN/Info.plist index 84bc33705..a0e5d8826 100644 --- a/ios/ShareRocketChatRN/Info.plist +++ b/ios/ShareRocketChatRN/Info.plist @@ -26,7 +26,7 @@ <key>CFBundlePackageType</key> <string>XPC!</string> <key>CFBundleShortVersionString</key> - <string>4.20.0</string> + <string>4.21.0</string> <key>CFBundleVersion</key> <string>1</string> <key>KeychainGroup</key> diff --git a/package.json b/package.json index 5e98f3f6c..939c200ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rocket-chat-reactnative", - "version": "4.20.0", + "version": "4.21.0", "private": true, "scripts": { "start": "react-native start", diff --git a/patches/react-native-image-crop-picker+0.31.1.patch b/patches/react-native-image-crop-picker+0.31.1.patch deleted file mode 100644 index 167e876ea..000000000 --- a/patches/react-native-image-crop-picker+0.31.1.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java -index 3500542..94e45b6 100644 ---- a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java -+++ b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java -@@ -584,6 +584,7 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi - image.putInt("height", options.outHeight); - image.putString("mime", options.outMimeType); - image.putInt("size", (int) new File(compressedImagePath).length()); -+ image.putString("filename", compressedImage.getName()); - image.putString("modificationDate", String.valueOf(modificationDate)); - - if (includeBase64) { diff --git a/storybook/stories/RoomItem.js b/storybook/stories/RoomItem.js index 5baeb9f95..1538c118a 100644 --- a/storybook/stories/RoomItem.js +++ b/storybook/stories/RoomItem.js @@ -7,6 +7,7 @@ import { Provider } from 'react-redux'; import { themes } from '../../app/constants/colors'; import RoomItemComponent from '../../app/presentation/RoomItem/RoomItem'; import { longText } from '../utils'; +import { DISPLAY_MODE_CONDENSED, DISPLAY_MODE_EXPANDED } from '../../app/constants/constantDisplayMode'; import { store } from './index'; const baseUrl = 'https://open.rocket.chat'; @@ -30,6 +31,8 @@ const RoomItem = props => ( baseUrl={baseUrl} width={width} theme={_theme} + showAvatar + displayMode={DISPLAY_MODE_EXPANDED} {...updatedAt} {...props} /> @@ -126,3 +129,57 @@ stories.add('Last Message', () => ( <RoomItem showLastMessage alert tunread={[1]} lastMessage={lastMessage} /> </> )); + +stories.add('Condensed Room Item', () => ( + <> + <RoomItem showLastMessage alert tunread={[1]} lastMessage={lastMessage} displayMode={DISPLAY_MODE_CONDENSED} /> + <RoomItem showLastMessage alert name='unread' unread={1000} displayMode={DISPLAY_MODE_CONDENSED} /> + + <RoomItem type='c' displayMode={DISPLAY_MODE_CONDENSED} autoJoin /> + </> +)); + +stories.add('Condensed Room Item without Avatar', () => ( + <> + <RoomItem + showLastMessage + alert + tunread={[1]} + lastMessage={lastMessage} + displayMode={DISPLAY_MODE_CONDENSED} + showAvatar={false} + /> + <RoomItem type='p' displayMode={DISPLAY_MODE_CONDENSED} showAvatar={false} /> + <RoomItem name={longText} autoJoin displayMode={DISPLAY_MODE_CONDENSED} showAvatar={false} /> + </> +)); + +stories.add('Expanded Room Item without Avatar', () => ( + <> + <RoomItem + showLastMessage + alert + tunread={[1]} + lastMessage={lastMessage} + displayMode={DISPLAY_MODE_EXPANDED} + showAvatar={false} + /> + <RoomItem + status='online' + showLastMessage + alert + tunread={[1]} + lastMessage={lastMessage} + displayMode={DISPLAY_MODE_EXPANDED} + showAvatar={false} + /> + <RoomItem + status='online' + showLastMessage + alert + lastMessage={lastMessage} + displayMode={DISPLAY_MODE_EXPANDED} + showAvatar={false} + /> + </> +)); diff --git a/yarn.lock b/yarn.lock index c18d86eed..569f41cc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13824,8 +13824,8 @@ react-native-gesture-handler@^1.10.3: prop-types "^15.7.2" react-native-image-crop-picker@RocketChat/react-native-image-crop-picker: - version "0.31.1" - resolved "https://codeload.github.com/RocketChat/react-native-image-crop-picker/tar.gz/db1040b57e8536bd64db699897361167009b359c" + version "0.36.3" + resolved "https://codeload.github.com/RocketChat/react-native-image-crop-picker/tar.gz/f347776247afb5cbd1400dde215689d7ca8fd6f2" react-native-image-progress@^1.1.1: version "1.1.1"