From de1a63c8159918aaa067ce4d0092997659f42960 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Fri, 31 Aug 2018 15:13:30 -0300 Subject: [PATCH] [NEW] Create channel layout (#420) * RoomsListView layout * Rooms list layout * Sort component * Header icons * Default header colors * Add server dropdown * Close sort dropdown if server dropdown will open * UserItem * Room type icon * Search working * Tests updated * Android layout * Using realm queries instead of array iterates * Animation duration * Fixed render bug * - NewMessageView - backButtonTitle always empty - SearchBox created * New create channel layout * Search refactored * loginSuccess dismiss modal * Tests working --- .../app/src/main/res/drawable-hdpi/plus.png | Bin 0 -> 402 bytes .../res/drawable-hdpi/textinput_search.png | Bin 0 -> 747 bytes .../app/src/main/res/drawable-mdpi/plus.png | Bin 0 -> 276 bytes .../res/drawable-mdpi/textinput_search.png | Bin 0 -> 458 bytes .../app/src/main/res/drawable-xhdpi/plus.png | Bin 0 -> 451 bytes .../res/drawable-xhdpi/textinput_search.png | Bin 0 -> 959 bytes .../app/src/main/res/drawable-xxhdpi/plus.png | Bin 0 -> 621 bytes .../res/drawable-xxhdpi/textinput_search.png | Bin 0 -> 1544 bytes .../src/main/res/drawable-xxxhdpi/plus.png | Bin 0 -> 900 bytes .../res/drawable-xxxhdpi/textinput_search.png | Bin 0 -> 2011 bytes app/actions/actionsTypes.js | 4 +- app/actions/server.js | 12 + app/constants/colors.js | 2 +- app/containers/SearchBox.js | 62 +++++ app/containers/Sidebar.js | 1 + app/i18n/locales/en.js | 7 +- app/lib/rocketchat.js | 50 ++++ app/presentation/UserItem.js | 24 +- app/reducers/server.js | 18 +- app/sagas/createChannel.js | 9 +- app/sagas/deepLinking.js | 1 + app/sagas/login.js | 10 +- app/sagas/messages.js | 1 + app/sagas/selectServer.js | 2 +- app/views/CreateChannelView.js | 230 +++++++++++++----- app/views/LoginSignupView.js | 4 +- app/views/LoginView.js | 4 +- app/views/NewMessageView.js | 166 +++++++++++++ app/views/NewServerView.js | 63 +++-- app/views/OAuthView.js | 2 +- app/views/OnboardingView/index.js | 2 + app/views/ProfileView/index.js | 6 +- app/views/RegisterView.js | 4 +- app/views/RoomActionsView/index.js | 3 +- app/views/RoomInfoView/index.js | 1 + app/views/RoomMembersView/index.js | 19 +- app/views/RoomView/index.js | 1 + app/views/RoomsListView/ServerDropdown.js | 8 + app/views/RoomsListView/index.js | 94 ++----- app/views/RoomsListView/styles.js | 11 - app/views/SelectedUsersView.js | 199 ++++++--------- app/views/SettingsView/index.js | 6 +- app/views/Styles.js | 19 +- app/views/index.js | 2 + e2e/01-welcome.spec.js | 4 +- e2e/04-login.spec.js | 6 +- e2e/05-roomslist.spec.js | 17 +- e2e/06-createroom.spec.js | 174 ++++++++----- e2e/07-room.spec.js | 7 +- e2e/08-roomactions.spec.js | 25 +- e2e/09-roominfo.spec.js | 8 +- e2e/11-broadcast.spec.js | 19 +- e2e/helpers/app.js | 8 +- .../Icons/plus.imageset/Contents.json | 23 ++ .../Icons/plus.imageset/plus.png | Bin 0 -> 276 bytes .../Icons/plus.imageset/plus@2x.png | Bin 0 -> 451 bytes .../Icons/plus.imageset/plus@3x.png | Bin 0 -> 621 bytes .../textinput_search.imageset/Contents.json | 23 ++ .../textinput_search.png | Bin 0 -> 458 bytes .../textinput_search@2x.png | Bin 0 -> 959 bytes .../textinput_search@3x.png | Bin 0 -> 1544 bytes package-lock.json | 2 +- 62 files changed, 893 insertions(+), 470 deletions(-) create mode 100644 android/app/src/main/res/drawable-hdpi/plus.png create mode 100644 android/app/src/main/res/drawable-hdpi/textinput_search.png create mode 100644 android/app/src/main/res/drawable-mdpi/plus.png create mode 100644 android/app/src/main/res/drawable-mdpi/textinput_search.png create mode 100644 android/app/src/main/res/drawable-xhdpi/plus.png create mode 100644 android/app/src/main/res/drawable-xhdpi/textinput_search.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/plus.png create mode 100644 android/app/src/main/res/drawable-xxhdpi/textinput_search.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/plus.png create mode 100644 android/app/src/main/res/drawable-xxxhdpi/textinput_search.png create mode 100644 app/containers/SearchBox.js create mode 100644 app/views/NewMessageView.js create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/Contents.json create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus.png create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus@2x.png create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus@3x.png create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/Contents.json create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search.png create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search@2x.png create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search@3x.png diff --git a/android/app/src/main/res/drawable-hdpi/plus.png b/android/app/src/main/res/drawable-hdpi/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..238a8cc9a05e6dedfb026729cceaf2044b793928 GIT binary patch literal 402 zcmV;D0d4+?P)EqVM)qnAyNvRK$cAA>%YGZWC3`rB7xnmI%)UR@qfHK#un)m zwDmojeAqfDikQ0s98|m=dwN~#DqcwLVi9&t4HYQAbSIZ wM>`Mqv-c2zh4_nt5;FfQ3u(qpm_UHQ4~8sHt-}5v`Tzg`07*qoM6N<$f>(pBYybcN literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-hdpi/textinput_search.png b/android/app/src/main/res/drawable-hdpi/textinput_search.png new file mode 100644 index 0000000000000000000000000000000000000000..274376fd04eba5c3cc100dfe32116d9706aefd68 GIT binary patch literal 747 zcmVO=uHA6rP`5k`$rfp$Dy4i$y#LLG9nww|TCNp^+c!5~%eAXLPEB?rBDDO9Oqr3XO`c(sH^HZy(`yCF`}2B`zf?0fHf z-@N(e%}}BZ*L8RLKDj6*874#yVh#~PzXA4|b8;u09)G2!w3#&UNGYjpdsCFkNkv2{ z`HI+kxK}|wKq-wX48{vW%+YMtssy?E+#pq8$8qn0>@=$S!kDx&ndGxpa6W&tzg}O> z0dq=KZW!j_RLWXz#RUHi(wP}|8r56@_P@+NGVG2FZJ}82Q15~ z>KLXpR9jXRhC=lGMuaLklNp=EbRE0fTdn@e>V(Q>Yljk2TTUbrE1f{PkMqH+(DoBl zOM#r}aVCU3st!fhPzSc!snoF=w7m_<`f@q%U~5eMGZ5~=itqG5H=A=h{|Cc{bSwj5 z7@DGO{%r`s8i$EP3+tJvRHCuzfF9^(Q!4ruhI95=$3kTK24Y7Gg~B#HxRH$<+&EkV zlYZ23YWsKLL>Ew753Xn1w!0ssBj|t!@p$})j$;!OiRY-c4A2?J@y_Z1-OlG9hj8*G z5OPWxE=>Dq^jIXq&!Gz{LWo)0E?g@Xi)K5N5WEdh-(P-4V<*z-#M1!(7ir)um&?Y&!r~!Ry&F}BQRR|h z)ITJX$?pNiaSBJ)Bko8VBzBu|MTc^jjndlj3O>QWXDXU`S{94}=U1%Zb&;M65z`)s0#} zFyw}Q5HTYAQLg-_Hp9Qa0?1;>?0^3mCIdD6XJll^Ll#G2GckX7)X9DoCI+`azUsf^ zzhB>;!6Yc5nHjkxA9ORm0GR=|0K}0k`)b1Yj~S;A{xkgRVfg=_fsv891f(7t_|NqB z!{aXQGuY$^ipiFKJ0M%~Z9hSUND792DjM|xQgmWx(maSrs&IhWW&tA;xWd8i+)+_- aLI42gHZCTC)xQG(00004#8KS)AR6vofFuil_ji<6r<6txARw6{f*MA&SgsRT8&4hU^a!EjrX@gHNh^Z|77OE*9Y%-ud|M`JM0H`yRs2Q>|7*zW=d_ zh;gkU0-$dJ@H7}4-cF@b9n)v3VBvYCS>KmUf`R~I!*yYbpdCaV>2}|)J+C}%08{Zh zNj4n^R&u#a-7J{ud6kUTYRz#*CbQY(tMNszCku#h{1?Pc`Fy%e=~q&A)(ym!w;B@y ze6YzNBA#oFOSajs zY4{=~p4h&iFR(?^V#Q)<*V3?_@&Et;07*qoM6N<$f&ePc A)&Kwi literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xhdpi/plus.png b/android/app/src/main/res/drawable-xhdpi/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..251c8d181cc70c57bf8c2068c0e8fe695bca9870 GIT binary patch literal 451 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTCmUKs7M+SzC{oH>NSs54@t2|vC zLo!(3Mq2wFb`Y2wsiGir=oyR1g^41XmlPf_&1p`snq+mSSzE!CLs6A|!GiuB&UUOH z4{I;6h|EFA@JwY^VUzhvIFJcEY zVv}S_#m)Eqs)%lI5dV3>;{4kz-Fogd=Q$shZuhc2!xZY5b7c}Mk5kj`MGgh0dfOH? zIC>O>i*Weuyci&2z`^;hi|L@Ixl~@QchBUbq7&vx{yZAHfH}aOdB)WV68T3D&YsEj zs@TY)?rzV&%4@8C^3UHs_5YQ}d_~kc;p#jdDfzVe-^L7fM#8VZ1wV7X%@f92l2l{A zpLyXQL8Yv;AJf(yJ+kF&cSYhyV^>bP0l+XkKqf@le literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xhdpi/textinput_search.png b/android/app/src/main/res/drawable-xhdpi/textinput_search.png new file mode 100644 index 0000000000000000000000000000000000000000..a8d16731036c8544af8aa30e3f2f98458f8e6e8b GIT binary patch literal 959 zcmV;w13>(VP)b#N=31PMp z?ds~vo}FE22HqfqFt4=7zO!GPvzGk)t?sI-sz9tQ@mq|b!{OMqv?QKGXfM%`*$@2o zBD43Z%6%L+{B(L@+Ei0P8qFhycOn zbTf+%XtSMr3DKK`38A-kcXww-+oR_^)cV3q4Pr8Bu4vu+RQ&z;_ZA8X|ARtdvFJ$c#2M$|>H-Na^C}gEz#*bHB`onkFwq(rBOk z*WjG?ZE)nv0#QlBW%OsrNa^Cnc0f#NEGlU@Gqy9`z%rh$E5XbVb3(>JNyX=|QQBED z=XhKk31c7QZ6So?28u4(KAl>nWUJ2SIzNk&YK z|7n609dF}>h1-lI;@r5^YMsX^aV=yKO?JEESR5+_Kt5RT1)`y1t+E3@Xs4gz;(fP~ z$?44T1#=%}h%N@dqnM35YuPG_#qr}yAngfnDwIDrPx1aBEr zzjLl*J*)sCM?o(K&$xiSFhu@4NG2L1NTN9DRgY)!(5>f2LjWtQi)0h zDadLEiTeG&2jO?X@LiednhKIgB2mC2nwDEejkM;1sYKUBP$r7!lvNA9*sd&0L zhEy=Vy}j3q*-?b`L3O&Lk5a@9&VLFotZyvcc;Qz2hE2v%%Y; zG-2YAom2Ja#2I?Wd@tRZa8qHrJEKX4>NHQ0yRy0$6t)!h2y(0t?r?BmIPyk;iIGuY zb2A4Ei^CZ{B>@2jLu*133?`S(9jtj=8SG>Bx6Ft4%J0Y=qhzJkKf6P}L|u{loc!?I z*2m&?RrgoTTR2M~Bk_5u^5%DzS|P`5E~eP#`o{6hwyrtivw3SG>!~@to6LApih|Qx z5{tJBUSpHrc#rpwb;fV=`;PzQt(N`yeKz0zIsfj^tIPj*>?nTwBWn5Eu&)wVWjHxR zHD~F++k0hq{>{{Vev8)4+ol!%b7SG0!|pjUle=EdV_~Z2WfJ7zXqX}EqM*Q#R3^~a zz|e8ik%fs#A&s4YL`axIY29MYCgo{-UcYs>e82iUKl$(ekKy*Nj8o)7f<&*2e7#@& zO@4pkjXlX!1Ss*4CHzmYWDabJ2vvpU46R)_>~{SXcawt*<{FsR&Fz44$rj JF6*2UngArW_jUjP literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/textinput_search.png b/android/app/src/main/res/drawable-xxhdpi/textinput_search.png new file mode 100644 index 0000000000000000000000000000000000000000..1c9db2d094d48f7ad826836ebd8b5a78421251af GIT binary patch literal 1544 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 Ib4q9e01V%O-v9sr literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxxhdpi/textinput_search.png b/android/app/src/main/res/drawable-xxxhdpi/textinput_search.png new file mode 100644 index 0000000000000000000000000000000000000000..363ff5db6a52ab5a443825afc11bdfa3cd3299fb GIT binary patch literal 2011 zcmV<12PF83P)KQK|jXA~InaFQ_#bZhVNce=Ja z_q1K_X}gu4Wb669^F7Y*p6`6;aW7G_D7$v;DmruK;zO#cSL!;sjSyu5%uS?}j>qp+ zMIoK|#UJfdRrQ3;rnJ}9)%})*OKDXis{v4!&d!Tf0I?LhkD~J>GByYyKT@i+S*_Nc zPUkB>$>?SxD+REr$@iqLEAIl_-I+KObaa6IxTuJ%#;Bea+Hyvk0_gQ_oD~j-y#RJ! z&OFBrk!yrduibv*+Un}+o;W*4m9zleZvQex(OVE!#i@K^B(9+CF#b+s9-hO4I0xPh zl#+6Ua5?BHXq%o&pYd7fDvG*T(`x@1Ois2fB|tXU&YnHL4k4+fFd9Iowgb?_WsiZD`zyFrrUVRB36q~#Al1ai|m&KwkbvkRmPS&0YFDbzOL~@}GPkxbT6rJb; ze{*rMx(0LSoY-!L_IP}A0rUgdNeFpnUsdTcm&>ukjIDGsNddauJ~x~vY!qF1BStBmAF>hQD3V@5M;Yce(1m5%|(%0zfwBDAhj_Eg=+lmY0vOP8W)zUOL+A zZJ8Pl_wB`q-eHs_7w|A=x?DAh7Kks6tcFlv3|9jY4g%PpKnZWFZ~P} zvw1B<%<N z>o|w%m3#}H)D^pZM zZvlOs%+qG-44{ym$SE_5LoI_yqVQo@__1%r@k_3#Fk}D-z!{woR!t8u0 zS1^K+3!=D|s*=2A&v+-I6=e6bJP&jkPn)S@1eG8!2*5==ozFU=OBejx1UfB=g(bjCtP?T z{~eO;l&WcUCy;1<;T18tpW^BK)QRCCBV2#gv>N#kWu3>SVUH06S>W;bXBv6KN4eb_ zA4VjJ{&!^30~Vqv>dxraC_=F?)pdPcgvi4`N<;4nZbMe9vPs~XA|n71rpF<@ThN&2 z@igNiXm|^JJ_C=lc&~W)dmIjjVOPVq4_1$kprYXZ}ty@cY{!rAJ4_r&M!{^OL`^4>Xt@y#n zGf%|X(()G|$_QC|6A?97AmR!HyhqX@smPJW2Dey;O!T%h~#skUP&G1ro6zgCO z9Qg5P5e*?39mA-;rfH6^#5OZ@e}}{G!}fz_DPDroxrRm)1bqF6Twf^*a!`QG zFE+h*B24xHWSaTMUYyWBqvy*`0AY736F}p$5@izF(p(r3rc(mhL9PbnM=~uy29n)J z>iVUXcz@vnIfC(WSlHJVe!G3dsz4w(4R5#(!mi z%r8J;gz0{Od9$(6fjQk@yxtA7a|u!kV5}3-`SdiKPvFhMoRY9Z*sa}m%-fBrUhd=) zq(sMJ@QlCB&CO+@5P2M2Y_r&PN91a+9BZi+JX|r*f52nMoDO`-br3H*4M#JhwE-$u zAhQNI(52kh%!0I7NM))I7Orc6?2%;)^126@Ey%1gVeW%6xkY^WbOiIbGI5X0%hQr@ znC@?C^54&M3t|Lg9mWSrbD~Sjpql}HP8JkE22>Vg!2yhc#0KF2MstvZIO(z3il-MO zK(ipRTWT*@fM!4f;FW^Nq6|#3qG|OV$gB&IVSAFbgLk^1M=%D8C7xt+5?_l{74!(k t0J4#cBov~s?`zoS)h8Lah6cy({{f>f2<0Xp+0+04002ovPDHLkV1k4})Ybq1 literal 0 HcmV?d00001 diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 49bd9cea..5b8f16c9 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -84,7 +84,9 @@ export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']); export const SERVER = createRequestTypes('SERVER', [ ...defaultTypes, 'SELECT_SUCCESS', - 'SELECT_REQUEST' + 'SELECT_REQUEST', + 'INIT_ADD', + 'FINISH_ADD' ]); export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']); export const LOGOUT = 'LOGOUT'; // logout is always success diff --git a/app/actions/server.js b/app/actions/server.js index 51d1baff..a6bab715 100644 --- a/app/actions/server.js +++ b/app/actions/server.js @@ -33,3 +33,15 @@ export function serverFailure(err) { err }; } + +export function serverInitAdd() { + return { + type: SERVER.INIT_ADD + }; +} + +export function serverFinishAdd() { + return { + type: SERVER.FINISH_ADD + }; +} diff --git a/app/constants/colors.js b/app/constants/colors.js index 47c587eb..5a603742 100644 --- a/app/constants/colors.js +++ b/app/constants/colors.js @@ -1,8 +1,8 @@ export const AVATAR_COLORS = ['#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFC107', '#FF9800', '#FF5722', '#795548', '#9E9E9E', '#607D8B']; -export const ESLINT_FIX = null; export const COLOR_DANGER = '#f5455c'; export const COLOR_BUTTON_PRIMARY = '#2D6AEA'; export const COLOR_TEXT = '#292E35'; +export const COLOR_SEPARATOR = '#CBCED1'; export const STATUS_COLORS = { online: '#2de0a5', busy: COLOR_DANGER, diff --git a/app/containers/SearchBox.js b/app/containers/SearchBox.js new file mode 100644 index 00000000..5135ba19 --- /dev/null +++ b/app/containers/SearchBox.js @@ -0,0 +1,62 @@ +import React from 'react'; +import { View, StyleSheet, Image, TextInput, Platform } from 'react-native'; +import PropTypes from 'prop-types'; + +import I18n from '../i18n'; + +const styles = StyleSheet.create({ + container: { + backgroundColor: Platform.OS === 'ios' ? '#F7F8FA' : '#54585E' + }, + searchBox: { + alignItems: 'center', + backgroundColor: '#E1E5E8', + borderRadius: 10, + color: '#8E8E93', + flexDirection: 'row', + fontSize: 17, + height: 36, + margin: 16, + marginVertical: 10, + paddingHorizontal: 10 + }, + icon: { + width: 14, + height: 14 + }, + input: { + color: '#8E8E93', + flex: 1, + fontSize: 17, + marginLeft: 8, + paddingTop: 0, + paddingBottom: 0 + } +}); + +const SearchBox = ({ onChangeText, testID }) => ( + + + + + + +); + +SearchBox.propTypes = { + onChangeText: PropTypes.func.isRequired, + testID: PropTypes.string +}; + +export default SearchBox; diff --git a/app/containers/Sidebar.js b/app/containers/Sidebar.js index 0cbf8aa3..382939ae 100644 --- a/app/containers/Sidebar.js +++ b/app/containers/Sidebar.js @@ -236,6 +236,7 @@ export default class Sidebar extends Component { setTimeout(() => { NavigationActions.push({ screen: 'NewServerView', + backButtonTitle: '', passProps: { server: item.id }, diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js index 44261479..8af6fa33 100644 --- a/app/i18n/locales/en.js +++ b/app/i18n/locales/en.js @@ -1,6 +1,7 @@ export default { '1_online_member': '1 online member', '1_person_reacted': '1 person reacted', + '1_user': '1 user', 'error-action-not-allowed': '{{action}} is not allowed', 'error-application-not-found': 'Application not found', 'error-archived-duplicate-name': 'There\'s an archived channel with name {{room_name}}', @@ -110,6 +111,7 @@ export default { Cancel_recording: 'Cancel recording', Cancel: 'Cancel', changing_avatar: 'changing avatar', + creating_channel: 'creating channel', Channel_Name: 'Channel Name', Channels: 'Channels', Chats: 'Chats', @@ -160,6 +162,7 @@ export default { Has_left_the_channel: 'Has left the channel', I_have_an_account: 'I have an account', Invisible: 'Invisible', + Invite: 'Invite', is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance', is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance', is_typing: 'is typing', @@ -188,13 +191,15 @@ export default { muted: 'muted', My_servers: 'My servers', N_online_members: '{{n}} online members', - N_person_reacted: '{{n}} people reacted', + N_people_reacted: '{{n}} people reacted', + N_users: '{{n}} users', name: 'name', Name: 'Name', New_in_RocketChat_question_mark: 'New in Rocket.Chat?', New_Message: 'New Message', New_Password: 'New Password', New_Server: 'New Server', + Next: 'Next', No_files: 'No files', No_mentioned_messages: 'No mentioned messages', No_pinned_messages: 'No pinned messages', diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index ed2b0289..711d5d99 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -528,6 +528,56 @@ const RocketChat = { return _sendMessageCall(JSON.parse(JSON.stringify(message))); }, + async search({ text, filterUsers = true, filterRooms = true }) { + const searchText = text.trim(); + if (searchText === '') { + delete this.oldPromise; + return []; + } + + let data = database.objects('subscriptions').filtered('name CONTAINS[c] $0', searchText); + + if (filterUsers && !filterRooms) { + data = data.filtered('t = $0', 'd'); + } else if (!filterUsers && filterRooms) { + data = data.filtered('t != $0', 'd'); + } + data = data.slice(0, 7); + + const usernames = data.map(sub => sub.name); + try { + if (data.length < 7) { + if (this.oldPromise) { + this.oldPromise('cancel'); + } + + const { users, rooms } = await Promise.race([ + RocketChat.spotlight(searchText, usernames, { users: filterUsers, rooms: filterRooms }), + new Promise((resolve, reject) => this.oldPromise = reject) + ]); + + data = data.concat(users.map(user => ({ + ...user, + rid: user.username, + name: user.username, + t: 'd', + search: true + })), rooms.map(room => ({ + rid: room._id, + ...room, + search: true + }))); + + delete this.oldPromise; + } + + return data; + } catch (e) { + console.warn(e); + return []; + } + }, + spotlight(search, usernames, type) { return call('spotlight', search, usernames, type); }, diff --git a/app/presentation/UserItem.js b/app/presentation/UserItem.js index cf8c07de..f4e03dbd 100644 --- a/app/presentation/UserItem.js +++ b/app/presentation/UserItem.js @@ -1,5 +1,5 @@ import React from 'react'; -import { Text, View, StyleSheet, Platform } from 'react-native'; +import { Text, View, StyleSheet, Platform, ViewPropTypes, Image } from 'react-native'; import PropTypes from 'prop-types'; import Avatar from '../containers/Avatar'; @@ -7,7 +7,8 @@ import Touch from '../utils/touch'; const styles = StyleSheet.create({ button: { - height: 54 + height: 54, + backgroundColor: '#fff' }, container: { flexDirection: 'row' @@ -24,24 +25,33 @@ const styles = StyleSheet.create({ fontSize: 18, color: '#0C0D0F', marginTop: Platform.OS === 'ios' ? 6 : 3, - marginBottom: 1 + marginBottom: 1, + textAlign: 'left' }, username: { fontSize: 14, color: '#9EA2A8' + }, + icon: { + width: 20, + height: 20, + marginHorizontal: 15, + resizeMode: 'contain', + alignSelf: 'center' } }); const UserItem = ({ - name, username, onPress, testID, onLongPress + name, username, onPress, testID, onLongPress, style, icon }) => ( - + {name} @{username} + {icon ? : null} ); @@ -51,7 +61,9 @@ UserItem.propTypes = { username: PropTypes.string.isRequired, onPress: PropTypes.func.isRequired, testID: PropTypes.string.isRequired, - onLongPress: PropTypes.func + onLongPress: PropTypes.func, + style: ViewPropTypes.style, + icon: PropTypes.string }; export default UserItem; diff --git a/app/reducers/server.js b/app/reducers/server.js index db6d5911..a423033e 100644 --- a/app/reducers/server.js +++ b/app/reducers/server.js @@ -6,7 +6,7 @@ const initialState = { failure: false, server: '', loading: true, - adding: true + adding: false }; @@ -16,16 +16,14 @@ export default function server(state = initialState, action) { return { ...state, connecting: true, - failure: false, - adding: true + failure: false }; case SERVER.FAILURE: return { ...state, connecting: false, connected: false, - failure: true, - adding: false + failure: true }; case SERVER.SELECT_REQUEST: return { @@ -43,6 +41,16 @@ export default function server(state = initialState, action) { connected: true, loading: false }; + case SERVER.INIT_ADD: + return { + ...state, + adding: true + }; + case SERVER.FINISH_ADD: + return { + ...state, + adding: false + }; default: return state; } diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index 9f64e3b4..bd5ff2d9 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -12,25 +12,26 @@ const create = function* create(data) { const handleRequest = function* handleRequest({ data }) { try { - // yield delay(1000); const auth = yield select(state => state.login.isAuthenticated); if (!auth) { yield take(LOGIN.SUCCESS); } const result = yield call(create, data); + yield put(createChannelSuccess(result)); + yield delay(300); const { rid, name } = result; - NavigationActions.popToRoot(); - yield delay(1000); + NavigationActions.dismissModal(); + yield delay(600); NavigationActions.push({ screen: 'RoomView', title: name, + backButtonTitle: '', passProps: { room: { rid, name }, rid, name } }); - yield put(createChannelSuccess(result)); } catch (err) { yield put(createChannelFailure(err)); } diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index ce21d94b..8f3bf1d6 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -17,6 +17,7 @@ const navigate = function* go({ params, sameServer = true }) { if (canOpenRoom) { return NavigationActions.push({ screen: 'RoomView', + backButtonTitle: '', passProps: { rid: params.rid } diff --git a/app/sagas/login.js b/app/sagas/login.js index 12f1b01e..93c1ce86 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -4,6 +4,7 @@ import { put, call, take, takeLatest, select, all } from 'redux-saga/effects'; import * as types from '../actions/actionsTypes'; import { appStart } from '../actions'; +import { serverFinishAdd } from '../actions/server'; import { // loginRequest, // loginSubmit, @@ -38,13 +39,18 @@ const forgotPasswordCall = args => RocketChat.forgotPassword(args); const handleLoginSuccess = function* handleLoginSuccess() { try { const user = yield select(getUser); + const adding = yield select(state => state.server.adding); yield AsyncStorage.setItem(RocketChat.TOKEN_KEY, user.token); if (!user.username || user.isRegistering) { yield put(registerIncomplete()); } else { yield delay(300); - NavigationActions.dismissModal(); - yield put(appStart('inside')); + if (adding) { + NavigationActions.dismissModal(); + } else { + yield put(appStart('inside')); + } + yield put(serverFinishAdd()); } } catch (e) { log('handleLoginSuccess', e); diff --git a/app/sagas/messages.js b/app/sagas/messages.js index f959fd18..556b66a7 100644 --- a/app/sagas/messages.js +++ b/app/sagas/messages.js @@ -80,6 +80,7 @@ const goRoom = function* goRoom({ rid, name }) { yield delay(1000); NavigationActions.push({ screen: 'RoomView', + backButtonTitle: '', passProps: { room: { rid, name }, rid, diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 9a1e4f24..be132c44 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -44,7 +44,7 @@ const handleSelectServer = function* handleSelectServer({ server }) { const handleServerRequest = function* handleServerRequest({ server }) { try { yield call(validate, server); - yield call(NavigationActions.push, { screen: 'LoginSignupView', title: server }); + yield call(NavigationActions.push, { screen: 'LoginSignupView', title: server, backButtonTitle: '' }); database.databases.serversDB.write(() => { database.databases.serversDB.create('servers', { id: server, current: false }, true); }); diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js index 9f9a62ca..5ce8e870 100644 --- a/app/views/CreateChannelView.js +++ b/app/views/CreateChannelView.js @@ -1,29 +1,85 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { View, Text, Switch, SafeAreaView, ScrollView, Platform } from 'react-native'; +import { View, Text, Switch, SafeAreaView, ScrollView, TextInput, StyleSheet, FlatList, Platform } from 'react-native'; -import RCTextInput from '../containers/TextInput'; import Loading from '../containers/Loading'; import LoggedView from './View'; import { createChannelRequest } from '../actions/createChannel'; -import styles from './Styles'; +import { removeUser } from '../actions/selectedUsers'; +import sharedStyles from './Styles'; import KeyboardView from '../presentation/KeyboardView'; import scrollPersistTaps from '../utils/scrollPersistTaps'; -import Button from '../containers/Button'; import I18n from '../i18n'; +import UserItem from '../presentation/UserItem'; +import { showErrorAlert } from '../utils/info'; + +const styles = StyleSheet.create({ + container: { + backgroundColor: '#f7f8fa' + }, + list: { + width: '100%', + backgroundColor: '#FFFFFF' + }, + separator: { + marginLeft: 60 + }, + formSeparator: { + marginLeft: 15 + }, + input: { + height: 54, + paddingHorizontal: 18, + color: '#9EA2A8', + backgroundColor: '#fff', + fontSize: 18 + }, + swithContainer: { + height: 54, + backgroundColor: '#fff', + alignItems: 'center', + justifyContent: 'space-between', + flexDirection: 'row', + paddingHorizontal: 18 + }, + label: { + color: '#0C0D0F', + fontSize: 18, + fontWeight: '500' + }, + invitedHeader: { + marginTop: 18, + marginHorizontal: 15, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center' + }, + invitedTitle: { + color: '#2F343D', + fontSize: 22, + fontWeight: 'bold', + lineHeight: 41 + }, + invitedCount: { + color: '#9EA2A8', + fontSize: 15 + } +}); @connect(state => ({ createChannel: state.createChannel, users: state.selectedUsers.users }), dispatch => ({ - create: data => dispatch(createChannelRequest(data)) + create: data => dispatch(createChannelRequest(data)), + removeUser: user => dispatch(removeUser(user)) })) /** @extends React.Component */ export default class CreateChannelView extends LoggedView { static propTypes = { navigator: PropTypes.object, create: PropTypes.func.isRequired, + removeUser: PropTypes.func.isRequired, createChannel: PropTypes.object.isRequired, users: PropTypes.array.isRequired }; @@ -36,6 +92,7 @@ export default class CreateChannelView extends LoggedView { readOnly: false, broadcast: false }; + props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); } componentDidMount() { @@ -44,6 +101,36 @@ export default class CreateChannelView extends LoggedView { }, 600); } + componentDidUpdate(prevProps) { + if (this.props.createChannel.error && prevProps.createChannel.error !== this.props.createChannel.error) { + setTimeout(() => { + const msg = this.props.createChannel.error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') }); + showErrorAlert(msg); + }, 300); + } + } + + onChangeText = (channelName) => { + const rightButtons = []; + if (channelName.trim().length > 0) { + rightButtons.push({ + id: 'create', + title: 'Create', + testID: 'create-channel-submit' + }); + } + this.props.navigator.setButtons({ rightButtons }); + this.setState({ channelName }); + } + + async onNavigatorEvent(event) { + if (event.type === 'NavBarButtonPress') { + if (event.id === 'create') { + this.submit(); + } + } + } + submit = () => { if (!this.state.channelName.trim() || this.props.createChannel.isFetching) { return; @@ -62,47 +149,35 @@ export default class CreateChannelView extends LoggedView { }); } - renderChannelNameError() { - if ( - !this.props.createChannel.failure || - this.props.createChannel.error.error !== 'error-duplicate-channel-name' - ) { - return null; + removeUser = (user) => { + if (this.props.users.length === 1) { + return; } - - return ( - - {this.props.createChannel.error.reason} - - ); + this.props.removeUser(user); } renderSwitch = ({ - id, value, label, description, onValueChange, disabled = false + id, value, label, onValueChange, disabled = false }) => ( - - - - {label} - - {description} + + {I18n.t(label)} + - ); + ) renderType() { const { type } = this.state; return this.renderSwitch({ id: 'type', value: type, - label: type ? I18n.t('Private_Channel') : I18n.t('Public_Channel'), - description: type ? I18n.t('Just_invited_people_can_access_this_channel') : I18n.t('Everyone_can_access_this_channel'), + label: 'Private_Channel', onValueChange: value => this.setState({ type: value }) }); } @@ -112,8 +187,7 @@ export default class CreateChannelView extends LoggedView { return this.renderSwitch({ id: 'readonly', value: readOnly, - label: I18n.t('Read_Only_Channel'), - description: readOnly ? I18n.t('Only_authorized_users_can_write_new_messages') : I18n.t('All_users_in_the_channel_can_write_new_messages'), + label: 'Read_Only_Channel', onValueChange: value => this.setState({ readOnly: value }), disabled: broadcast }); @@ -124,8 +198,7 @@ export default class CreateChannelView extends LoggedView { return this.renderSwitch({ id: 'broadcast', value: broadcast, - label: I18n.t('Broadcast_Channel'), - description: I18n.t('Broadcast_channel_Description'), + label: 'Broadcast_Channel', onValueChange: (value) => { this.setState({ broadcast: value, @@ -135,39 +208,70 @@ export default class CreateChannelView extends LoggedView { }); } + renderSeparator = () => + + renderFormSeparator = () => + + renderItem = ({ item }) => ( + this.removeUser(item)} + testID={`create-channel-view-item-${ item.name }`} + /> + ) + + renderInvitedList = () => ( + item._id} + style={[styles.list, sharedStyles.separatorVertical]} + renderItem={this.renderItem} + ItemSeparatorComponent={this.renderSeparator} + enableEmptySections + keyboardShouldPersistTaps='always' + /> + ) + render() { + const userCount = this.props.users.length; return ( - - - this.channelNameRef = ref} - label={I18n.t('Channel_Name')} - value={this.state.channelName} - onChangeText={channelName => this.setState({ channelName })} - placeholder={I18n.t('Type_the_channel_name_here')} - returnKeyType='done' - testID='create-channel-name' - /> - {this.renderChannelNameError()} - {this.renderType()} - {this.renderReadOnly()} - {this.renderBroadcast()} - -