diff --git a/README.md b/README.md index 174994ae8..0ef048c2f 100644 --- a/README.md +++ b/README.md @@ -50,37 +50,35 @@ Readme will guide you on how to config. ### Current priorities -1) Onboarding ([#392][i392]) -2) Splash screen ([#399][i399]) -3) Add empty chat background ([#398][i398]) -4) Rooms list layout ([#395][i395]) -5) Create channel layout ([#401][i401]) +1) Open PDF and other file types ([#341][i341]) +2) [NEW] Commands ([#405][i405]) +3) Better message actions ([#329][i329]) +4) [NEW] Login/Register/Forgot Password layout ([#400][i400]) ### To do | Task | Status | |--------------------|-----| | [NEW] Reply Preview ([#311][i311]) | ✅ | | Image upload improvements ([#368][i368]) | ✅ | -| [NEW] Onboarding ([#392][i392]) | WIP | -| [NEW] Contextual bar layout ([#402][i402]) | ❌ | -| [NEW] Create channel layout ([#401][i401]) | ❌ | -| [NEW] Login/Register/Forgot Password layout ([#400][i400]) | ❌ | -| [NEW] Splash screen ([#399][i399]) | ❌ | -| [NEW] Add empty chat background ([#398][i398]) | ❌ | -| [NEW] Message layout ([#397][i397]) | ❌ | +| [NEW] Onboarding ([#392][i392]) | ✅ | +| [NEW] Create channel layout ([#401][i401]) | ✅ | +| [NEW] Splash screen ([#399][i399]) | ✅ | +| [NEW] Add empty chat background ([#398][i398]) | ✅ | +| [NEW] Message layout ([#397][i397]) | ✅ | +| [NEW] Rooms list layout ([#395][i395]) | ✅ | +| Add components to Storybook ([#38][i38]) | WIP | +| Open PDF and other file types ([#341][i341]) | WIP | +| Better message actions ([#329][i329]) | ❌ | | [NEW] Settings layout ([#396][i396]) | ❌ | -| [NEW] Rooms list layout ([#395][i395]) | ❌ | +| [NEW] Contextual bar layout ([#402][i402]) | ❌ | +| [NEW] Login/Register/Forgot Password layout ([#400][i400]) | ❌ | | [NEW] Commands ([#405][i405]) | ❌ | | [Android] Add Fastlane ([#404][i404]) | ❌ | -| [Android] Adaptive icons ([#403][i403]) | ❌ | | [NEW] Auto versioning app on Circle CI ([#393][i393]) | ❌ | | [Android] Group notifications by room ([#391][i391]) | ❌ | -| Open PDF and other file types ([#341][i341]) | ❌ | -| Better message actions ([#329][i329]) | ❌ | | Integrate project with code push ([#233][i233]) | ❌ | | Custom icons ([#210][i210]) | ❌ | | Share Extension ([#69][i69]) | ❌ | -| Add components to Storybook ([#38][i38]) | ❌ | | Upload files ([#2][i2]) | ❌ | [i2]: https://github.com/RocketChat/Rocket.Chat.ReactNative/issues/2 @@ -124,10 +122,10 @@ Readme will guide you on how to config. | Messages list: load more on scroll | ✅ | | Messages list: receive new messages via subscription | ✅ | | Subscriptions list | ✅ | -| Segmented subscriptions list: Favorites | ❌ | -| Segmented subscriptions list: Unreads | ❌ | -| Segmented subscriptions list: DMs | ❌ | -| Segmented subscriptions list: Channels | ❌ | +| Segmented subscriptions list: Favorites | ✅ | +| Segmented subscriptions list: Unreads | ✅ | +| Segmented subscriptions list: DMs | ✅ | +| Segmented subscriptions list: Channels | ✅ | | Subscriptions list: update user status via subscription | ✅ | | Numbers os messages unread in the Subscriptions list | ✅ | | Status change | ✅ | @@ -205,7 +203,7 @@ Readme will guide you on how to config. | Localized in Portuguese (pt-BR) | ❌ | | Localized in Russian | ✅ | | Localized in English | ✅ | -| Full name setting | ❌ | +| Full name setting | ✅ | | Read only rooms | ✅ | | Typing status | ✅ | | Create channel/group | ✅ | diff --git a/__mocks__/fileMock.js b/__mocks__/fileMock.js new file mode 100644 index 000000000..86059f362 --- /dev/null +++ b/__mocks__/fileMock.js @@ -0,0 +1 @@ +module.exports = 'test-file-stub'; diff --git a/__mocks__/react-native-i18n.js b/__mocks__/react-native-i18n.js new file mode 100644 index 000000000..ae8e97878 --- /dev/null +++ b/__mocks__/react-native-i18n.js @@ -0,0 +1,7 @@ +// @flow +/* eslint-disable */ +import I18nJs from 'i18n-js'; + +I18nJs.locale = 'en'; // a locale from your available translations +export const getLanguages = (): Promise => Promise.resolve(['en']); +export default I18nJs; \ No newline at end of file diff --git a/__mocks__/react-native-safari-view.js b/__mocks__/react-native-safari-view.js new file mode 100644 index 000000000..b1b73a38c --- /dev/null +++ b/__mocks__/react-native-safari-view.js @@ -0,0 +1,5 @@ +export default function() { + return { + show: () => {} + }; +} diff --git a/__mocks__/react-native-video-controls.js b/__mocks__/react-native-video-controls.js new file mode 100644 index 000000000..6b2ed22f8 --- /dev/null +++ b/__mocks__/react-native-video-controls.js @@ -0,0 +1 @@ +export default () => 'Video'; diff --git a/__mocks__/react-native-video.js b/__mocks__/react-native-video.js new file mode 100644 index 000000000..6b2ed22f8 --- /dev/null +++ b/__mocks__/react-native-video.js @@ -0,0 +1 @@ +export default () => 'Video'; diff --git a/__tests__/RoomItem.js b/__tests__/RoomItem.js index b6c422246..a2dfc92f3 100644 --- a/__tests__/RoomItem.js +++ b/__tests__/RoomItem.js @@ -13,29 +13,28 @@ import RoomItem from '../app/presentation/RoomItem'; import renderer from 'react-test-renderer'; const date = new Date(2017, 10, 10, 10); - -jest.mock('react-native-img-cache', () => { return { CachedImage: 'View' } }); +const onPress = () => {}; it('renders correctly', () => { - expect(renderer.create().toJSON()).toMatchSnapshot(); + expect(renderer.create().toJSON()).toMatchSnapshot(); }); it('render unread', () => { - expect(renderer.create().toJSON()).toMatchSnapshot(); + expect(renderer.create().toJSON()).toMatchSnapshot(); }); it('render unread +999', () => { - expect(renderer.create().toJSON()).toMatchSnapshot(); + expect(renderer.create().toJSON()).toMatchSnapshot(); }); it('render no icon', () => { - expect(renderer.create().toJSON()).toMatchSnapshot(); + expect(renderer.create().toJSON()).toMatchSnapshot(); }); it('render private group', () => { - expect(renderer.create( ).toJSON()).toMatchSnapshot(); + expect(renderer.create( ).toJSON()).toMatchSnapshot(); }); it('render channel', () => { - expect(renderer.create().toJSON()).toMatchSnapshot(); + expect(renderer.create().toJSON()).toMatchSnapshot(); }); diff --git a/__tests__/__snapshots__/Storyshots.test.js.snap b/__tests__/__snapshots__/Storyshots.test.js.snap index 2d8a2d067..2464e0f55 100644 --- a/__tests__/__snapshots__/Storyshots.test.js.snap +++ b/__tests__/__snapshots__/Storyshots.test.js.snap @@ -60,7 +60,7 @@ exports[`Storyshots Avatar avatar 1`] = ` source={ Object { "priority": "high", - "uri": "/avatar/test", + "uri": "baseUrl/avatar/test", } } style={ @@ -132,7 +132,7 @@ exports[`Storyshots Avatar avatar 1`] = ` source={ Object { "priority": "high", - "uri": "/avatar/aa", + "uri": "baseUrl/avatar/aa", } } style={ @@ -204,7 +204,7 @@ exports[`Storyshots Avatar avatar 1`] = ` source={ Object { "priority": "high", - "uri": "/avatar/bb", + "uri": "baseUrl/avatar/bb", } } style={ @@ -276,7 +276,7 @@ exports[`Storyshots Avatar avatar 1`] = ` source={ Object { "priority": "high", - "uri": "/avatar/test", + "uri": "baseUrl/avatar/test", } } style={ @@ -3017,3 +3017,16780 @@ exports[`Storyshots Channel Cell Direct Messages 1`] = ` `; + +exports[`Storyshots Message list 1`] = ` + + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Message + + + + + + + + + + Simple + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum + + + + + + + + + + Long message + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + … + + + + + + + + + + + + + + R + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + Different user + + + + + + + + + + + + + + + + + This is the third message + + + + + + + + + + + + + + + + + This is the second message + + + + + + + + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + This is the first message + + + + + + + + + + Grouped messages + + + + + + + + + + Message + + + + + + + + + + Without header + + + + + + + D + + + + + + + + + Diego Mello + + + @ + diego.mello + + + 10:00 AM + + + + + + + Message + + + + + + + + + + With alias + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Message + + + + (edited) + + + + + + + + + Edited + + + + + + + + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Message + + + + + + + + + + Static avatar + + + + + + + D + + + + + + + + + Diego Mello + + + 10:00 AM + + + + + + + Message + + + + + + + + + + Full name + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + +   + rocket.cat +   + + + + + +   + diego.mello +   + + + + + +   + all +   + + + + + +   + here +   + + + + + +  # + general +   + + + + + + + + + + Mentions + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + 👊🤙👏 + + + + + + + + + + Emojis + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + + + + + + + + + + + + + Custom Emojis + + + + + + + D + + + + + + + + + diego.mello + + + 10 November 2017 + + + + + + + Testing + + + + + + + + + + Time format + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Reactions + + + + + + + + + 😂 + + + 3 + + + + + + + + 13 + + + + + + + 🤔 + + + 1 + + + + + + + + + + + + + Reactions + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Multiple Reactions + + + + + + + + + + 1 + + + + + + + + 1 + + + + + + + + 1 + + + + + + + ❤️ + + + 1 + + + + + + + 🐶 + + + 1 + + + + + + + 😀 + + + 1 + + + + + + + 😬 + + + 1 + + + + + + + 😁 + + + 1 + + + + + + + + + + + + + Multiple reactions + + + + + + + R + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + Fourth message + + + + + + + + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Third message + + + + + + + + + + + + + + R + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + Second message + + + + + + + + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + First message + + + + + + + + + + Intercalated users + + + + + + + R + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + Fourth message + + + + + + + + + + + Nov 10, 2017 + + + + unread messages + + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Third message + + + + + + + + + + + + unread messages + + + + + + + + + + + + Second message + + + + + + + + + + + + + + R + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + Second message + + + + + + + + + + + + Nov 10, 2017 + + + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + First message + + + + + + + + + + Date and Unread separators + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + This is a description + + + + + + + + + + + + This is a title + + + This is a description + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + This is a description + + + + + + + + + + + + This is a title + + + This is a description + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + With image + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + This is a description + + + + + + + + + Video + + + + + + + + With video + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Video + + + + + + + + + + + 00:00 + + + + + + + This is a description + + + + + + + + + + With audio + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + https://rocket.chat + + + Rocket.Chat - Free, Open Source, Enterprise Team Chat + + + Rocket.Chat is the leading open source team chat software solution. Free, unlimited and completely customizable with on-premises and SaaS cloud hosting. + + + + + + + + + https://google.com + + + Google + + + Search the world's information, including webpages, images, videos and more. Google has many special features to help you find exactly what you're looking for. + + + + + + + + + + URL + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Message + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + Custom fields + + + + + + + + Field 1 + + + Value 1 + + + + + Field 2 + + + Value 2 + + + + + Field 3 + + + Value 3 + + + + + Field 4 + + + Value 4 + + + + + Field 5 + + + Value 5 + + + + + + + + + + + Custom fields + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Message + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + Custom fields + + + + + + + + Field 1 + + + Value 1 + + + + + Field 2 + + + Value 2 + + + + + + + + + + rocket.cat + + + 10:00 AM + + + + + + + Custom fields 2 + + + + + + + + Field 1 + + + Value 1 + + + + + Field 2 + + + Value 2 + + + + + + + + + + + Two short custom fields + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Broadcasted message + + + + + + + + + Reply + + + + + + + + + Broadcast + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + This message is inside an archived room + + + + + + + + + + Archived + + + + + +  + + + + + + + This message has error too + + + + + + + + + + + + +  + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + This message has error + + + + + + + + + + Error + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Temp message + + + + + + + + + + Temp + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Message being edited + + + + + + + + + + Editing + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Message removed + + + + + + + Removed + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Has joined the channel + + + + + + + Joined + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Room name changed to: New name by diego.mello + + + + + + + Room name changed + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Message pinned + + + + + + rocket.cat + + + 10:00 AM + + + + + + + First message + + + + + + + + + + + + Message pinned + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Has left the channel + + + + + + + Has left the channel + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + User rocket.cat removed by diego.mello + + + + + + + User removed + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + User rocket.cat added by diego.mello + + + + + + + User added + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + User rocket.cat muted by diego.mello + + + + + + + User muted + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + User rocket.cat unmuted by diego.mello + + + + + + + User unmuted + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + rocket.cat was set admin by diego.mello + + + + + + + Role added + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + rocket.cat is no longer admin by diego.mello + + + + + + + Role removed + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Room description changed to: new description by diego.mello + + + + + + + Changed description + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Room announcement changed to: new announcement by diego.mello + + + + + + + Changed announcement + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Room topic changed to: new topic by diego.mello + + + + + + + Changed topic + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + Room type changed to: public by diego.mello + + + + + + + Changed type + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Message + + + + + + + + + + Custom style + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Italic with + + + + asterisks + + + + or + + + + underscores + + + + . Bold with + + + + asterisks + + + + or + + + + underscores + + + + . + + + + Strikethrough + + + + + + + + + + + Markdown emphasis + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + H1 + + + + + + + H2 + + + + + + + H3 + + + + + + + H4 + + + + + + + H5 + + + + + + + H6 + + + + + + + + + + Markdown headers + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Support + + + + Google + + + + + + + + I\`m an inline-style link + + + + https://google.com + + + + + + + + + + Markdown links + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + + + + Markdown image + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + Inline + + + code + + + has + + + back-ticks around + + + it. + + + + + Code block + + + + + + + + + Markdown code + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + Quote + + + + + + + + + + + Markdown quote + + + + + + + D + + + + + + + + + diego.mello + + + 10:00 AM + + + + + + + + + + First Header + + + + + + + Second Header + + + + + + + + + + + Content from cell 1 + + + + + + + Content from cell 2 + + + + + + + + + Content in the first column + + + + + + + Content in the second column + + + + + + + + + + + + + Markdown table + + + +`; diff --git a/android/app/build.gradle b/android/app/build.gradle index c0cbdd07f..f318e3dd4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -102,7 +102,7 @@ android { minSdkVersion 19 targetSdkVersion 27 versionCode VERSIONCODE as Integer - versionName "1.1.1" + versionName "1.2" ndk { abiFilters "armeabi-v7a", "x86" } diff --git a/android/app/src/main/res/drawable-hdpi/add_reaction.png b/android/app/src/main/res/drawable-hdpi/add_reaction.png new file mode 100644 index 000000000..ade95a3de Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/add_reaction.png differ diff --git a/android/app/src/main/res/drawable-hdpi/pause.png b/android/app/src/main/res/drawable-hdpi/pause.png new file mode 100644 index 000000000..bd701d4e4 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/pause.png differ diff --git a/android/app/src/main/res/drawable-hdpi/play.png b/android/app/src/main/res/drawable-hdpi/play.png new file mode 100644 index 000000000..f42026fbf Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/play.png differ diff --git a/android/app/src/main/res/drawable-hdpi/play_video.png b/android/app/src/main/res/drawable-hdpi/play_video.png new file mode 100644 index 000000000..5c5a9bb6d Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/play_video.png differ diff --git a/android/app/src/main/res/drawable-hdpi/reply.png b/android/app/src/main/res/drawable-hdpi/reply.png new file mode 100644 index 000000000..ffb027e1d Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/reply.png differ diff --git a/android/app/src/main/res/drawable-mdpi/add_reaction.png b/android/app/src/main/res/drawable-mdpi/add_reaction.png new file mode 100644 index 000000000..fd4ef8bda Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/add_reaction.png differ diff --git a/android/app/src/main/res/drawable-mdpi/pause.png b/android/app/src/main/res/drawable-mdpi/pause.png new file mode 100644 index 000000000..b13411a50 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/pause.png differ diff --git a/android/app/src/main/res/drawable-mdpi/play.png b/android/app/src/main/res/drawable-mdpi/play.png new file mode 100644 index 000000000..b40c8f3cc Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/play.png differ diff --git a/android/app/src/main/res/drawable-mdpi/play_video.png b/android/app/src/main/res/drawable-mdpi/play_video.png new file mode 100644 index 000000000..83fc41316 Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/play_video.png differ diff --git a/android/app/src/main/res/drawable-mdpi/reply.png b/android/app/src/main/res/drawable-mdpi/reply.png new file mode 100644 index 000000000..4d791de4a Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/reply.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/add_reaction.png b/android/app/src/main/res/drawable-xhdpi/add_reaction.png new file mode 100644 index 000000000..1c2825de8 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/add_reaction.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/pause.png b/android/app/src/main/res/drawable-xhdpi/pause.png new file mode 100644 index 000000000..962f15c4c Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/pause.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/play.png b/android/app/src/main/res/drawable-xhdpi/play.png new file mode 100644 index 000000000..c38804865 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/play.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/play_video.png b/android/app/src/main/res/drawable-xhdpi/play_video.png new file mode 100644 index 000000000..a8958ac26 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/play_video.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/reply.png b/android/app/src/main/res/drawable-xhdpi/reply.png new file mode 100644 index 000000000..b5d4a0043 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/reply.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/add_reaction.png b/android/app/src/main/res/drawable-xxhdpi/add_reaction.png new file mode 100644 index 000000000..d76bb4295 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/add_reaction.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/pause.png b/android/app/src/main/res/drawable-xxhdpi/pause.png new file mode 100644 index 000000000..e2ba5b563 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/pause.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/play.png b/android/app/src/main/res/drawable-xxhdpi/play.png new file mode 100644 index 000000000..2e60e189a Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/play.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/play_video.png b/android/app/src/main/res/drawable-xxhdpi/play_video.png new file mode 100644 index 000000000..def52c709 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/play_video.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/reply.png b/android/app/src/main/res/drawable-xxhdpi/reply.png new file mode 100644 index 000000000..5ceef53aa Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/reply.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/add_reaction.png b/android/app/src/main/res/drawable-xxxhdpi/add_reaction.png new file mode 100644 index 000000000..b6b77bbc4 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/add_reaction.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/pause.png b/android/app/src/main/res/drawable-xxxhdpi/pause.png new file mode 100644 index 000000000..81d47ece6 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/pause.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/play.png b/android/app/src/main/res/drawable-xxxhdpi/play.png new file mode 100644 index 000000000..55329245a Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/play.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/play_video.png b/android/app/src/main/res/drawable-xxxhdpi/play_video.png new file mode 100644 index 000000000..41cc52ed6 Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/play_video.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/reply.png b/android/app/src/main/res/drawable-xxxhdpi/reply.png new file mode 100644 index 000000000..2c38d2b2b Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/reply.png differ diff --git a/app/constants/settings.js b/app/constants/settings.js index 7206e0ecb..ec9916268 100644 --- a/app/constants/settings.js +++ b/app/constants/settings.js @@ -79,5 +79,9 @@ export default { }, Store_Last_Message: { type: 'valueAsBoolean' + }, + UI_Use_Real_Name: { + type: 'valueAsBoolean' } }; +export const settingsUpdatedAt = new Date('2018-09-10'); diff --git a/app/containers/Avatar.js b/app/containers/Avatar.js index cf357c2b2..b55466cd4 100644 --- a/app/containers/Avatar.js +++ b/app/containers/Avatar.js @@ -1,6 +1,5 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; import { StyleSheet, Text, View, ViewPropTypes } from 'react-native'; import FastImage from 'react-native-fast-image'; import avatarInitialsAndColor from '../utils/avatarInitialsAndColor'; @@ -19,13 +18,10 @@ const styles = StyleSheet.create({ } }); -@connect(state => ({ - baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' -})) export default class Avatar extends React.PureComponent { static propTypes = { + baseUrl: PropTypes.string.isRequired, style: ViewPropTypes.style, - baseUrl: PropTypes.string, text: PropTypes.string, avatar: PropTypes.string, size: PropTypes.number, diff --git a/app/containers/EmojiPicker/CustomEmoji.js b/app/containers/EmojiPicker/CustomEmoji.js index 30f8670fd..7079d31e5 100644 --- a/app/containers/EmojiPicker/CustomEmoji.js +++ b/app/containers/EmojiPicker/CustomEmoji.js @@ -1,11 +1,7 @@ import React from 'react'; import { ViewPropTypes, Image } from 'react-native'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -@connect(state => ({ - baseUrl: state.settings.Site_Url -})) export default class CustomEmoji extends React.Component { static propTypes = { baseUrl: PropTypes.string.isRequired, diff --git a/app/containers/EmojiPicker/EmojiCategory.js b/app/containers/EmojiPicker/EmojiCategory.js index cdef44435..bfd7ff94f 100644 --- a/app/containers/EmojiPicker/EmojiCategory.js +++ b/app/containers/EmojiPicker/EmojiCategory.js @@ -10,9 +10,9 @@ import scrollPersistTaps from '../../utils/scrollPersistTaps'; const emojisPerRow = Platform.OS === 'ios' ? 8 : 9; -const renderEmoji = (emoji, size) => { +const renderEmoji = (emoji, size, baseUrl) => { if (emoji.isCustom) { - return ; + return ; } return ( @@ -25,6 +25,7 @@ const renderEmoji = (emoji, size) => { @responsive export default class EmojiCategory extends React.Component { static propTypes = { + baseUrl: PropTypes.string.isRequired, emojis: PropTypes.any, window: PropTypes.any, onEmojiSelected: PropTypes.func, @@ -44,6 +45,7 @@ export default class EmojiCategory extends React.Component { } renderItem(emoji, size) { + const { baseUrl } = this.props; return ( this.props.onEmojiSelected(emoji)} testID={`reaction-picker-${ emoji.isCustom ? emoji.content : emoji }`} > - {renderEmoji(emoji, size)} + {renderEmoji(emoji, size, baseUrl)} ); } diff --git a/app/containers/EmojiPicker/index.js b/app/containers/EmojiPicker/index.js index ac30b7c37..93306e41a 100644 --- a/app/containers/EmojiPicker/index.js +++ b/app/containers/EmojiPicker/index.js @@ -19,6 +19,7 @@ const scrollProps = { export default class EmojiPicker extends Component { static propTypes = { + baseUrl: PropTypes.string.isRequired, onEmojiSelected: PropTypes.func, tabEmojiStyle: PropTypes.object, emojisPerRow: PropTypes.number, @@ -110,6 +111,7 @@ export default class EmojiPicker extends Component { style={styles.categoryContainer} size={this.props.emojisPerRow} width={this.props.width} + baseUrl={this.props.baseUrl} /> ); } @@ -123,12 +125,14 @@ export default class EmojiPicker extends Component { } contentProps={scrollProps} + style={styles.background} > { categories.tabs.map((tab, i) => ( {this.renderCategory(tab.category, i)} diff --git a/app/containers/EmojiPicker/styles.js b/app/containers/EmojiPicker/styles.js index 038d11b20..35cbd7e97 100644 --- a/app/containers/EmojiPicker/styles.js +++ b/app/containers/EmojiPicker/styles.js @@ -1,6 +1,9 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ + background: { + backgroundColor: '#fff' + }, container: { flex: 1 }, diff --git a/app/containers/MessageBox/EmojiKeyboard.js b/app/containers/MessageBox/EmojiKeyboard.js index ec60b34b8..9b5cb08e2 100644 --- a/app/containers/MessageBox/EmojiKeyboard.js +++ b/app/containers/MessageBox/EmojiKeyboard.js @@ -1,22 +1,25 @@ import React from 'react'; import { View } from 'react-native'; import { KeyboardRegistry } from 'react-native-keyboard-input'; -import { Provider } from 'react-redux'; + import store from '../../lib/createStore'; import EmojiPicker from '../EmojiPicker'; import styles from './styles'; export default class EmojiKeyboard extends React.PureComponent { + constructor(props) { + super(props); + const state = store.getState(); + this.baseUrl = state.settings.Site_Url || state.server ? state.server.server : ''; + } onEmojiSelected = (emoji) => { KeyboardRegistry.onItemSelected('EmojiKeyboard', { emoji }); } render() { return ( - - - this.onEmojiSelected(emoji)} /> - - + + this.onEmojiSelected(emoji)} baseUrl={this.baseUrl} /> + ); } } diff --git a/app/containers/MessageBox/ReplyPreview.js b/app/containers/MessageBox/ReplyPreview.js index 2463984ec..1b3f321ae 100644 --- a/app/containers/MessageBox/ReplyPreview.js +++ b/app/containers/MessageBox/ReplyPreview.js @@ -9,15 +9,17 @@ import Markdown from '../message/Markdown'; const styles = StyleSheet.create({ container: { - flexDirection: 'row' + flexDirection: 'row', + marginTop: 10, + backgroundColor: '#fff' }, messageContainer: { flex: 1, - marginHorizontal: 15, + marginHorizontal: 10, backgroundColor: '#F3F4F5', paddingHorizontal: 15, paddingVertical: 10, - borderRadius: 2 + borderRadius: 4 }, header: { flexDirection: 'row', @@ -35,18 +37,23 @@ const styles = StyleSheet.create({ marginLeft: 5 }, close: { - marginRight: 15 + marginRight: 10 } }); @connect(state => ({ - Message_TimeFormat: state.settings.Message_TimeFormat + Message_TimeFormat: state.settings.Message_TimeFormat, + customEmojis: state.customEmojis, + baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' })) export default class ReplyPreview extends Component { static propTypes = { message: PropTypes.object.isRequired, Message_TimeFormat: PropTypes.string.isRequired, - close: PropTypes.func.isRequired + close: PropTypes.func.isRequired, + customEmojis: PropTypes.object.isRequired, + baseUrl: PropTypes.string.isRequired, + username: PropTypes.string.isRequired } close = () => { @@ -54,7 +61,9 @@ export default class ReplyPreview extends Component { } render() { - const { message, Message_TimeFormat } = this.props; + const { + message, Message_TimeFormat, customEmojis, baseUrl, username + } = this.props; const time = moment(message.ts).format(Message_TimeFormat); return ( @@ -63,9 +72,9 @@ export default class ReplyPreview extends Component { {message.u.username} {time} - + - + ); } diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js index 63eb3bb12..3c520def0 100644 --- a/app/containers/MessageBox/index.js +++ b/app/containers/MessageBox/index.js @@ -530,6 +530,7 @@ export default class MessageBox extends React.PureComponent { text={item.username || item.name} size={30} type={item.username ? 'd' : 'c'} + baseUrl={this.props.baseUrl} />, { item.username || item.name } ] @@ -556,11 +557,13 @@ export default class MessageBox extends React.PureComponent { }; renderReplyPreview = () => { - const { replyMessage, replying, closeReply } = this.props; + const { + replyMessage, replying, closeReply, username + } = this.props; if (!replying) { return null; } - return ; + return ; }; renderFilesActions = () => { @@ -584,29 +587,30 @@ export default class MessageBox extends React.PureComponent { return ( [ this.renderMentions(), - this.renderReplyPreview(), - - {this.leftButtons} - this.component = component} - style={styles.textBoxInput} - returnKeyType='default' - keyboardType='twitter' - blurOnSubmit={false} - placeholder={I18n.t('New_Message')} - onChangeText={text => this.onChangeText(text)} - value={this.state.text} - underlineColorAndroid='transparent' - defaultValue='' - multiline - placeholderTextColor='#9EA2A8' - testID='messagebox-input' - /> - {this.rightButtons} + + {this.renderReplyPreview()} + + {this.leftButtons} + this.component = component} + style={styles.textBoxInput} + returnKeyType='default' + keyboardType='twitter' + blurOnSubmit={false} + placeholder={I18n.t('New_Message')} + onChangeText={text => this.onChangeText(text)} + value={this.state.text} + underlineColorAndroid='transparent' + defaultValue='' + multiline + placeholderTextColor='#9EA2A8' + testID='messagebox-input' + /> + {this.rightButtons} + ] ); diff --git a/app/containers/MessageBox/styles.js b/app/containers/MessageBox/styles.js index 8293f6e25..5fa1aea79 100644 --- a/app/containers/MessageBox/styles.js +++ b/app/containers/MessageBox/styles.js @@ -11,13 +11,17 @@ export default StyleSheet.create({ borderTopColor: '#D8D8D8', zIndex: 2 }, + composer: { + backgroundColor: '#fff', + flexDirection: 'column', + borderTopColor: '#e1e5e8', + borderTopWidth: 1 + }, textArea: { flexDirection: 'row', alignItems: 'center', flexGrow: 0, - backgroundColor: '#fff', - borderTopColor: '#ECECEC', - borderTopWidth: 1 + backgroundColor: '#fff' }, textBoxInput: { textAlignVertical: 'center', diff --git a/app/containers/Sidebar.js b/app/containers/Sidebar.js index 382939ae6..8a1bccb72 100644 --- a/app/containers/Sidebar.js +++ b/app/containers/Sidebar.js @@ -85,7 +85,8 @@ const keyExtractor = item => item.id; server: state.login.user && state.login.user.server, status: state.login.user && state.login.user.status, username: state.login.user && state.login.user.username - } + }, + baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' }), dispatch => ({ selectServerRequest: server => dispatch(selectServerRequest(server)), logout: () => dispatch(logout()), @@ -93,6 +94,7 @@ const keyExtractor = item => item.id; })) export default class Sidebar extends Component { static propTypes = { + baseUrl: PropTypes.string, navigator: PropTypes.object, server: PropTypes.string.isRequired, selectServerRequest: PropTypes.func.isRequired, @@ -323,7 +325,7 @@ export default class Sidebar extends Component { ) render() { - const { user, server } = this.props; + const { user, server, baseUrl } = this.props; if (!user) { return null; } @@ -341,6 +343,7 @@ export default class Sidebar extends Component { text={user.username} size={30} style={styles.avatar} + baseUrl={baseUrl} /> diff --git a/app/containers/message/Audio.js b/app/containers/message/Audio.js index c809dee80..ecedd793c 100644 --- a/app/containers/message/Audio.js +++ b/app/containers/message/Audio.js @@ -1,74 +1,55 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, StyleSheet, TouchableOpacity, Text, Easing } from 'react-native'; +import { View, StyleSheet, TouchableOpacity, Text, Easing, Image } from 'react-native'; import Video from 'react-native-video'; -import Icon from 'react-native-vector-icons/MaterialIcons'; import Slider from 'react-native-slider'; -import { connect } from 'react-redux'; +import moment from 'moment'; + import Markdown from './Markdown'; const styles = StyleSheet.create({ audioContainer: { flex: 1, flexDirection: 'row', - justifyContent: 'center', alignItems: 'center', - height: 50, - margin: 5, - backgroundColor: '#eee', - borderRadius: 6 + height: 56, + backgroundColor: '#f7f8fa', + borderRadius: 4, + marginBottom: 10 }, playPauseButton: { - width: 50, + width: 56, alignItems: 'center', - backgroundColor: 'transparent', - borderRightColor: '#ccc', - borderRightWidth: 1 - }, - playPauseIcon: { - color: '#ccc', backgroundColor: 'transparent' }, - progressContainer: { + playPauseImage: { + width: 30, + height: 30 + }, + slider: { flex: 1, - justifyContent: 'center', - height: '100%', - marginHorizontal: 10 - }, - label: { - color: '#888', - fontSize: 10 - }, - currentTime: { - position: 'absolute', - left: 0, - bottom: 2 + marginRight: 10 }, duration: { - position: 'absolute', - right: 0, - bottom: 2 + marginRight: 16, + fontSize: 14, + fontWeight: '500', + color: '#54585e' + }, + thumbStyle: { + width: 12, + height: 12 } }); -const formatTime = (t = 0, duration = 0) => { - const time = Math.min( - Math.max(t, 0), - duration - ); - const formattedMinutes = Math.floor(time / 60).toFixed(0).padStart(2, 0); - const formattedSeconds = Math.floor(time % 60).toFixed(0).padStart(2, 0); - return `${ formattedMinutes }:${ formattedSeconds }`; -}; +const formatTime = seconds => moment.utc(seconds * 1000).format('mm:ss'); -@connect(state => ({ - baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' -})) export default class Audio extends React.PureComponent { static propTypes = { file: PropTypes.object.isRequired, baseUrl: PropTypes.string.isRequired, - user: PropTypes.object.isRequired + user: PropTypes.object.isRequired, + customEmojis: PropTypes.object.isRequired } constructor(props) { @@ -90,7 +71,7 @@ export default class Audio extends React.PureComponent { } onProgress(data) { - if (data.currentTime < this.state.duration) { + if (data.currentTime <= this.state.duration) { this.setState({ currentTime: data.currentTime }); } } @@ -102,10 +83,6 @@ export default class Audio extends React.PureComponent { }); } - getCurrentTime() { - return formatTime(this.state.currentTime, this.state.duration); - } - getDuration() { return formatTime(this.state.duration); } @@ -116,7 +93,10 @@ export default class Audio extends React.PureComponent { render() { const { uri, paused } = this.state; - const { description } = this.props.file; + const { + user, baseUrl, customEmojis, file + } = this.props; + const { description } = file; return ( [ @@ -136,29 +116,30 @@ export default class Audio extends React.PureComponent { onPress={() => this.togglePlayPause()} > { - paused ? - : + paused ? + : + } - - {this.getCurrentTime()} - {this.getDuration()} - this.setState({ currentTime: value })} - /> - + this.setState({ currentTime: value })} + thumbStyle={styles.thumbStyle} + /> + {this.getDuration()} , - + ] ); } diff --git a/app/containers/message/Emoji.js b/app/containers/message/Emoji.js index 5f87f64e0..c26cba26d 100644 --- a/app/containers/message/Emoji.js +++ b/app/containers/message/Emoji.js @@ -2,28 +2,28 @@ import React from 'react'; import { Text, ViewPropTypes } from 'react-native'; import PropTypes from 'prop-types'; import { emojify } from 'react-emojione'; -import { connect } from 'react-redux'; import CustomEmoji from '../EmojiPicker/CustomEmoji'; -@connect(state => ({ - customEmojis: state.customEmojis -})) export default class Emoji extends React.PureComponent { static propTypes = { - content: PropTypes.string, + content: PropTypes.string.isRequired, + baseUrl: PropTypes.string.isRequired, standardEmojiStyle: Text.propTypes.style, customEmojiStyle: ViewPropTypes.style, - customEmojis: PropTypes.object.isRequired + customEmojis: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]) }; render() { const { - content, standardEmojiStyle, customEmojiStyle, customEmojis + content, standardEmojiStyle, customEmojiStyle, customEmojis, baseUrl } = this.props; const parsedContent = content.replace(/^:|:$/g, ''); const emojiExtension = customEmojis[parsedContent]; if (emojiExtension) { const emoji = { extension: emojiExtension, content: parsedContent }; - return ; + return ; } return { emojify(`${ content }`, { output: 'unicode' }) }; } diff --git a/app/containers/message/Image.js b/app/containers/message/Image.js index f3b83b27b..d6ab72e7e 100644 --- a/app/containers/message/Image.js +++ b/app/containers/message/Image.js @@ -2,29 +2,30 @@ import PropTypes from 'prop-types'; import React from 'react'; import FastImage from 'react-native-fast-image'; import { TouchableOpacity } from 'react-native'; -import { connect } from 'react-redux'; import PhotoModal from './PhotoModal'; import Markdown from './Markdown'; import styles from './styles'; -@connect(state => ({ - baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' -})) export default class extends React.PureComponent { static propTypes = { file: PropTypes.object.isRequired, baseUrl: PropTypes.string.isRequired, user: PropTypes.object.isRequired, - customEmojis: PropTypes.object + customEmojis: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]) } state = { modalVisible: false }; getDescription() { - const { file, customEmojis } = this.props; + const { + file, customEmojis, baseUrl, user + } = this.props; if (file.description) { - return ; + return ; } } @@ -47,13 +48,14 @@ export default class extends React.PureComponent { {this.getDescription()} , this.setState({ modalVisible: false })} diff --git a/app/containers/message/Markdown.js b/app/containers/message/Markdown.js index da54d141c..10d00a4e2 100644 --- a/app/containers/message/Markdown.js +++ b/app/containers/message/Markdown.js @@ -1,8 +1,7 @@ import React from 'react'; -import { Text, Platform, Image } from 'react-native'; +import { Text, Image } from 'react-native'; import PropTypes from 'prop-types'; import { emojify } from 'react-emojione'; -import { connect } from 'react-redux'; import MarkdownRenderer, { PluginContainer } from 'react-native-markdown-renderer'; import MarkdownFlowdock from 'markdown-it-flowdock'; import styles from './styles'; @@ -16,16 +15,13 @@ const formatText = text => (match, url, title) => `[${ title }](${ url })` ); -@connect(state => ({ - customEmojis: state.customEmojis -})) export default class Markdown extends React.Component { shouldComponentUpdate(nextProps) { return nextProps.msg !== this.props.msg; } render() { const { - msg, customEmojis, style, rules + msg, customEmojis, style, rules, baseUrl, username, edited } = this.props; if (!msg) { return null; @@ -36,21 +32,38 @@ export default class Markdown extends React.Component { return ( ( - - {children} - - ) - }, - mention: node => ( - alert(`Username @${ node.content }`)} style={styles.mention}> - @{node.content} + paragraph: (node, children) => ( + + {children}{edited ? (edited) : null} ), + mention: (node) => { + const { content, key } = node; + let mentionStyle = styles.mention; + if (content === 'all' || content === 'here') { + mentionStyle = { + ...mentionStyle, + ...styles.mentionAll + }; + } else if (content === username) { + mentionStyle = { + ...mentionStyle, + ...styles.mentionLoggedUser + }; + } + return ( + alert(`Username ${ content }`)} + style={mentionStyle} + > +  {content}  + + ); + }, hashtag: node => ( alert(`Room #${ node.content }`)} style={styles.mention}> - #{node.content} +  #{node.content}  ), emoji: (node) => { @@ -59,7 +72,7 @@ export default class Markdown extends React.Component { const emojiExtension = customEmojis[content]; if (emojiExtension) { const emoji = { extension: emojiExtension, content }; - return ; + return ; } return :{content}:; } @@ -74,6 +87,11 @@ export default class Markdown extends React.Component { }} style={{ paragraph: styles.paragraph, + text: { + color: '#0C0D0F', + fontSize: 16, + letterSpacing: 0.1 + }, codeInline: { borderWidth: 1, borderColor: '#CCCCCC', @@ -81,6 +99,9 @@ export default class Markdown extends React.Component { padding: 2, borderRadius: 4 }, + link: { + color: '#1D74F5' + }, ...style }} plugins={[ @@ -95,7 +116,10 @@ export default class Markdown extends React.Component { Markdown.propTypes = { msg: PropTypes.string, - customEmojis: PropTypes.object, + username: PropTypes.string.isRequired, + baseUrl: PropTypes.string.isRequired, + customEmojis: PropTypes.object.isRequired, style: PropTypes.any, - rules: PropTypes.object + rules: PropTypes.object, + edited: PropTypes.bool }; diff --git a/app/containers/message/Message.js b/app/containers/message/Message.js new file mode 100644 index 000000000..665b97d78 --- /dev/null +++ b/app/containers/message/Message.js @@ -0,0 +1,351 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { View, Text, TouchableOpacity, ViewPropTypes, Image as ImageRN } from 'react-native'; +import Icon from 'react-native-vector-icons/MaterialIcons'; +import moment from 'moment'; +import { KeyboardUtils } from 'react-native-keyboard-input'; + +import Image from './Image'; +import User from './User'; +import Avatar from '../Avatar'; +import Audio from './Audio'; +import Video from './Video'; +import Markdown from './Markdown'; +import Url from './Url'; +import Reply from './Reply'; +import ReactionsModal from './ReactionsModal'; +import Emoji from './Emoji'; +import styles from './styles'; +import Touch from '../../utils/touch'; +import I18n from '../../i18n'; +import messagesStatus from '../../constants/messagesStatus'; + +const SYSTEM_MESSAGES = [ + 'r', + 'au', + 'ru', + 'ul', + 'uj', + 'rm', + 'user-muted', + 'user-unmuted', + 'message_pinned', + 'subscription-role-added', + 'subscription-role-removed', + 'room_changed_description', + 'room_changed_announcement', + 'room_changed_topic', + 'room_changed_privacy' +]; + +const getInfoMessage = ({ + type, role, msg, user +}) => { + const { username } = user; + if (type === 'rm') { + return I18n.t('Message_removed'); + } else if (type === 'uj') { + return I18n.t('Has_joined_the_channel'); + } else if (type === 'r') { + return I18n.t('Room_name_changed', { name: msg, userBy: username }); + } else if (type === 'message_pinned') { + return I18n.t('Message_pinned'); + } else if (type === 'ul') { + return I18n.t('Has_left_the_channel'); + } else if (type === 'ru') { + return I18n.t('User_removed_by', { userRemoved: msg, userBy: username }); + } else if (type === 'au') { + return I18n.t('User_added_by', { userAdded: msg, userBy: username }); + } else if (type === 'user-muted') { + return I18n.t('User_muted_by', { userMuted: msg, userBy: username }); + } else if (type === 'user-unmuted') { + return I18n.t('User_unmuted_by', { userUnmuted: msg, userBy: username }); + } else if (type === 'subscription-role-added') { + return `${ msg } was set ${ role } by ${ username }`; + } else if (type === 'subscription-role-removed') { + return `${ msg } is no longer ${ role } by ${ username }`; + } else if (type === 'room_changed_description') { + return I18n.t('Room_changed_description', { description: msg, userBy: username }); + } else if (type === 'room_changed_announcement') { + return I18n.t('Room_changed_announcement', { announcement: msg, userBy: username }); + } else if (type === 'room_changed_topic') { + return I18n.t('Room_changed_topic', { topic: msg, userBy: username }); + } else if (type === 'room_changed_privacy') { + return I18n.t('Room_changed_privacy', { type: msg, userBy: username }); + } + return ''; +}; + +export default class Message extends PureComponent { + static propTypes = { + baseUrl: PropTypes.string.isRequired, + customEmojis: PropTypes.object.isRequired, + timeFormat: PropTypes.string.isRequired, + msg: PropTypes.string, + user: PropTypes.shape({ + id: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + token: PropTypes.string.isRequired + }), + author: PropTypes.shape({ + _id: PropTypes.string.isRequired, + username: PropTypes.string.isRequired, + name: PropTypes.string + }), + status: PropTypes.any, + reactions: PropTypes.any, + editing: PropTypes.bool, + style: ViewPropTypes.style, + archived: PropTypes.bool, + broadcast: PropTypes.bool, + reactionsModal: PropTypes.bool, + type: PropTypes.string, + header: PropTypes.bool, + avatar: PropTypes.string, + alias: PropTypes.string, + ts: PropTypes.instanceOf(Date), + edited: PropTypes.bool, + attachments: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]), + urls: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]), + useRealName: PropTypes.bool, + // methods + closeReactions: PropTypes.func, + onErrorPress: PropTypes.func, + onLongPress: PropTypes.func, + onReactionLongPress: PropTypes.func, + onReactionPress: PropTypes.func, + replyBroadcast: PropTypes.func, + toggleReactionPicker: PropTypes.func + } + + static defaultProps = { + archived: false, + broadcast: false, + attachments: [], + urls: [], + reactions: [], + onLongPress: () => {} + } + + onPress = () => { + KeyboardUtils.dismiss(); + } + + isInfoMessage() { + return SYSTEM_MESSAGES.includes(this.props.type); + } + + isOwn = () => this.props.author._id === this.props.user.id; + + isDeleted() { + return this.props.type === 'rm'; + } + + isTemp() { + return this.props.status === messagesStatus.TEMP || this.props.status === messagesStatus.ERROR; + } + + hasError() { + return this.props.status === messagesStatus.ERROR; + } + + renderAvatar = () => { + const { + header, avatar, author, baseUrl + } = this.props; + if (header) { + return ( + + ); + } + return null; + } + + renderUsername = () => { + const { + header, timeFormat, author, alias, ts, useRealName + } = this.props; + if (header) { + return ( + + ); + } + return null; + } + + renderContent() { + if (this.isInfoMessage()) { + return {getInfoMessage({ ...this.props })}; + } + const { + customEmojis, msg, baseUrl, user, edited + } = this.props; + return ; + } + + renderAttachment() { + const { attachments, timeFormat } = this.props; + + if (attachments.length === 0) { + return null; + } + + return attachments.map((file, index) => { + const { user, baseUrl, customEmojis } = this.props; + if (file.image_url) { + return ; + } + if (file.audio_url) { + return @@ -97,22 +98,22 @@ export default class ReactionsModal extends React.PureComponent { render() { const { - isVisible, onClose, reactions + isVisible, close, reactions } = this.props; return ( - + {I18n.t('Reactions')} diff --git a/app/containers/message/Reply.js b/app/containers/message/Reply.js index fd7654329..2516f9d53 100644 --- a/app/containers/message/Reply.js +++ b/app/containers/message/Reply.js @@ -1,39 +1,41 @@ import React from 'react'; -import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'; +import { View, Text, StyleSheet } from 'react-native'; import PropTypes from 'prop-types'; import moment from 'moment'; import Markdown from './Markdown'; -import QuoteMark from './QuoteMark'; -import Avatar from '../Avatar'; import openLink from '../../utils/openLink'; - +import Touch from '../../utils/touch'; const styles = StyleSheet.create({ button: { flex: 1, flexDirection: 'row', alignItems: 'center', - marginTop: 2, + marginTop: 15, alignSelf: 'flex-end' }, attachmentContainer: { flex: 1, - flexDirection: 'column' + borderRadius: 4, + flexDirection: 'column', + backgroundColor: '#f3f4f5', + padding: 15 }, authorContainer: { flexDirection: 'row', alignItems: 'center' }, author: { - fontWeight: 'bold', - marginHorizontal: 5, - flex: 1 + color: '#1d74f5', + fontSize: 18, + fontWeight: '500', + marginRight: 10 }, time: { - fontSize: 10, + fontSize: 14, fontWeight: 'normal', - color: '#888', + color: '#9ea2a8', marginLeft: 5 }, fieldsContainer: { @@ -47,6 +49,9 @@ const styles = StyleSheet.create({ }, fieldTitle: { fontWeight: 'bold' + }, + marginTop: { + marginTop: 4 } }); @@ -58,23 +63,13 @@ const onPress = (attachment) => { openLink(attachment.title_link || attachment.author_link); }; -const Reply = ({ attachment, timeFormat }) => { +const Reply = ({ + attachment, timeFormat, baseUrl, customEmojis, user, index +}) => { if (!attachment) { return null; } - const renderAvatar = () => { - if (!attachment.author_icon && !attachment.author_name) { - return null; - } - return ( - - ); - }; - const renderAuthor = () => ( attachment.author_name ? {attachment.author_name} : null ); @@ -90,7 +85,6 @@ const Reply = ({ attachment, timeFormat }) => { } return ( - {renderAvatar()} {renderAuthor()} {renderTime()} @@ -98,7 +92,7 @@ const Reply = ({ attachment, timeFormat }) => { }; const renderText = () => ( - attachment.text ? : null + attachment.text ? : null ); const renderFields = () => { @@ -119,28 +113,26 @@ const Reply = ({ attachment, timeFormat }) => { }; return ( - onPress(attachment)} - style={styles.button} + style={[styles.button, index > 0 && styles.marginTop]} > - {renderTitle()} {renderText()} {renderFields()} - {attachment.attachments ? - attachment.attachments - .map(attach => ) - : null - } - + ); }; Reply.propTypes = { attachment: PropTypes.object.isRequired, - timeFormat: PropTypes.string.isRequired + timeFormat: PropTypes.string.isRequired, + baseUrl: PropTypes.string.isRequired, + customEmojis: PropTypes.object.isRequired, + user: PropTypes.object.isRequired, + index: PropTypes.number }; export default Reply; diff --git a/app/containers/message/Url.js b/app/containers/message/Url.js index 43dd2542e..730b065e0 100644 --- a/app/containers/message/Url.js +++ b/app/containers/message/Url.js @@ -1,67 +1,82 @@ import React from 'react'; -import { View, Text, TouchableOpacity, StyleSheet, Image } from 'react-native'; +import { View, Text, StyleSheet } from 'react-native'; import PropTypes from 'prop-types'; +import FastImage from 'react-native-fast-image'; -import QuoteMark from './QuoteMark'; import openLink from '../../utils/openLink'; +import Touch from '../../utils/touch'; const styles = StyleSheet.create({ button: { - flex: 1, - flexDirection: 'row', - alignItems: 'center', - marginVertical: 2 + marginTop: 10 }, - image: { - height: 80, - width: 80, - resizeMode: 'cover', - borderRadius: 6 + container: { + flex: 1, + flexDirection: 'column', + borderRadius: 4, + backgroundColor: '#F3F4F5' }, textContainer: { flex: 1, - height: '100%', flexDirection: 'column', - padding: 4, + padding: 15, justifyContent: 'flex-start', alignItems: 'flex-start' }, title: { - fontWeight: 'bold', - fontSize: 12 + fontWeight: '500', + color: '#1D74F5', + fontSize: 16, + marginTop: 5 }, description: { - fontSize: 12 + marginTop: 5, + fontSize: 16, + color: '#0C0D0F' + }, + url: { + fontSize: 15, + fontWeight: '500', + color: '#9EA2A8' + }, + marginTop: { + marginTop: 4 + }, + image: { + width: '100%', + height: 150, + borderTopLeftRadius: 4, + borderTopRightRadius: 4 } }); const onPress = (url) => { openLink(url); }; -const Url = ({ url }) => { +const Url = ({ url, index }) => { if (!url) { return null; } return ( - onPress(url.url)} style={styles.button}> - - {url.image ? - - : null - } - - {url.title} - {url.description} + onPress(url.url)} style={[styles.button, index > 0 && styles.marginTop]}> + + {/* + {url.image ? : null} + */} + {url.image ? : null} + + {url.url} + {url.title} + {url.description} + - + ); }; Url.propTypes = { - url: PropTypes.object.isRequired + url: PropTypes.object.isRequired, + index: PropTypes.number }; export default Url; diff --git a/app/containers/message/User.js b/app/containers/message/User.js index 8480f9b7d..2b988acc8 100644 --- a/app/containers/message/User.js +++ b/app/containers/message/User.js @@ -2,14 +2,13 @@ import React from 'react'; import PropTypes from 'prop-types'; import { View, Text, StyleSheet } from 'react-native'; import moment from 'moment'; -import Icon from 'react-native-vector-icons/FontAwesome'; -import Avatar from '../Avatar'; const styles = StyleSheet.create({ username: { - color: '#000', - fontWeight: '400', - fontSize: 14 + color: '#0C0D0F', + fontWeight: '600', + fontSize: 16, + lineHeight: 22 }, usernameView: { flexDirection: 'row', @@ -17,67 +16,50 @@ const styles = StyleSheet.create({ marginBottom: 2 }, alias: { - fontSize: 10, - color: '#888', - paddingLeft: 5 + fontSize: 14, + color: '#9EA2A8', + paddingLeft: 6, + lineHeight: 16 }, time: { - fontSize: 10, - color: '#888', - paddingLeft: 5, - fontWeight: '400' - }, - edited: { - marginLeft: 5, - flexDirection: 'row', - alignItems: 'center' + fontSize: 14, + color: '#9EA2A8', + paddingLeft: 10, + fontWeight: '300', + lineHeight: 16 } }); export default class User extends React.PureComponent { static propTypes = { - item: PropTypes.object.isRequired, - Message_TimeFormat: PropTypes.string.isRequired, + timeFormat: PropTypes.string.isRequired, + username: PropTypes.string, + alias: PropTypes.string, + ts: PropTypes.instanceOf(Date), + temp: PropTypes.bool, onPress: PropTypes.func } - renderEdited = (item) => { - if (!item.editedBy) { - return null; - } - return ( - - - - - ); - } - render() { - const { item } = this.props; + const { + username, alias, ts, temp + } = this.props; const extraStyle = {}; - if (item.temp) { + if (temp) { extraStyle.opacity = 0.3; } - const username = item.alias || item.u.username; - const aliasUsername = item.alias ? (@{item.u.username}) : null; - const time = moment(item.ts).format(this.props.Message_TimeFormat); + const aliasUsername = alias ? (@{username}) : null; + const time = moment(ts).format(this.props.timeFormat); return ( - {username} + {alias || username} {aliasUsername} {time} - {this.renderEdited(item)} ); } diff --git a/app/containers/message/Video.js b/app/containers/message/Video.js index 4476cc0c4..59be1c448 100644 --- a/app/containers/message/Video.js +++ b/app/containers/message/Video.js @@ -1,9 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { StyleSheet, TouchableOpacity, Image, Platform } from 'react-native'; +import { StyleSheet, TouchableOpacity, Image, Platform, View } from 'react-native'; import Modal from 'react-native-modal'; import VideoPlayer from 'react-native-video-controls'; -import { connect } from 'react-redux'; import Markdown from './Markdown'; import openLink from '../../utils/openLink'; @@ -11,31 +10,31 @@ const SUPPORTED_TYPES = ['video/quicktime', 'video/mp4', ...(Platform.OS === 'io const isTypeSupported = type => SUPPORTED_TYPES.indexOf(type) !== -1; const styles = StyleSheet.create({ - container: { + button: { flex: 1, - height: 100, - margin: 5 + borderRadius: 4, + height: 150, + backgroundColor: '#1f2329', + marginBottom: 10, + alignItems: 'center', + justifyContent: 'center' }, modal: { margin: 0, backgroundColor: '#000' }, image: { - flex: 1, - width: null, - height: null, - resizeMode: 'contain' + width: 54, + height: 54 } }); -@connect(state => ({ - baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' -})) export default class Video extends React.PureComponent { static propTypes = { file: PropTypes.object.isRequired, baseUrl: PropTypes.string.isRequired, - user: PropTypes.object.isRequired + user: PropTypes.object.isRequired, + customEmojis: PropTypes.object.isRequired } state = { isVisible: false }; @@ -62,19 +61,21 @@ export default class Video extends React.PureComponent { render() { const { isVisible } = this.state; const { description } = this.props.file; + const { baseUrl, user, customEmojis } = this.props; return ( [ - this.open()} - > - - - , + + this.open()} + > + + + + , { - if (t === 'rm') { - return I18n.t('Message_removed'); - } else if (t === 'uj') { - return I18n.t('Has_joined_the_channel'); - } else if (t === 'r') { - return I18n.t('Room_name_changed', { name: msg, userBy: u.username }); - } else if (t === 'message_pinned') { - return I18n.t('Message_pinned'); - } else if (t === 'ul') { - return I18n.t('Has_left_the_channel'); - } else if (t === 'ru') { - return I18n.t('User_removed_by', { userRemoved: msg, userBy: u.username }); - } else if (t === 'au') { - return I18n.t('User_added_by', { userAdded: msg, userBy: u.username }); - } else if (t === 'user-muted') { - return I18n.t('User_muted_by', { userMuted: msg, userBy: u.username }); - } else if (t === 'user-unmuted') { - return I18n.t('User_unmuted_by', { userUnmuted: msg, userBy: u.username }); - } else if (t === 'subscription-role-added') { - return `${ msg } was set ${ role } by ${ u.username }`; - } else if (t === 'subscription-role-removed') { - return `${ msg } is no longer ${ role } by ${ u.username }`; - } else if (t === 'room_changed_description') { - return I18n.t('Room_changed_description', { description: msg, userBy: u.username }); - } else if (t === 'room_changed_announcement') { - return I18n.t('Room_changed_announcement', { announcement: msg, userBy: u.username }); - } else if (t === 'room_changed_topic') { - return I18n.t('Room_changed_topic', { topic: msg, userBy: u.username }); - } else if (t === 'room_changed_privacy') { - return I18n.t('Room_changed_privacy', { type: msg, userBy: u.username }); - } - return ''; -}; +import Message from './Message'; +import { errorActionsShow, toggleReactionPicker, replyBroadcast } from '../../actions/messages'; @connect(state => ({ - message: state.messages.message, - editing: state.messages.editing, + baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', customEmojis: state.customEmojis, + editing: state.messages.editing, + Message_GroupingPeriod: state.settings.Message_GroupingPeriod, Message_TimeFormat: state.settings.Message_TimeFormat, - Message_GroupingPeriod: state.settings.Message_GroupingPeriod + message: state.messages.message, + useRealName: state.settings.UI_Use_Real_Name }), dispatch => ({ - actionsShow: actionMessage => dispatch(actionsShow(actionMessage)), errorActionsShow: actionMessage => dispatch(errorActionsShow(actionMessage)), - toggleReactionPicker: message => dispatch(toggleReactionPicker(message)), - replyBroadcast: message => dispatch(replyBroadcast(message)) + replyBroadcast: message => dispatch(replyBroadcast(message)), + toggleReactionPicker: message => dispatch(toggleReactionPicker(message)) })) -export default class Message extends React.Component { +export default class MessageContainer extends React.Component { static propTypes = { - status: PropTypes.any, item: PropTypes.object.isRequired, reactions: PropTypes.any.isRequired, - Message_TimeFormat: PropTypes.string.isRequired, - Message_GroupingPeriod: PropTypes.number.isRequired, - customTimeFormat: PropTypes.string, - message: PropTypes.object.isRequired, user: PropTypes.shape({ id: PropTypes.string.isRequired, username: PropTypes.string.isRequired, token: PropTypes.string.isRequired }), - editing: PropTypes.bool, - errorActionsShow: PropTypes.func, - toggleReactionPicker: PropTypes.func, - replyBroadcast: PropTypes.func, - onReactionPress: PropTypes.func, + customTimeFormat: PropTypes.string, style: ViewPropTypes.style, - onLongPress: PropTypes.func, - _updatedAt: PropTypes.instanceOf(Date), + status: PropTypes.number, archived: PropTypes.bool, broadcast: PropTypes.bool, - previousItem: PropTypes.object + previousItem: PropTypes.object, + _updatedAt: PropTypes.instanceOf(Date), + // redux + baseUrl: PropTypes.string, + customEmojis: PropTypes.object, + editing: PropTypes.bool, + Message_GroupingPeriod: PropTypes.number, + Message_TimeFormat: PropTypes.string, + message: PropTypes.object, + useRealName: PropTypes.bool, + // methods - props + onLongPress: PropTypes.func, + onReactionPress: PropTypes.func, + // methods - redux + errorActionsShow: PropTypes.func, + replyBroadcast: PropTypes.func, + toggleReactionPicker: PropTypes.func } static defaultProps = { @@ -127,7 +63,7 @@ export default class Message extends React.Component { constructor(props) { super(props); this.state = { reactionsModal: false }; - this.onClose = this.onClose.bind(this); + this.closeReactions = this.closeReactions.bind(this); } shouldComponentUpdate(nextProps, nextState) { @@ -147,12 +83,12 @@ export default class Message extends React.Component { if (this.props.broadcast !== nextProps.broadcast) { return true; } + if (this.props.editing !== nextProps.editing) { + return true; + } return this.props._updatedAt.toGMTString() !== nextProps._updatedAt.toGMTString(); } - onPress = () => { - KeyboardUtils.dismiss(); - } onLongPress = () => { this.props.onLongPress(this.parseMessage()); @@ -165,10 +101,9 @@ export default class Message extends React.Component { onReactionPress = (emoji) => { this.props.onReactionPress(emoji, this.props.item._id); } - onClose() { - this.setState({ reactionsModal: false }); - } - onReactionLongPress() { + + + onReactionLongPress = () => { this.setState({ reactionsModal: true }); Vibration.vibrate(50); } @@ -178,29 +113,12 @@ export default class Message extends React.Component { return customTimeFormat || Message_TimeFormat; } - parseMessage = () => JSON.parse(JSON.stringify(this.props.item)); - - isInfoMessage() { - return SYSTEM_MESSAGES.includes(this.props.item.t); + closeReactions = () => { + this.setState({ reactionsModal: false }); } - isOwn = () => this.props.item.u && this.props.item.u._id === this.props.user.id; - - isDeleted() { - return this.props.item.t === 'rm'; - } - - isTemp() { - return this.props.item.status === messagesStatus.TEMP || this.props.item.status === messagesStatus.ERROR; - } - - hasError() { - return this.props.item.status === messagesStatus.ERROR; - } - - renderHeader = (username) => { + isHeader = () => { const { item, previousItem } = this.props; - if (previousItem && ( (previousItem.ts.toDateString() === item.ts.toDateString()) && (previousItem.u.username === item.u.username) && @@ -208,172 +126,61 @@ export default class Message extends React.Component { (previousItem.status === item.status) && (item.ts - previousItem.ts < this.props.Message_GroupingPeriod * 1000) )) { - return null; + return false; } - - return ( - - - - - ); + return true; } - renderContent() { - if (this.isInfoMessage()) { - return {getInfoMessage(this.props.item)}; - } - const { item } = this.props; - return ; + parseMessage = () => JSON.parse(JSON.stringify(this.props.item)); + + toggleReactionPicker = () => { + this.props.toggleReactionPicker(this.parseMessage()); } - renderAttachment() { - if (this.props.item.attachments.length === 0) { - return null; - } - - return this.props.item.attachments.map((file) => { - const { user } = this.props; - if (file.image_url) { - return ; - } - if (file.audio_url) { - return : null diff --git a/app/views/RoomView/Separator.js b/app/views/RoomView/Separator.js index efb5a1f42..c722809cb 100644 --- a/app/views/RoomView/Separator.js +++ b/app/views/RoomView/Separator.js @@ -9,51 +9,62 @@ const styles = StyleSheet.create({ flex: 1, flexDirection: 'row', alignItems: 'center', - marginVertical: 10 + marginBottom: 25, + marginTop: 15, + transform: [{ scaleY: -1 }] }, line: { - borderTopColor: '#eaeaea', - borderTopWidth: StyleSheet.hairlineWidth, + backgroundColor: '#9ea2a8', + height: 1, flex: 1 }, text: { - color: '#444444', - fontSize: 11, - paddingHorizontal: 10, - transform: [{ scaleY: -1 }] + color: '#9ea2a8', + fontSize: 14, + fontWeight: '600' }, unreadLine: { - borderTopColor: 'red' + backgroundColor: '#f5455c' }, unreadText: { - color: 'red' + color: '#f5455c' + }, + marginLeft: { + marginLeft: 10 + }, + marginRight: { + marginRight: 10 + }, + marginHorizontal: { + marginHorizontal: 10 } }); const DateSeparator = ({ ts, unread }) => { - const date = ts ? moment(ts).format('MMMM DD, YYYY') : null; + const date = ts ? moment(ts).format('MMM DD, YYYY') : null; if (ts && unread) { return ( - {date} - - {I18n.t('unread_messages')} + {date} + + {I18n.t('unread_messages')} ); } if (ts) { return ( - - {date} - + + {date} + ); } return ( - - {I18n.t('unread_messages')} + + {I18n.t('unread_messages')} + ); }; diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index b739e9389..f2d01a3e9 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -232,15 +232,15 @@ export default class RoomView extends LoggedView { ); @@ -273,7 +273,7 @@ export default class RoomView extends LoggedView { renderHeader = () => { if (!this.state.end) { - return {I18n.t('Loading_messages_ellipsis')}; + return ; } return null; } diff --git a/app/views/RoomView/styles.js b/app/views/RoomView/styles.js index ab37be0c4..aea3eff45 100644 --- a/app/views/RoomView/styles.js +++ b/app/views/RoomView/styles.js @@ -46,7 +46,8 @@ export default StyleSheet.create({ flexDirection: 'column' }, loading: { - flex: 1 + flex: 1, + marginVertical: 15 }, imageBackground: { width: '100%', diff --git a/app/views/RoomsListView/Header/Header.ios.js b/app/views/RoomsListView/Header/Header.ios.js index 4ae44bc11..34a7e3aab 100644 --- a/app/views/RoomsListView/Header/Header.ios.js +++ b/app/views/RoomsListView/Header/Header.ios.js @@ -7,7 +7,8 @@ import I18n from '../../../i18n'; const styles = StyleSheet.create({ container: { flex: 1, - alignItems: 'center' + alignItems: 'center', + justifyContent: 'center' }, button: { flexDirection: 'row' diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index 1df2dc90d..3978abd88 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -43,7 +43,7 @@ if (Platform.OS === 'android') { @connect(state => ({ userId: state.login.user && state.login.user.id, server: state.server.server, - Site_Url: state.settings.Site_Url, + baseUrl: state.settings.baseUrl || state.server ? state.server.server : '', searchText: state.rooms.searchText, loadingServer: state.server.loading, showServerDropdown: state.rooms.showServerDropdown, @@ -51,7 +51,8 @@ if (Platform.OS === 'android') { sortBy: state.sortPreferences.sortBy, groupByType: state.sortPreferences.groupByType, showFavorites: state.sortPreferences.showFavorites, - showUnread: state.sortPreferences.showUnread + showUnread: state.sortPreferences.showUnread, + useRealName: state.settings.UI_Use_Real_Name }), dispatch => ({ toggleSortDropdown: () => dispatch(toggleSortDropdown()) })) @@ -72,7 +73,7 @@ export default class RoomsListView extends LoggedView { static propTypes = { navigator: PropTypes.object, userId: PropTypes.string, - Site_Url: PropTypes.string, + baseUrl: PropTypes.string, server: PropTypes.string, searchText: PropTypes.string, loadingServer: PropTypes.bool, @@ -82,12 +83,14 @@ export default class RoomsListView extends LoggedView { groupByType: PropTypes.bool, showFavorites: PropTypes.bool, showUnread: PropTypes.bool, - toggleSortDropdown: PropTypes.func + toggleSortDropdown: PropTypes.func, + useRealName: PropTypes.bool } constructor(props) { super('RoomsListView', props); + this.data = []; this.state = { search: [], loading: true, @@ -396,18 +399,19 @@ export default class RoomsListView extends LoggedView { renderItem = ({ item }) => { const id = item.rid.replace(this.props.userId, '').trim(); + const { useRealName } = this.props; return ( this._onPressItem(item)} testID={`rooms-list-view-item-${ item.name }`} height={ROW_HEIGHT} @@ -417,6 +421,15 @@ export default class RoomsListView extends LoggedView { renderSeparator = () => ; renderSection = (data, header) => { + if (header === 'Unread' && !this.props.showUnread) { + return null; + } else if (header === 'Favorites' && !this.props.showFavorites) { + return null; + } else if (['Channels', 'Direct_Messages', 'Private_Groups', 'Livechat'].includes(header) && !this.props.groupByType) { + return null; + } else if (header === 'Chats' && this.props.groupByType) { + return null; + } if (data.length > 0) { return ( {}} onReactionPress={async(emoji) => { try { await RocketChat.setReaction(emoji, item._id); @@ -124,7 +122,7 @@ export default class SearchMessagesView extends LoggedView { placeholder={I18n.t('Search_Messages')} testID='search-message-view-input' /> - + ({ + baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', users: state.selectedUsers.users, loading: state.selectedUsers.loading }), dispatch => ({ @@ -43,6 +44,7 @@ export default class SelectedUsersView extends LoggedView { navigator: PropTypes.object, rid: PropTypes.string, nextAction: PropTypes.string.isRequired, + baseUrl: PropTypes.string, addUser: PropTypes.func.isRequired, removeUser: PropTypes.func.isRequired, reset: PropTypes.func.isRequired, @@ -185,6 +187,7 @@ export default class SelectedUsersView extends LoggedView { username={item.name} onPress={() => this._onPressSelectedItem(item)} testID={`selected-user-${ item.name }`} + baseUrl={this.props.baseUrl} style={{ paddingRight: 15 }} /> ) @@ -211,6 +214,7 @@ export default class SelectedUsersView extends LoggedView { onPress={() => this._onPressItem(item._id, item)} testID={`select-users-view-item-${ item.name }`} icon={this.isChecked(username) ? 'check' : null} + baseUrl={this.props.baseUrl} style={style} /> ); diff --git a/app/views/SnippetedMessagesView/index.js b/app/views/SnippetedMessagesView/index.js index 7c4c897e2..442c5859f 100644 --- a/app/views/SnippetedMessagesView/index.js +++ b/app/views/SnippetedMessagesView/index.js @@ -17,8 +17,7 @@ import I18n from '../../i18n'; id: state.login.user && state.login.user.id, username: state.login.user && state.login.user.username, token: state.login.user && state.login.user.token - }, - baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' + } }), dispatch => ({ openSnippetedMessages: (rid, limit) => dispatch(openSnippetedMessages(rid, limit)), closeSnippetedMessages: () => dispatch(closeSnippetedMessages()) @@ -30,7 +29,6 @@ export default class SnippetedMessagesView extends LoggedView { messages: PropTypes.array, ready: PropTypes.bool, user: PropTypes.object, - baseUrl: PropTypes.string, openSnippetedMessages: PropTypes.func, closeSnippetedMessages: PropTypes.func } @@ -87,9 +85,7 @@ export default class SnippetedMessagesView extends LoggedView { style={styles.message} reactions={item.reactions} user={this.props.user} - baseUrl={this.props.baseUrl} customTimeFormat='MMMM Do YYYY, h:mm:ss a' - onLongPress={() => {}} /> ); diff --git a/app/views/StarredMessagesView/index.js b/app/views/StarredMessagesView/index.js index 4e2c476b4..3fa214b8f 100644 --- a/app/views/StarredMessagesView/index.js +++ b/app/views/StarredMessagesView/index.js @@ -23,8 +23,7 @@ const options = [I18n.t('Unstar'), I18n.t('Cancel')]; id: state.login.user && state.login.user.id, username: state.login.user && state.login.user.username, token: state.login.user && state.login.user.token - }, - baseUrl: state.settings.Site_Url || state.server ? state.server.server : '' + } }), dispatch => ({ openStarredMessages: (rid, limit) => dispatch(openStarredMessages(rid, limit)), closeStarredMessages: () => dispatch(closeStarredMessages()), @@ -37,7 +36,6 @@ export default class StarredMessagesView extends LoggedView { messages: PropTypes.array, ready: PropTypes.bool, user: PropTypes.object, - baseUrl: PropTypes.string, openStarredMessages: PropTypes.func, closeStarredMessages: PropTypes.func, toggleStarRequest: PropTypes.func @@ -113,7 +111,6 @@ export default class StarredMessagesView extends LoggedView { style={styles.message} reactions={item.reactions} user={this.props.user} - baseUrl={this.props.baseUrl} customTimeFormat='MMMM Do YYYY, h:mm:ss a' onLongPress={this.onLongPress} /> diff --git a/e2e/05-roomslist.spec.js b/e2e/05-roomslist.spec.js index 309ebf87d..87537f9fb 100644 --- a/e2e/05-roomslist.spec.js +++ b/e2e/05-roomslist.spec.js @@ -16,7 +16,7 @@ describe('Rooms list screen', () => { // }); it('should have room item', async() => { - await expect(element(by.id('rooms-list-view-item-general'))).toExist(); + await expect(element(by.id('rooms-list-view-item-general')).atIndex(0)).toExist(); }); // Render - Header diff --git a/e2e/06-createroom.spec.js b/e2e/06-createroom.spec.js index dc056586b..897bfc16a 100644 --- a/e2e/06-createroom.spec.js +++ b/e2e/06-createroom.spec.js @@ -88,8 +88,8 @@ describe('Create room screen', () => { it('should navigate to create channel view', async() => { await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toBeVisible().withTimeout(5000); - await expect(element(by.id('create-channel-view'))).toBeVisible(); + await waitFor(element(by.id('create-channel-view'))).toExist().withTimeout(5000); + await expect(element(by.id('create-channel-view'))).toExist(); }); }) @@ -145,6 +145,7 @@ describe('Create room screen', () => { await expect(element(by.text(`private${ data.random }`))).toBeVisible(); await tapBack(2); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await element(by.id('rooms-list-view-search')).replaceText(`private${ data.random }`); await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000); await expect(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible(); }); diff --git a/e2e/07-room.spec.js b/e2e/07-room.spec.js index 69a726837..09cd43dab 100644 --- a/e2e/07-room.spec.js +++ b/e2e/07-room.spec.js @@ -9,10 +9,11 @@ async function mockMessage(message) { await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText(`${ data.random }${ message }`); await element(by.id('messagebox-send-message')).tap(); - await waitFor(element(by.text(`${ data.random }${ message }`))).toBeVisible().withTimeout(60000); + await waitFor(element(by.text(`${ data.random }${ message }`))).toExist().withTimeout(60000); }; async function navigateToRoom() { + await element(by.id('rooms-list-view-search')).replaceText(`private${ data.random }`); await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000); await element(by.id(`rooms-list-view-item-private${ data.random }`)).tap(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); @@ -92,8 +93,7 @@ describe('Room screen', () => { describe('Messagebox', async() => { it('should send message', async() => { await mockMessage('message'); - await waitFor(element(by.text(`${ data.random }message`))).toBeVisible().withTimeout(60000); - await expect(element(by.text(`${ data.random }message`))).toBeVisible(); + await expect(element(by.text(`${ data.random }message`))).toExist(); }); it('should show/hide emoji keyboard', async() => { @@ -139,9 +139,9 @@ describe('Room screen', () => { await element(by.id(`mention-item-${ data.user }`)).tap(); await expect(element(by.id('messagebox-input'))).toHaveText(`@${ data.user } `); await element(by.id('messagebox-input')).tap(); - await element(by.id('messagebox-input')).typeText('test'); + await element(by.id('messagebox-input')).typeText(`${ data.random }mention`); await element(by.id('messagebox-send-message')).tap(); - await waitFor(element(by.text(`@${ data.user } test`))).toBeVisible().withTimeout(60000); + await waitFor(element(by.text(`@${ data.user } ${ data.random }mention`))).toBeVisible().withTimeout(60000); }); it('should show and tap on room autocomplete', async() => { @@ -250,8 +250,8 @@ describe('Room screen', () => { await element(by.text('Edit')).tap(); await element(by.id('messagebox-input')).typeText('ed'); await element(by.id('messagebox-send-message')).tap(); - await waitFor(element(by.text(`${ data.random }edited`))).toBeVisible().withTimeout(60000); - await expect(element(by.text(`${ data.random }edited`))).toBeVisible(); + await waitFor(element(by.text(`${ data.random }edited (edited)`))).toBeVisible().withTimeout(60000); + await expect(element(by.text(`${ data.random }edited (edited)`))).toBeVisible(); }); it('should quote message', async() => { @@ -266,13 +266,15 @@ describe('Room screen', () => { }); it('should pin message', async() => { - await element(by.text(`${ data.random }edited`)).longPress(); + await waitFor(element(by.text(`${ data.random }edited (edited)`))).toBeVisible().whileElement(by.id('room-view-messages')).scroll(200, 'up'); + await element(by.text(`${ data.random }edited (edited)`)).longPress(); await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Pin')).tap(); await waitFor(element(by.text('Message actions'))).toBeNotVisible().withTimeout(5000); - await waitFor(element(by.text(`${ data.random }edited`)).atIndex(1)).toBeVisible().withTimeout(60000); - await element(by.text(`${ data.random }edited`)).atIndex(0).longPress(); + await waitFor(element(by.text(`${ data.random }edited (edited)`))).toBeVisible().whileElement(by.id('room-view-messages')).scroll(200, 'up'); + await waitFor(element(by.text(`${ data.random }edited (edited)`)).atIndex(1)).toBeVisible().withTimeout(60000); + await element(by.text(`${ data.random }edited (edited)`)).atIndex(0).longPress(); await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000); await expect(element(by.text('Unpin'))).toBeVisible(); await element(by.text('Cancel')).tap(); diff --git a/e2e/08-roomactions.spec.js b/e2e/08-roomactions.spec.js index a6b939e48..a15ca65e0 100644 --- a/e2e/08-roomactions.spec.js +++ b/e2e/08-roomactions.spec.js @@ -14,7 +14,7 @@ async function navigateToRoomActions(type) { } else { room = `private${ data.random }`; } - await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toExist().withTimeout(2000); await element(by.id(`rooms-list-view-item-${ room }`)).tap(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); await element(by.id('room-view-header-actions')).tap(); @@ -210,8 +210,9 @@ describe('Room actions screen', () => { it('should show mentioned messages', async() => { await element(by.id('room-actions-mentioned')).tap(); await waitFor(element(by.id('mentioned-messages-view'))).toExist().withTimeout(2000); - await waitFor(element(by.text(`@${ data.user } test`).withAncestor(by.id('mentioned-messages-view')))).toBeVisible().withTimeout(60000); - await expect(element(by.text(`@${ data.user } test`).withAncestor(by.id('mentioned-messages-view')))).toBeVisible(); + await expect(element(by.id('mentioned-messages-view'))).toExist(); + // await waitFor(element(by.text(` ${ data.random }mention`))).toBeVisible().withTimeout(60000); + // await expect(element(by.text(` ${ data.random }mention`))).toBeVisible(); await backToActions(); }); @@ -233,14 +234,14 @@ describe('Room actions screen', () => { await waitFor(element(by.id('room-actions-pinned'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down'); await element(by.id('room-actions-pinned')).tap(); await waitFor(element(by.id('pinned-messages-view'))).toExist().withTimeout(2000); - await waitFor(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeVisible().withTimeout(60000); - await expect(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view')))).toBeVisible(); - await element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).longPress(); + await waitFor(element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeVisible().withTimeout(60000); + await expect(element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view')))).toBeVisible(); + await element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view'))).longPress(); await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000); await expect(element(by.text('Unpin'))).toBeVisible(); await element(by.text('Unpin')).tap(); - await waitFor(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeNotVisible().withTimeout(60000); - await expect(element(by.text(`${ data.random }edited`).withAncestor(by.id('pinned-messages-view')))).toBeNotVisible(); + await waitFor(element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view'))).atIndex(0)).toBeNotVisible().withTimeout(60000); + await expect(element(by.text(`${ data.random }edited (edited)`).withAncestor(by.id('pinned-messages-view')))).toBeNotVisible(); await backToActions(); }); diff --git a/e2e/09-roominfo.spec.js b/e2e/09-roominfo.spec.js index 0638d0123..4d22f0d76 100644 --- a/e2e/09-roominfo.spec.js +++ b/e2e/09-roominfo.spec.js @@ -12,6 +12,7 @@ async function navigateToRoomInfo(type) { } else { room = `private${ data.random }`; } + await element(by.id('rooms-list-view-search')).replaceText(room); await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeVisible().withTimeout(2000); await element(by.id(`rooms-list-view-item-${ room }`)).tap(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); @@ -310,6 +311,7 @@ describe('Room info screen', () => { await expect(element(by.text('Yes, delete it!'))).toBeVisible(); await element(by.text('Yes, delete it!')).tap(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await element(by.id('rooms-list-view-search')).replaceText(''); await waitFor(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible().withTimeout(60000); await expect(element(by.id(`rooms-list-view-item-${ room }`))).toBeNotVisible(); }); diff --git a/e2e/11-broadcast.spec.js b/e2e/11-broadcast.spec.js index 582cb8935..de4383c4b 100644 --- a/e2e/11-broadcast.spec.js +++ b/e2e/11-broadcast.spec.js @@ -36,14 +36,14 @@ describe('Broadcast room', () => { await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); await tapBack(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); - await tapBack(2); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000); - await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist(); + // await tapBack(2); + // await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + // await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000); + // await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist(); }); it('should send message', async() => { - await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap(); + // await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); await element(by.id('messagebox-input')).tap(); await element(by.id('messagebox-input')).typeText(`${ data.random }message`); diff --git a/e2e/data.js b/e2e/data.js index 19257a80f..8cdd99e0e 100644 --- a/e2e/data.js +++ b/e2e/data.js @@ -2,7 +2,7 @@ const random = require('./helpers/random'); const value = random(20); const data = { server: 'https://stable.rocket.chat', - alternateServer: 'https://open.rocket.chat', + alternateServer: 'https://unstable.rocket.chat', user: `user${ value }`, password: `password${ value }`, alternateUser: 'detox', diff --git a/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/Contents.json b/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/Contents.json new file mode 100644 index 000000000..ecf2c0cd8 --- /dev/null +++ b/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "pause.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "pause@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "pause@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause.png b/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause.png new file mode 100644 index 000000000..b13411a50 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause@2x.png b/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause@2x.png new file mode 100644 index 000000000..962f15c4c Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause@2x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause@3x.png b/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause@3x.png new file mode 100644 index 000000000..e2ba5b563 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/pause.imageset/pause@3x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/Contents.json b/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/Contents.json new file mode 100644 index 000000000..db7ffa04f --- /dev/null +++ b/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "play.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "play@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "play@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play.png b/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play.png new file mode 100644 index 000000000..b40c8f3cc Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play@2x.png b/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play@2x.png new file mode 100644 index 000000000..c38804865 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play@2x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play@3x.png b/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play@3x.png new file mode 100644 index 000000000..2e60e189a Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/play.imageset/play@3x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/Contents.json b/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/Contents.json new file mode 100644 index 000000000..fe5012b98 --- /dev/null +++ b/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "play_video@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "play_video@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "play_video@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@1x.png b/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@1x.png new file mode 100644 index 000000000..83fc41316 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@1x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@2x.png b/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@2x.png new file mode 100644 index 000000000..a8958ac26 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@2x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@3x.png b/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@3x.png new file mode 100644 index 000000000..def52c709 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/play_video.imageset/play_video@3x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/Contents.json b/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/Contents.json new file mode 100644 index 000000000..dfbbac4ff --- /dev/null +++ b/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "reply_icon.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "reply_icon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "reply_icon@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon.png b/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon.png new file mode 100644 index 000000000..4d791de4a Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon@2x.png b/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon@2x.png new file mode 100644 index 000000000..b5d4a0043 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon@2x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon@3x.png b/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon@3x.png new file mode 100644 index 000000000..5ceef53aa Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/reply.imageset/reply_icon@3x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/Contents.json b/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/Contents.json new file mode 100644 index 000000000..4e17e7f83 --- /dev/null +++ b/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "add_reaction@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "add_reaction@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "add_reaction@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@1x.png b/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@1x.png new file mode 100644 index 000000000..fd4ef8bda Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@1x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@2x.png b/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@2x.png new file mode 100644 index 000000000..1c2825de8 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@2x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@3x.png b/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@3x.png new file mode 100644 index 000000000..d76bb4295 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/add_reaction.imageset/add_reaction@3x.png differ diff --git a/ios/RocketChatRN/Info.plist b/ios/RocketChatRN/Info.plist index 0fe91a1d2..acd16e986 100644 --- a/ios/RocketChatRN/Info.plist +++ b/ios/RocketChatRN/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.1 + 1.2 CFBundleSignature ???? CFBundleURLTypes diff --git a/package.json b/package.json index ffc2fed13..f0f2ca9a6 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,12 @@ "react-test-renderer": "^16.4.2", "reactotron-react-native": "^2.0.0", "reactotron-redux": "^2.0.0", - "reactotron-redux-saga": "^2.0.0" + "reactotron-redux-saga": "^2.0.0", + "@storybook/react-native": "^3.4.10", + "@storybook/addon-actions": "^3.4.10", + "@storybook/addon-links": "^3.4.10", + "@storybook/addons": "^3.4.10", + "babel-runtime": "^6.26.0" }, "jest": { "testPathIgnorePatterns": [ @@ -114,7 +119,8 @@ "coverageDirectory": "./coverage/", "collectCoverage": true, "moduleNameMapper": { - ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "identity-obj-proxy" + ".+\\.(css|styl|less|sass|scss)$": "identity-obj-proxy", + ".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/fileMock.js" }, "transform": { "^.+\\.js$": "/node_modules/react-native/jest/preprocessor.js" diff --git a/storybook/index.js b/storybook/index.js new file mode 100644 index 000000000..f0513c5a8 --- /dev/null +++ b/storybook/index.js @@ -0,0 +1,3 @@ +import StorybookUI from './storybook'; + +export default StorybookUI; diff --git a/storybook/stories/Avatar.js b/storybook/stories/Avatar.js index 2f0e0de66..45f5c4d6c 100644 --- a/storybook/stories/Avatar.js +++ b/storybook/stories/Avatar.js @@ -7,15 +7,15 @@ import Avatar from '../../app/containers/Avatar'; const reducers = combineReducers({ settings: () => ({}) }); const store = createStore(reducers); - +const baseUrl = 'baseUrl'; export default ( - - - - + + + + ); diff --git a/storybook/stories/Message.js b/storybook/stories/Message.js new file mode 100644 index 000000000..4984e088f --- /dev/null +++ b/storybook/stories/Message.js @@ -0,0 +1,428 @@ +import React from 'react'; +import { ScrollView, StyleSheet } from 'react-native'; + +import MessageComponent from '../../app/containers/message/Message'; +import StoriesSeparator from './StoriesSeparator'; +import messagesStatus from '../../app/constants/messagesStatus'; +import MessageSeparator from '../../app/views/RoomView/Separator'; + +const styles = StyleSheet.create({ + separator: { + transform: [{ scaleY: -1 }], + marginBottom: 30, + marginTop: 0 + } +}); + +const user = { + id: 'y8bd77ptZswPj3EW8', + username: 'diego.mello', + token: '79q6lH40W4ZRGLOshDiDiVlQaCc4f_lU9HNdHLAzuHz' +}; +const author = { + _id: 'userid', + username: 'diego.mello' +}; +const baseUrl = 'https://open.rocket.chat'; +const customEmojis = { react_rocket: 'png', nyan_rocket: 'png', marioparty: 'gif' }; +const date = new Date(2017, 10, 10, 10); + +const Message = props => ( + +); + +// eslint-disable-next-line react/prop-types +const Separator = ({ title }) => ; + +export default ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {}} + /> + + + {}} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + alert('broadcast!')} /> + + + + + + alert('Error pressed')} header={false} /> + alert('Error pressed')} /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +); diff --git a/storybook/stories/StoriesSeparator.js b/storybook/stories/StoriesSeparator.js new file mode 100644 index 000000000..3ab4cea79 --- /dev/null +++ b/storybook/stories/StoriesSeparator.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { Text, StyleSheet } from 'react-native'; +import PropTypes from 'prop-types'; + +const styles = StyleSheet.create({ + separator: { + marginTop: 30, + marginLeft: 10, + fontSize: 20, + fontWeight: '300' + } +}); + +const Separator = ({ title, style }) => {title}; + +Separator.propTypes = { + title: PropTypes.string.isRequired, + style: PropTypes.object +}; + +export default Separator; diff --git a/storybook/stories/index.js b/storybook/stories/index.js index 09631e178..6454659c7 100644 --- a/storybook/stories/index.js +++ b/storybook/stories/index.js @@ -7,28 +7,15 @@ import { createStore, combineReducers } from 'redux'; import { storiesOf } from '@storybook/react-native'; -// import { action } from '@storybook/addon-actions'; -// import { linkTo } from '@storybook/addon-links'; import DirectMessage from './Channels/DirectMessage'; import Avatar from './Avatar'; +import Message from './Message'; const reducers = combineReducers({ settings: () => ({}), login: () => ({ user: {} }), meteor: () => ({ connected: true }) }); const store = createStore(reducers); storiesOf('Avatar', module).addDecorator(story => {story()}).add('avatar', () => Avatar); storiesOf('Channel Cell', module).addDecorator(story => {story()}).add('Direct Messages', () => DirectMessage); - -// storiesOf('Welcome', module).add('to Storybook', () => ); - -// storiesOf('Button', module) -// .addDecorator(getStory => ( -// -// {getStory()} -// -// )) -// .add('with text', () => ( -// -// )) +storiesOf('Message', module) + .add('list', () => Message); diff --git a/storybook/storybook.js b/storybook/storybook.js index 841d13edd..fc8fdc828 100644 --- a/storybook/storybook.js +++ b/storybook/storybook.js @@ -1,4 +1,5 @@ -import { AppRegistry } from 'react-native'; +import React, { Component } from 'react'; +import { Navigation } from 'react-native-navigation'; import { getStorybookUI, configure } from '@storybook/react-native'; // import stories @@ -8,7 +9,23 @@ configure(() => { // This assumes that storybook is running on the same host as your RN packager, // to set manually use, e.g. host: 'localhost' option -const StorybookUI = getStorybookUI({ port: 7007, onDeviceUI: true }); -AppRegistry.registerComponent('RocketChatRN', () => StorybookUI); +const StorybookUIRoot = getStorybookUI({ port: 7007, onDeviceUI: true }); -export default StorybookUI; +// react-native hot module loader must take in a Class - https://github.com/facebook/react-native/issues/10991 +// https://github.com/storybooks/storybook/issues/2081 +// eslint-disable-next-line react/prefer-stateless-function +class StorybookUIHMRRoot extends Component { + render() { + return ; + } +} + +Navigation.registerComponent('storybook.UI', () => StorybookUIHMRRoot); +Navigation.startSingleScreenApp({ + screen: { + screen: 'storybook.UI', + title: 'Storybook' + } +}); + +export default StorybookUIHMRRoot;