From de1a63c8159918aaa067ce4d0092997659f42960 Mon Sep 17 00:00:00 2001 From: Diego Mello <diegolmello@gmail.com> 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)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ00004XF*Lt006O% z3;baP0003=Nkl<ZSPAWx%}N6?5XWaWD{d@!Q1ECMuRRE&PoYOW_yWE`sp4fXErJi? z1L&K0w|-r`C<txC(q>EqVM)qnAyNvRK$cAA>%YGZWC3`rB7xnmI%)UR@qfHK#un)m zwDmojeAqfD<j(62e#O*GVv=p{NU2RAO6?TFtRqLXpveASIt%iUI2%mJ0pgiV$V1|6 zFd+wsXD%Hf?=H)`<8uvkO@wQn{COJ)_S)F{bs;_h!E|1jzelA&96aA{+*N&P)v)Us zz?mu90rO}ys$qY^8YP-u1g9pnUXX0bi*l^)HRxu9fVN5Fz-mB|Tmk9NCVGgHQns=O zH`e=PZa~P+FDLnIs)sf%T~G31U%rHL>ikQ0s98|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 zcmV<H0u=p;P)<h;3K|Lk000e1NJLTq000#L000#T1^@s6sTZY|00004XF*Lt006O% z3;baP0007_Nkl<ZI1#N>O=uHA6rP`5k`$rfp$Dy4i$y#LLG<Fqqt%N)pk69f0xoHq zzk>9nww|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@_<nD1<U%YK`xyZA*>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-<R@#TS5AyAuaLoq=*Kd!M z^@jisp^8N$mIyKC@iMIl;qQP<O2bin20jo<6NS`pR4239u_y2~N0U@}JkQ%wtI10s z9EZ8JiCX&s$s^8<%UGmO2s#HG+L6>4V<*z-#M1!(7ir)um&?Y&!r~!Ry&F}BQRR|h z)ITJX$?pNiaSBJ)Bko8VBzBu|MTc<?b68xxzs-s_MXDCni9vpR)1)1UEB}R55i5-$ d!!7ju%isK|Bel%+U$g)K002ovPDHLkV1gi!RFVJy literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/plus.png b/android/app/src/main/res/drawable-mdpi/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..cce622415373cec9bcd8d00b0abb6808f50dc088 GIT binary patch literal 276 zcmV+v0qg#WP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00004XF*Lt006O% z3;baP0002XNkl<ZNDX6P7*Rl>^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(0000<MNUMnLSTaR$8d1~ literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-mdpi/textinput_search.png b/android/app/src/main/res/drawable-mdpi/textinput_search.png new file mode 100644 index 0000000000000000000000000000000000000000..4eefb38cdf08f3356af850ae8bdf538b1b147dab GIT binary patch literal 458 zcmV;*0X6=KP)<h;3K|Lk000e1NJLTq000gE000gM1^@s6A4o0H00004XF*Lt006O% z3;baP0004jNkl<ZC>4#8KS)AR6vofFuil_ji<6r<6txARw6{f*MA&SgsRT8&<WP<F zLdC5iLd?CXse*>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 ze<j&Xbc7nE&e$G<qX5pMZT4#+oIBdpxa|wA^^Ml=W476^rIb;+>6YzNBA#oFOSajs zY4{=~p4h&iFR(?^V#Q)<*<LW3eS+p(SL_;(EAne^uM$^E9g)1GrW)eVJ%EfVrIR#t zo_yX}Jsb`t6N!XmhA#(lx%5RK;7-uugHpJvlsr=k3Uq`gL^sS3{YeY0*2`g|(Qugg zN@Vfh!_5paY?228d8iH3aSjFs4cu-&?~{&v0yRXI>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|E<v{yJ7T^G-#YNxt3twW`r-le{Ga*!CC-l%}7xm>FA@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+k<X*|oBn-iE2}FP<E2ySuAFw{m*K7lXHoOVxW5gZ93E?p7~e zb1~+(pjOvE_R38Tca=Jr7Bwg{7q!T6mb7&<ofEMV6JSNKKP+eXk!?ELDQ|sKb;67K r<eS^SewFLdQs!bl)L;-WsWJXzs_ThNA>F&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)<h;3K|Lk000e1NJLTq000~S000~a1^@s6at+^<00004XF*Lt006O% z3;baP000AaNkl<ZNDZ}C-%FEG82--rK9#M^SyZOxRnQLtZ;Ob22pVY#UFeNB(uL4M znMG+3NCw@hvo=~yqNp3Kv`9;Z3X0I1h^RjxY8fi#ujbV4J3a5VZ)|R7o23WbcfRL+ z-uF50`@QEJnIah-9e1}2v3N!ZrGhbK4`_LqFDr`hC<^<;x#%;QGW+Z5>b#N=31PMp z?ds~vo}FE22HqfqFt4=7zO!GPvzGk)t?sI-sz9tQ@mq|b!{OMqv?QKGXfM%`*$@2o zBD43Z%6<aM2n0kC@DgCBk!?<h#|F*jjA~>%L+{B(L@+Ei0<kR+nZwGjVC8qP@})!p zTrO9R*Sk!WxCkE=i}<3dxrgfOYi1Mi4J;@aF5_q@GxiO}^2WyVUky^SXIq=26q!E8 zUp5vxV6$0IF~)S64<`eCGZF)F9<OPOk#A$8b%-%ujVVOLNxR)yL%3{k>P8qFhycOn zbTf+%XtSMr3DKK`38A-kcXww-+oR_^)cV3q4Pr8Bu4v<G=4P{Y9sC6}Q_<Mi#BrG~ z1eJ=Ak@cd>u+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?2<r<QE9u0M5mVvq?e{D~uoJ{XNrz>8u4(KAl>nWUJ2SIzNk&YK z|7n609dF}>h1-lI;@r5^YMsX^aV=yKO?JEESR5+_Kt5RT1)`y1t+E3@Xs4gz;(fP~ z<!ekeUDu!5?YBw@PiK&ru8Sw&YUomLW+r=-%rA01x!vx~b925ASm|y!__2UCMcH`6 zW~-e}6ri*7;U-<5z6>$?44T1#=%}h%N@dqnM35YuPG_#qr}yAngfnDwIDrPx1aBEr zzjLl*J*)sCM?o(K&$xiSFhu@4NG2L1NTN9DRgY)<EP}N_Xa`|Q#uS3LW@YjFi;H?c zL=PHVnP{A#;Q(Qi$1_=mYmN?LoQ<LYPn3?0<4OB$*kakxQ(If>!(5>f2LjWtQi)0h zDadLEiTeG&2jO?X@LiednhKIgB2mC2nwDEejkM;1sYKUBP$r7<K3g|IiRje23L2v5 h-UsO3!?-D~{sp^Xe6r9atNH){002ovPDHLkV1jmU#s&ZY literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxhdpi/plus.png b/android/app/src/main/res/drawable-xxhdpi/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..71db08c7acda5cadd0b6c5da25b931113c969f01 GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*sd&0L zhEy=Vy}j3q*-?b`L3O&Lk5a@9&VLFotZyvcc;Qz2hE2v%%Y;<R!t9zOxXO~0?V4NH zWWHI`6E$P6UqFGfqWX@-GmmZ-h<LuEJl*bnp0QgCJFA8RBUeBJ6C=wk-{Q1oj9#qT zf@f2nNzK(g`{YA$YVWtew<<TZxJ9~{PCP#2wJcZA+wR(;So6S{yYF9bXUgU-TFD)> 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@4pkjXl<Dw*2Q>X!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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 literal 0 HcmV?d00001 diff --git a/android/app/src/main/res/drawable-xxxhdpi/plus.png b/android/app/src/main/res/drawable-xxxhdpi/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..320adb8bf8ef5d5f177e971d8a5a6163cd6957c3 GIT binary patch literal 900 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGoEa{HEjtmSN`?>!lvNA9*TYI`V zhEy=Vox3qx#8IR@-E7g$kglzY9KY5_WacJXu~vU%N}3p^zqYkiL6G|`i}rtU#d}w~ zdegaX7Mfi(RNpgu&v&!!_sRuM@V)<=_Uw1jbltKY0u_!7OacuIED8*a91b9+!+Msa z^_wOAwv`uDEziCES7v5V#=Wy@x8r#pS9eP-X}J6G{ogCwj(1O)wXE{P+ov!7KDr@L zm*2MGL{aZb-}B*;+S%7WR!v|sIuWgUM&$6@q8yIv&B7Cc++=sne>C;br-;B!ebbwT zH}naZW;->s&2!Xe;!(&_5?HV>{X#bbbH^+WmW&sYUz83oEK*})l&WIe%VE&4qSJwX zN;_VucYWu3_Wib*%}IaF`#m3%=6RfRPg<v&Z?#O~^7*HQ|0nL5C*t%+YWty!UyhzT zK1*^n%N>dTyTosOSee%3ySlgV^}CWye=D!nhS#zj6_HyjwP$DP+rzW=hI5`cI6eE2 z`9`yPso0e_%cnL-%wBbV`>fVK_FK=k&)OTpd1AT1{gw+Q>`&^h_-qo~YFN|n`#Hx< za_`!k`B&{99(}O)aLHENy$g0Pu>V~Dhw;ho1>TEacW1wO{XNZk$>hTo>#R2vbHB(g zerj`9aqr#bpSQ`J-`<#6wk7y$U);GD^(h71@8l=G@8S6HRC}4SgUgfX28Q^3NmfiQ zF8f#qR<K!E;Iro*fyY~pOp^a>r+K`sz}A9M_6pDMP)>8R)5)o4t>XCK)i|(!eA9EF z;-AI*NpJU^(5+wUZ*}gtbNu|h*1H(vP!cgxdS+npVyb5}?#g)KzVVtKFk3Nry85}S 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 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 literal 0 HcmV?d00001 diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 49bd9cea5..5b8f16c9f 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 51d1bafff..a6bab715c 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 47c587eb9..5a6037428 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 000000000..5135ba197 --- /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 }) => ( + <View style={styles.container}> + <View style={styles.searchBox}> + <Image source={{ uri: 'textinput_search' }} style={styles.icon} /> + <TextInput + autoCapitalize='none' + autoCorrect={false} + blurOnSubmit + clearButtonMode='while-editing' + placeholder={I18n.t('Search')} + returnKeyType='search' + style={styles.input} + testID={testID} + underlineColorAndroid='transparent' + onChangeText={onChangeText} + /> + </View> + </View> +); + +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 0cbf8aa3f..382939ae6 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 442614790..8af6fa337 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 ed2b02897..711d5d99e 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 cf8c07de2..f4e03dbd4 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 }) => ( <Touch onPress={onPress} onLongPress={onLongPress} style={styles.button} testID={testID}> - <View style={styles.container}> + <View style={[styles.container, style]}> <Avatar text={username} size={30} type='d' style={styles.avatar} /> <View style={styles.textContainer}> <Text style={styles.name}>{name}</Text> <Text style={styles.username}>@{username}</Text> </View> + {icon ? <Image source={{ uri: icon }} style={styles.icon} /> : null} </View> </Touch> ); @@ -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 db6d59119..a423033e2 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 9f64e3b40..bd5ff2d91 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 ce21d94b4..8f3bf1d6f 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 12f1b01e1..93c1ce86e 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 f959fd18b..556b66a7d 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 9a1e4f248..be132c445 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 9f9a62cab..5ce8e870b 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 ( - <Text style={[styles.label_white, styles.label_error]} testID='create-channel-error'> - {this.props.createChannel.error.reason} - </Text> - ); + this.props.removeUser(user); } renderSwitch = ({ - id, value, label, description, onValueChange, disabled = false + id, value, label, onValueChange, disabled = false }) => ( - <View style={{ marginBottom: 15 }}> - <View style={styles.switchContainer}> - <Switch - value={value} - onValueChange={onValueChange} - testID={`create-channel-${ id }`} - onTintColor='#2de0a5' - tintColor={Platform.OS === 'android' ? '#f5455c' : null} - disabled={disabled} - /> - <Text style={styles.switchLabel}>{label}</Text> - </View> - <Text style={styles.switchDescription}>{description}</Text> + <View style={styles.swithContainer}> + <Text style={styles.label}>{I18n.t(label)}</Text> + <Switch + value={value} + onValueChange={onValueChange} + testID={`create-channel-${ id }`} + onTintColor='#2de0a5' + tintColor={Platform.OS === 'android' ? '#f5455c' : null} + disabled={disabled} + /> </View> - ); + ) 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 = () => <View style={[sharedStyles.separator, styles.separator]} /> + + renderFormSeparator = () => <View style={[sharedStyles.separator, styles.formSeparator]} /> + + renderItem = ({ item }) => ( + <UserItem + name={item.fname} + username={item.name} + onPress={() => this.removeUser(item)} + testID={`create-channel-view-item-${ item.name }`} + /> + ) + + renderInvitedList = () => ( + <FlatList + data={this.props.users} + extraData={this.props.users} + keyExtractor={item => item._id} + style={[styles.list, sharedStyles.separatorVertical]} + renderItem={this.renderItem} + ItemSeparatorComponent={this.renderSeparator} + enableEmptySections + keyboardShouldPersistTaps='always' + /> + ) + render() { + const userCount = this.props.users.length; return ( <KeyboardView - contentContainerStyle={styles.container} + contentContainerStyle={[sharedStyles.container, styles.container]} keyboardVerticalOffset={128} > - <ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}> - <SafeAreaView testID='create-channel-view'> - <RCTextInput - inputRef={ref => 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()} - <View style={styles.alignItemsFlexStart}> - <Button - title={I18n.t('Create')} - type='primary' - onPress={this.submit} - disabled={this.state.channelName.length === 0 || this.props.createChannel.isFetching} - testID='create-channel-submit' + <SafeAreaView testID='create-channel-view'> + <ScrollView {...scrollPersistTaps}> + <View style={sharedStyles.separatorVertical}> + <TextInput + ref={ref => this.channelNameRef = ref} + style={styles.input} + label={I18n.t('Channel_Name')} + value={this.state.channelName} + onChangeText={this.onChangeText} + placeholder={I18n.t('Channel_Name')} + returnKeyType='done' + testID='create-channel-name' + autoCorrect={false} + autoCapitalize='none' + underlineColorAndroid='transparent' /> + {this.renderFormSeparator()} + {this.renderType()} + {this.renderFormSeparator()} + {this.renderReadOnly()} + {this.renderFormSeparator()} + {this.renderBroadcast()} + </View> + <View style={styles.invitedHeader}> + <Text style={styles.invitedTitle}>{I18n.t('Invite')}</Text> + <Text style={styles.invitedCount}>{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}</Text> </View> + {this.renderInvitedList()} <Loading visible={this.props.createChannel.isFetching} /> - </SafeAreaView> - </ScrollView> + </ScrollView> + </SafeAreaView> </KeyboardView> ); } diff --git a/app/views/LoginSignupView.js b/app/views/LoginSignupView.js index 9560021fd..a9e86e178 100644 --- a/app/views/LoginSignupView.js +++ b/app/views/LoginSignupView.js @@ -182,7 +182,7 @@ export default class LoginSignupView extends LoggedView { this.props.navigator.push({ screen: 'LoginView', title: this.props.server, - backButtonTitle: I18n.t('Welcome') + backButtonTitle: '' }); } @@ -190,7 +190,7 @@ export default class LoginSignupView extends LoggedView { this.props.navigator.push({ screen: 'RegisterView', title: this.props.server, - backButtonTitle: I18n.t('Welcome') + backButtonTitle: '' }); } diff --git a/app/views/LoginView.js b/app/views/LoginView.js index 0050d5592..aa8c6110a 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -68,7 +68,7 @@ export default class LoginView extends LoggedView { this.props.navigator.push({ screen: 'RegisterView', title: this.props.server, - backButtonTitle: I18n.t('Login') + backButtonTitle: '' }); } @@ -76,7 +76,7 @@ export default class LoginView extends LoggedView { this.props.navigator.push({ screen: 'ForgotPasswordView', title: I18n.t('Forgot_Password'), - backButtonTitle: I18n.t('Login') + backButtonTitle: '' }); } diff --git a/app/views/NewMessageView.js b/app/views/NewMessageView.js new file mode 100644 index 000000000..13ac58641 --- /dev/null +++ b/app/views/NewMessageView.js @@ -0,0 +1,166 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image } from 'react-native'; + +import database from '../lib/realm'; +import RocketChat from '../lib/rocketchat'; +import UserItem from '../presentation/UserItem'; +import debounce from '../utils/debounce'; +import LoggedView from './View'; +import sharedStyles from './Styles'; +import I18n from '../i18n'; +import Touch from '../utils/touch'; +import SearchBox from '../containers/SearchBox'; + +const styles = StyleSheet.create({ + safeAreaView: { + flex: 1, + backgroundColor: Platform.OS === 'ios' ? '#F7F8FA' : '#E1E5E8' + }, + separator: { + marginLeft: 60 + }, + createChannelButton: { + marginVertical: 25 + }, + createChannelContainer: { + height: 47, + backgroundColor: '#fff', + flexDirection: 'row', + alignItems: 'center' + }, + createChannelIcon: { + width: 24, + height: 24, + marginHorizontal: 18 + }, + createChannelText: { + color: '#1D74F5', + fontSize: 18 + } +}); + +/** @extends React.Component */ +export default class SelectedUsersView extends LoggedView { + static navigatorButtons = { + leftButtons: [{ + id: 'cancel', + title: I18n.t('Cancel') + }] + } + + static propTypes = { + navigator: PropTypes.object, + onPressItem: PropTypes.func.isRequired + }; + + constructor(props) { + super('NewMessageView', props); + this.data = database.objects('subscriptions').filtered('t = $0', 'd').sorted('roomUpdatedAt', true); + this.state = { + search: [] + }; + this.data.addListener(this.updateState); + props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); + } + + componentWillUnmount() { + this.updateState.stop(); + this.data.removeAllListeners(); + } + + async onNavigatorEvent(event) { + if (event.type === 'NavBarButtonPress') { + if (event.id === 'cancel') { + this.props.navigator.dismissModal(); + } + } + } + + onSearchChangeText(text) { + this.search(text); + } + + onPressItem = (item) => { + this.props.navigator.dismissModal(); + setTimeout(() => { + this.props.onPressItem(item); + }, 600); + } + + updateState = debounce(() => { + this.forceUpdate(); + }, 1000); + + search = async(text) => { + const result = await RocketChat.search({ text, filterRooms: false }); + this.setState({ + search: result + }); + } + + createChannel = () => { + this.props.navigator.push({ + screen: 'SelectedUsersView', + title: I18n.t('Select_Users'), + backButtonTitle: '', + passProps: { + nextAction: 'CREATE_CHANNEL' + } + }); + } + + renderHeader = () => ( + <View> + <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='new-message-view-search' /> + <Touch onPress={this.createChannel} style={styles.createChannelButton} testID='new-message-view-create-channel'> + <View style={[sharedStyles.separatorVertical, styles.createChannelContainer]}> + <Image style={styles.createChannelIcon} source={{ uri: 'plus' }} /> + <Text style={styles.createChannelText}>{I18n.t('Create_Channel')}</Text> + </View> + </Touch> + </View> + ) + + renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} />; + + renderItem = ({ item, index }) => { + let style = {}; + if (index === 0) { + style = { ...sharedStyles.separatorTop }; + } + if (this.state.search.length > 0 && index === this.state.search.length - 1) { + style = { ...style, ...sharedStyles.separatorBottom }; + } + if (this.state.search.length === 0 && index === this.data.length - 1) { + style = { ...style, ...sharedStyles.separatorBottom }; + } + return ( + <UserItem + name={item.search ? item.name : item.fname} + username={item.search ? item.username : item.name} + onPress={() => this.onPressItem(item)} + testID={`new-message-view-item-${ item.name }`} + style={style} + /> + ); + } + + renderList = () => ( + <FlatList + data={this.state.search.length > 0 ? this.state.search : this.data} + extraData={this.state} + keyExtractor={item => item._id} + ListHeaderComponent={this.renderHeader} + renderItem={this.renderItem} + ItemSeparatorComponent={this.renderSeparator} + keyboardShouldPersistTaps='always' + /> + ) + + render = () => ( + <SafeAreaView style={styles.safeAreaView} testID='new-message-view'> + {this.renderList()} + </SafeAreaView> + ); +} diff --git a/app/views/NewServerView.js b/app/views/NewServerView.js index 66e14a7b0..cbff4379a 100644 --- a/app/views/NewServerView.js +++ b/app/views/NewServerView.js @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet, Platform } from 'react-native'; +import { Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet } from 'react-native'; import { connect } from 'react-redux'; -import { serverRequest } from '../actions/server'; +import { serverRequest, selectServerRequest, serverInitAdd, serverFinishAdd } from '../actions/server'; import sharedStyles from './Styles'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import Button from '../containers/Button'; @@ -12,7 +12,6 @@ import LoggedView from './View'; import I18n from '../i18n'; import { scale, verticalScale, moderateScale } from '../utils/scaling'; import KeyboardView from '../presentation/KeyboardView'; -import { iconsMap } from '../Icons'; const styles = StyleSheet.create({ image: { @@ -45,9 +44,13 @@ const defaultServer = 'https://open.rocket.chat'; @connect(state => ({ connecting: state.server.connecting, failure: state.server.failure, - currentServer: state.server.server + currentServer: state.server.server, + adding: state.server.adding }), dispatch => ({ - connectServer: url => dispatch(serverRequest(url)) + initAdd: () => dispatch(serverInitAdd()), + finishAdd: () => dispatch(serverFinishAdd()), + connectServer: server => dispatch(serverRequest(server)), + selectServer: server => dispatch(selectServerRequest(server)) })) /** @extends React.Component */ export default class NewServerView extends LoggedView { @@ -55,10 +58,14 @@ export default class NewServerView extends LoggedView { navigator: PropTypes.object, server: PropTypes.string, connecting: PropTypes.bool.isRequired, + adding: PropTypes.bool, failure: PropTypes.bool.isRequired, connectServer: PropTypes.func.isRequired, + selectServer: PropTypes.func.isRequired, previousServer: PropTypes.string, - currentServer: PropTypes.string + currentServer: PropTypes.string, + initAdd: PropTypes.func, + finishAdd: PropTypes.func } constructor(props) { @@ -69,25 +76,8 @@ export default class NewServerView extends LoggedView { props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); } - componentWillMount() { - // if previousServer exists, New Server View is a modal - if (this.props.previousServer) { - const closeButton = { - id: 'close', - testID: 'new-server-close', - title: I18n.t('Close') - }; - if (Platform.OS === 'android') { - closeButton.icon = iconsMap.close; - } - this.props.navigator.setButtons({ - leftButtons: [closeButton] - }); - } - } - componentDidMount() { - const { server } = this.props; + const { server, previousServer } = this.props; if (server) { this.props.connectServer(server); this.setState({ text: server }); @@ -96,6 +86,9 @@ export default class NewServerView extends LoggedView { this.input.focus(); }, 600); } + if (previousServer) { + this.props.initAdd(); + } } componentWillReceiveProps(nextProps) { @@ -104,16 +97,22 @@ export default class NewServerView extends LoggedView { } } + componentWillUnmount() { + const { + selectServer, previousServer, currentServer, adding, finishAdd + } = this.props; + if (adding) { + if (previousServer !== currentServer) { + selectServer(previousServer); + } + finishAdd(); + } + } + onNavigatorEvent(event) { if (event.type === 'NavBarButtonPress') { - if (event.id === 'close') { - const { - navigator, connectServer, previousServer, currentServer - } = this.props; - navigator.dismissModal(); - if (previousServer !== currentServer) { - connectServer(previousServer); - } + if (event.id === 'cancel') { + this.props.navigator.dismissModal(); } } } diff --git a/app/views/OAuthView.js b/app/views/OAuthView.js index e2c735472..c054955e6 100644 --- a/app/views/OAuthView.js +++ b/app/views/OAuthView.js @@ -13,7 +13,7 @@ const userAgent = Platform.OS === 'ios' ? 'UserAgent' : userAgentAndroid; @connect(state => ({ server: state.server.server })) -export default class TermsServiceView extends React.PureComponent { +export default class OAuthView extends React.PureComponent { static navigatorButtons = { leftButtons: [{ id: 'close', diff --git a/app/views/OnboardingView/index.js b/app/views/OnboardingView/index.js index 5d5ed15bd..0a9501a64 100644 --- a/app/views/OnboardingView/index.js +++ b/app/views/OnboardingView/index.js @@ -21,6 +21,7 @@ export default class OnboardingView extends LoggedView { connectServer = () => { this.props.navigator.push({ screen: 'NewServerView', + backButtonTitle: '', navigatorStyle: { navBarHidden: true } @@ -30,6 +31,7 @@ export default class OnboardingView extends LoggedView { joinCommunity = () => { this.props.navigator.push({ screen: 'NewServerView', + backButtonTitle: '', passProps: { server: 'https://open.rocket.chat' }, diff --git a/app/views/ProfileView/index.js b/app/views/ProfileView/index.js index 0b2fb65ec..94454f23f 100644 --- a/app/views/ProfileView/index.js +++ b/app/views/ProfileView/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, ScrollView, SafeAreaView, Keyboard, Platform, Dimensions } from 'react-native'; +import { View, ScrollView, SafeAreaView, Keyboard, Dimensions } from 'react-native'; import { connect } from 'react-redux'; import Dialog from 'react-native-dialog'; import SHA256 from 'js-sha256'; @@ -61,7 +61,7 @@ export default class ProfileView extends LoggedView { componentWillMount() { this.props.navigator.setButtons({ leftButtons: [{ - id: 'sideMenu', + id: 'settings', icon: { uri: 'settings', scale: Dimensions.get('window').scale } }] }); @@ -91,7 +91,7 @@ export default class ProfileView extends LoggedView { onNavigatorEvent(event) { if (event.type === 'NavBarButtonPress') { - if (event.id === 'sideMenu' && Platform.OS === 'ios') { + if (event.id === 'settings') { this.props.navigator.toggleDrawer({ side: 'left' }); diff --git a/app/views/RegisterView.js b/app/views/RegisterView.js index ff7e65b0b..e5e91483d 100644 --- a/app/views/RegisterView.js +++ b/app/views/RegisterView.js @@ -93,7 +93,7 @@ export default class RegisterView extends LoggedView { this.props.navigator.push({ screen: 'TermsServiceView', title: I18n.t('Terms_of_Service'), - backButtonTitle: I18n.t('Sign_Up') + backButtonTitle: '' }); } @@ -101,7 +101,7 @@ export default class RegisterView extends LoggedView { this.props.navigator.push({ screen: 'PrivacyPolicyView', title: I18n.t('Privacy_Policy'), - backButtonTitle: I18n.t('Sign_Up') + backButtonTitle: '' }); } diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 93af3e2bd..f17dc5438 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -65,7 +65,8 @@ export default class RoomActionsView extends LoggedView { this.props.navigator.push({ screen: item.route, title: item.name, - passProps: item.params + passProps: item.params, + backButtonTitle: '' }); } if (item.event) { diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index 69343c69e..89a382a15 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -118,6 +118,7 @@ export default class RoomInfoView extends LoggedView { this.props.navigator.push({ screen: 'RoomInfoEditView', title: I18n.t('Room_Info_Edit'), + backButtonTitle: '', passProps: { rid: this.props.rid } diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index 65e8a235e..1e6427c86 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { FlatList, View, TextInput, Vibration, SafeAreaView } from 'react-native'; +import { FlatList, View, Vibration, SafeAreaView } from 'react-native'; import ActionSheet from 'react-native-actionsheet'; import LoggedView from '../View'; @@ -12,6 +12,7 @@ import database from '../../lib/realm'; import { showToast } from '../../utils/info'; import log from '../../utils/log'; import I18n from '../../i18n'; +import SearchBox from '../../containers/SearchBox'; /** @extends React.Component */ export default class RoomMembersView extends LoggedView { @@ -132,6 +133,7 @@ export default class RoomMembersView extends LoggedView { this.props.navigator.push({ screen: 'RoomView', title: name, + backButtonTitle: '', passProps: { room: { rid, name }, rid, @@ -162,20 +164,7 @@ export default class RoomMembersView extends LoggedView { } renderSearchBar = () => ( - <View style={styles.searchBoxView}> - <TextInput - underlineColorAndroid='transparent' - style={styles.searchBox} - onChangeText={text => this.onSearchChangeText(text)} - returnKeyType='search' - placeholder={I18n.t('Search')} - clearButtonMode='while-editing' - blurOnSubmit - autoCorrect={false} - autoCapitalize='none' - testID='room-members-view-search' - /> - </View> + <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='room-members-view-search' /> ) renderSeparator = () => <View style={styles.separator} />; diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index a754e5736..b739e9389 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -127,6 +127,7 @@ export default class RoomView extends LoggedView { this.props.navigator.push({ screen: 'RoomActionsView', title: I18n.t('Actions'), + backButtonTitle: '', passProps: { rid: this.state.room.rid } diff --git a/app/views/RoomsListView/ServerDropdown.js b/app/views/RoomsListView/ServerDropdown.js index b399780e3..92508ceb2 100644 --- a/app/views/RoomsListView/ServerDropdown.js +++ b/app/views/RoomsListView/ServerDropdown.js @@ -86,6 +86,13 @@ export default class ServerDropdown extends Component { title: I18n.t('Add_Server'), passProps: { previousServer: this.props.server + }, + navigatorButtons: { + leftButtons: [{ + id: 'cancel', + testID: 'new-server-close', + title: I18n.t('Close') + }] } }); }, ANIMATION_DURATION); @@ -101,6 +108,7 @@ export default class ServerDropdown extends Component { setTimeout(() => { this.props.navigator.push({ screen: 'NewServerView', + backButtonTitle: '', passProps: { server }, diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index f82dd681a..bc11cacf4 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -1,9 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { Platform, View, TextInput, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard } from 'react-native'; +import { Platform, View, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard } from 'react-native'; import { connect } from 'react-redux'; import { isEqual } from 'lodash'; +import SearchBox from '../../containers/SearchBox'; import database from '../../lib/realm'; import RocketChat from '../../lib/rocketchat'; import RoomItem from '../../presentation/RoomItem'; @@ -26,7 +27,7 @@ const leftButtons = [{ testID: 'rooms-list-view-sidebar' }]; const rightButtons = [{ - id: 'createChannel', + id: 'newMessage', icon: { uri: 'new_channel', scale: Dimensions.get('window').scale }, testID: 'rooms-list-view-create-channel' }]; @@ -38,7 +39,6 @@ if (Platform.OS === 'android') { }); } - @connect((state) => { let result = { userId: state.login.user && state.login.user.id, @@ -74,7 +74,6 @@ export default class RoomsListView extends LoggedView { static navigatorStyle = { navBarCustomView: 'RoomsListHeaderView', - navBarComponentAlignment: 'fill', navBarBackgroundColor: isAndroid() ? '#2F343D' : undefined, navBarTextColor: isAndroid() ? '#FFF' : undefined, navBarButtonColor: isAndroid() ? '#FFF' : undefined @@ -157,6 +156,10 @@ export default class RoomsListView extends LoggedView { this.removeListener(this.direct); this.removeListener(this.livechat); + if (database && database.deleteAll) { + database.deleteAll(); + } + if (this.timeout) { clearTimeout(this.timeout); } @@ -165,12 +168,12 @@ export default class RoomsListView extends LoggedView { onNavigatorEvent(event) { const { navigator } = this.props; if (event.type === 'NavBarButtonPress') { - if (event.id === 'createChannel') { - this.props.navigator.push({ - screen: 'SelectedUsersView', - title: I18n.t('Select_Users'), + if (event.id === 'newMessage') { + this.props.navigator.showModal({ + screen: 'NewMessageView', + title: I18n.t('New_Message'), passProps: { - nextAction: 'CREATE_CHANNEL' + onPressItem: this._onPressItem } }); } else if (event.id === 'settings') { @@ -190,10 +193,6 @@ export default class RoomsListView extends LoggedView { } } - onSearchChangeText(text) { - this.search(text); - } - getSubscriptions = () => { if (this.props.server && this.hasActiveDB()) { if (this.props.sidebarSortby === 'alphabetical') { @@ -285,7 +284,6 @@ export default class RoomsListView extends LoggedView { navigator.setButtons({ leftButtons, rightButtons }); navigator.setStyle({ navBarCustomView: 'RoomsListHeaderView', - navBarComponentAlignment: 'fill', navBarBackgroundColor: isAndroid() ? '#2F343D' : undefined, navBarTextColor: isAndroid() ? '#FFF' : undefined, navBarButtonColor: isAndroid() ? '#FFF' : undefined @@ -327,55 +325,18 @@ export default class RoomsListView extends LoggedView { _isUnread = item => item.unread > 0 || item.alert - async search(text) { - const searchText = text.trim(); - if (searchText === '') { - delete this.oldPromise; - return this.setState({ - search: [] - }); - } - - let data = database.objects('subscriptions').filtered('name CONTAINS[c] $0', searchText).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: true, rooms: true }), - 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; - } - this.setState({ - search: data - }); - } catch (e) { - // alert(JSON.stringify(e)); - } + search = async(text) => { + const result = await RocketChat.search({ text }); + this.setState({ + search: result + }); } goRoom = (rid, name) => { this.props.navigator.push({ screen: 'RoomView', title: name, + backButtonTitle: '', passProps: { room: { rid, name }, rid, @@ -429,22 +390,7 @@ export default class RoomsListView extends LoggedView { renderSearchBar = () => { if (Platform.OS === 'ios') { - return ( - <View style={styles.searchBoxView}> - <TextInput - underlineColorAndroid='transparent' - style={styles.searchBox} - onChangeText={text => this.onSearchChangeText(text)} - returnKeyType='search' - placeholder={I18n.t('Search')} - clearButtonMode='while-editing' - blurOnSubmit - autoCorrect={false} - autoCapitalize='none' - testID='rooms-list-view-search' - /> - </View> - ); + return <SearchBox onChangeText={text => this.search(text)} testID='rooms-list-view-search' />; } } @@ -537,7 +483,7 @@ export default class RoomsListView extends LoggedView { return ( <ScrollView - contentOffset={Platform.OS === 'ios' ? { x: 0, y: 37 } : {}} + contentOffset={Platform.OS === 'ios' ? { x: 0, y: 56 } : {}} keyboardShouldPersistTaps='always' testID='rooms-list-view-list' > diff --git a/app/views/RoomsListView/styles.js b/app/views/RoomsListView/styles.js index 1f5afa5ea..433289e79 100644 --- a/app/views/RoomsListView/styles.js +++ b/app/views/RoomsListView/styles.js @@ -31,17 +31,6 @@ export default StyleSheet.create({ height: 22, color: 'white' }, - searchBoxView: { - backgroundColor: '#eee' - }, - searchBox: { - backgroundColor: '#fff', - margin: 5, - borderRadius: 5, - padding: 5, - paddingLeft: 10, - color: '#aaa' - }, loading: { flex: 1 }, diff --git a/app/views/SelectedUsersView.js b/app/views/SelectedUsersView.js index beb94b16d..45c091a3f 100644 --- a/app/views/SelectedUsersView.js +++ b/app/views/SelectedUsersView.js @@ -1,56 +1,29 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, StyleSheet, TextInput, Text, TouchableOpacity, SafeAreaView, FlatList, LayoutAnimation } from 'react-native'; +import { View, StyleSheet, SafeAreaView, FlatList, LayoutAnimation, Platform } from 'react-native'; import { connect } from 'react-redux'; import { addUser, removeUser, reset, setLoading } from '../actions/selectedUsers'; import database from '../lib/realm'; import RocketChat from '../lib/rocketchat'; import UserItem from '../presentation/UserItem'; -import Avatar from '../containers/Avatar'; import Loading from '../containers/Loading'; import debounce from '../utils/debounce'; import LoggedView from './View'; import I18n from '../i18n'; import log from '../utils/log'; -import { iconsMap } from '../Icons'; +import SearchBox from '../containers/SearchBox'; +import sharedStyles from './Styles'; const styles = StyleSheet.create({ - container: { - flex: 1, - alignItems: 'stretch', - justifyContent: 'center' - }, safeAreaView: { flex: 1, - backgroundColor: '#FFFFFF' - }, - list: { - width: '100%', - backgroundColor: '#FFFFFF' + backgroundColor: Platform.OS === 'ios' ? '#F7F8FA' : '#E1E5E8' }, - searchBoxView: { - backgroundColor: '#eee' - }, - searchBox: { - backgroundColor: '#fff', - margin: 5, - borderRadius: 5, - padding: 5, - paddingLeft: 10, - color: '#aaa' - }, - selectItemView: { - width: 80, - height: 80, - padding: 8, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center' + header: { + backgroundColor: '#fff' }, separator: { - height: StyleSheet.hairlineWidth, - backgroundColor: '#E1E5E8', marginLeft: 60 } }); @@ -95,15 +68,19 @@ export default class SelectedUsersView extends LoggedView { }); } - componentWillReceiveProps(nextProps) { - if (nextProps.users.length !== this.props.users.length) { - const { length } = nextProps.users; + async componentDidUpdate(prevProps) { + const isVisible = await this.props.navigator.screenIsCurrentlyVisible(); + if (!isVisible) { + return; + } + if (prevProps.users.length !== this.props.users.length) { + const { length } = this.props.users; const rightButtons = []; if (length > 0) { rightButtons.push({ id: 'create', - testID: 'selected-users-view-submit', - icon: iconsMap.add + title: I18n.t('Next'), + testID: 'selected-users-view-submit' }); } this.props.navigator.setButtons({ rightButtons }); @@ -123,7 +100,8 @@ export default class SelectedUsersView extends LoggedView { if (nextAction === 'CREATE_CHANNEL') { this.props.navigator.push({ screen: 'CreateChannelView', - title: I18n.t('Create_Channel') + title: I18n.t('Create_Channel'), + backButtonTitle: '' }); } else { try { @@ -148,90 +126,40 @@ export default class SelectedUsersView extends LoggedView { this.forceUpdate(); }, 1000); - async search(text) { - const searchText = text.trim(); - if (searchText === '') { - delete this.oldPromise; - return this.setState({ - search: [] - }); - } - - let data = this.data.filtered('name CONTAINS[c] $0 AND t = $1', searchText, 'd').slice(0, 7); - - const usernames = data.map(sub => sub.map); - try { - if (data.length < 7) { - if (this.oldPromise) { - this.oldPromise('cancel'); - } - - const { users } = await Promise.race([ - RocketChat.spotlight(searchText, usernames, { users: true, rooms: false }), - new Promise((resolve, reject) => this.oldPromise = reject) - ]); - - data = users.map(user => ({ - ...user, - rid: user.username, - name: user.username, - t: 'd', - search: true - })); - - delete this.oldPromise; - } - this.setState({ - search: data - }); - } catch (e) { - // alert(JSON.stringify(e)); - } + search = async(text) => { + const result = await RocketChat.search({ text, filterRooms: false }); + this.setState({ + search: result + }); } + isChecked = username => this.props.users.findIndex(el => el.name === username) !== -1; + toggleUser = (user) => { LayoutAnimation.easeInEaseOut(); - const index = this.props.users.findIndex(el => el.name === user.name); - if (index === -1) { + if (!this.isChecked(user.name)) { this.props.addUser(user); } else { this.props.removeUser(user); } - }; + } _onPressItem = (id, item = {}) => { if (item.search) { - this.toggleUser({ _id: item._id, name: item.username }); + this.toggleUser({ _id: item._id, name: item.username, fname: item.name }); } else { - this.toggleUser({ _id: item._id, name: item.name }); + this.toggleUser({ _id: item._id, name: item.name, fname: item.fname }); } - }; + } _onPressSelectedItem = item => this.toggleUser(item); renderHeader = () => ( - <View style={styles.container}> - {this.renderSearchBar()} + <View style={styles.header}> + <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='select-users-view-search' /> {this.renderSelected()} </View> - ); - - renderSearchBar = () => ( - <View style={styles.searchBoxView}> - <TextInput - underlineColorAndroid='transparent' - style={styles.searchBox} - onChangeText={text => this.onSearchChangeText(text)} - returnKeyType='search' - placeholder={I18n.t('Search')} - clearButtonMode='while-editing' - blurOnSubmit - testID='select-users-view-search' - autoCorrect={false} - autoCapitalize='none' - /> - </View> - ); + ) renderSelected = () => { if (this.props.users.length === 0) { @@ -241,57 +169,70 @@ export default class SelectedUsersView extends LoggedView { <FlatList data={this.props.users} keyExtractor={item => item._id} - style={styles.list} + style={[styles.list, sharedStyles.separatorTop]} + contentContainerStyle={{ marginVertical: 5 }} renderItem={this.renderSelectedItem} enableEmptySections keyboardShouldPersistTaps='always' horizontal /> ); - }; + } renderSelectedItem = ({ item }) => ( - <TouchableOpacity - key={item._id} - style={styles.selectItemView} + <UserItem + name={item.fname} + username={item.name} onPress={() => this._onPressSelectedItem(item)} testID={`selected-user-${ item.name }`} - > - <Avatar text={item.name} size={40} /> - <Text ellipsizeMode='tail' numberOfLines={1} style={{ fontSize: 10 }}> - {item.name} - </Text> - </TouchableOpacity> - ); - - renderSeparator = () => <View style={styles.separator} />; - - renderItem = ({ item }) => ( - <UserItem - name={item.fname ? item.fname : item.name} - username={item.fname ? item.name : item.username} - onPress={() => this._onPressItem(item._id, item)} - testID={`select-users-view-item-${ item.name }`} + style={{ paddingRight: 15 }} /> ) + renderSeparator = () => <View style={[sharedStyles.separator, styles.separator]} /> + + renderItem = ({ item, index }) => { + const name = item.search ? item.name : item.fname; + const username = item.search ? item.username : item.name; + let style = {}; + if (index === 0) { + style = { ...sharedStyles.separatorTop }; + } + if (this.state.search.length > 0 && index === this.state.search.length - 1) { + style = { ...style, ...sharedStyles.separatorBottom }; + } + if (this.state.search.length === 0 && index === this.data.length - 1) { + style = { ...style, ...sharedStyles.separatorBottom }; + } + return ( + <UserItem + name={name} + username={username} + onPress={() => this._onPressItem(item._id, item)} + testID={`select-users-view-item-${ item.name }`} + icon={this.isChecked(username) ? 'check' : null} + style={style} + /> + ); + } + renderList = () => ( <FlatList data={this.state.search.length > 0 ? this.state.search : this.data} extraData={this.props} keyExtractor={item => item._id} - style={styles.list} renderItem={this.renderItem} - ListHeaderComponent={this.renderHeader} ItemSeparatorComponent={this.renderSeparator} + ListHeaderComponent={this.renderHeader} enableEmptySections keyboardShouldPersistTaps='always' /> - ); + ) + render = () => ( <SafeAreaView style={styles.safeAreaView} testID='select-users-view'> {this.renderList()} <Loading visible={this.props.loading} /> </SafeAreaView> - ); + ) } diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js index bf3b06919..2de1393d0 100644 --- a/app/views/SettingsView/index.js +++ b/app/views/SettingsView/index.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, ScrollView, SafeAreaView, Platform, Dimensions } from 'react-native'; +import { View, ScrollView, SafeAreaView, Dimensions } from 'react-native'; import RNPickerSelect from 'react-native-picker-select'; import { connect } from 'react-redux'; @@ -50,7 +50,7 @@ export default class SettingsView extends LoggedView { componentWillMount() { this.props.navigator.setButtons({ leftButtons: [{ - id: 'sideMenu', + id: 'settings', icon: { uri: 'settings', scale: Dimensions.get('window').scale } }] }); @@ -65,7 +65,7 @@ export default class SettingsView extends LoggedView { onNavigatorEvent(event) { if (event.type === 'NavBarButtonPress') { - if (event.id === 'sideMenu' && Platform.OS === 'ios') { + if (event.id === 'settings') { this.props.navigator.toggleDrawer({ side: 'left' }); diff --git a/app/views/Styles.js b/app/views/Styles.js index c0fe421ff..facf01c60 100644 --- a/app/views/Styles.js +++ b/app/views/Styles.js @@ -1,6 +1,6 @@ import { StyleSheet, Platform } from 'react-native'; -import { COLOR_DANGER, COLOR_BUTTON_PRIMARY, COLOR_TEXT } from '../constants/colors'; +import { COLOR_DANGER, COLOR_BUTTON_PRIMARY, COLOR_TEXT, COLOR_SEPARATOR } from '../constants/colors'; export default StyleSheet.create({ container: { @@ -197,5 +197,22 @@ export default StyleSheet.create({ width: 44, alignItems: 'center', justifyContent: 'center' + }, + separator: { + height: StyleSheet.hairlineWidth, + backgroundColor: COLOR_SEPARATOR + }, + separatorTop: { + borderColor: COLOR_SEPARATOR, + borderTopWidth: StyleSheet.hairlineWidth + }, + separatorBottom: { + borderColor: COLOR_SEPARATOR, + borderBottomWidth: StyleSheet.hairlineWidth + }, + separatorVertical: { + borderColor: COLOR_SEPARATOR, + borderTopWidth: StyleSheet.hairlineWidth, + borderBottomWidth: StyleSheet.hairlineWidth } }); diff --git a/app/views/index.js b/app/views/index.js index 1b47ee6cf..85e1e7a5b 100644 --- a/app/views/index.js +++ b/app/views/index.js @@ -6,6 +6,7 @@ import ForgotPasswordView from './ForgotPasswordView'; import LoginSignupView from './LoginSignupView'; import LoginView from './LoginView'; import MentionedMessagesView from './MentionedMessagesView'; +import NewMessageView from './NewMessageView'; import NewServerView from './NewServerView'; import OAuthView from './OAuthView'; import OnboardingView from './OnboardingView'; @@ -36,6 +37,7 @@ export const registerScreens = (store) => { Navigation.registerComponent('LoginSignupView', () => LoginSignupView, store, Provider); Navigation.registerComponent('LoginView', () => LoginView, store, Provider); Navigation.registerComponent('MentionedMessagesView', () => MentionedMessagesView, store, Provider); + Navigation.registerComponent('NewMessageView', () => NewMessageView, store, Provider); Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); Navigation.registerComponent('OAuthView', () => OAuthView, store, Provider); Navigation.registerComponent('OnboardingView', () => OnboardingView, store, Provider); diff --git a/e2e/01-welcome.spec.js b/e2e/01-welcome.spec.js index 5001ef680..81a6075b1 100644 --- a/e2e/01-welcome.spec.js +++ b/e2e/01-welcome.spec.js @@ -30,14 +30,14 @@ describe('Welcome screen', () => { await element(by.id('welcome-view-login')).tap(); await waitFor(element(by.id('login-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('login-view'))).toBeVisible(); - await tapBack('Welcome'); + await tapBack(); }); it('should navigate to register', async() => { await element(by.id('welcome-view-register')).tap(); await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('register-view'))).toBeVisible(); - await tapBack('Welcome'); + await tapBack(); }); afterEach(async() => { diff --git a/e2e/04-login.spec.js b/e2e/04-login.spec.js index c017646f3..4540f91e3 100644 --- a/e2e/04-login.spec.js +++ b/e2e/04-login.spec.js @@ -49,18 +49,18 @@ describe('Login screen', () => { await element(by.id('login-view-register')).tap(); await waitFor(element(by.id('register-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('register-view'))).toBeVisible(); - await tapBack('Login'); + await tapBack(); }); it('should navigate to forgot password', async() => { await element(by.id('login-view-forgot-password')).tap(); await waitFor(element(by.id('forgot-password-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('forgot-password-view'))).toBeVisible(); - await tapBack('Login'); + await tapBack(); }); it('should navigate to welcome', async() => { - await tapBack('Welcome'); + await tapBack(); await waitFor(element(by.id('welcome-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('welcome-view'))).toBeVisible(); await element(by.id('welcome-view-login')).tap(); diff --git a/e2e/05-roomslist.spec.js b/e2e/05-roomslist.spec.js index 645e35933..309ebf87d 100644 --- a/e2e/05-roomslist.spec.js +++ b/e2e/05-roomslist.spec.js @@ -11,9 +11,9 @@ describe('Rooms list screen', () => { await expect(element(by.id('rooms-list-view'))).toBeVisible(); }); - it('should have rooms list', async() => { - await expect(element(by.id('rooms-list-view-list'))).toBeVisible(); - }); + // it('should have rooms list', async() => { + // await expect(element(by.id('rooms-list-view-list'))).toBeVisible(); + // }); it('should have room item', async() => { await expect(element(by.id('rooms-list-view-item-general'))).toExist(); @@ -38,9 +38,12 @@ describe('Rooms list screen', () => { describe('Usage', async() => { it('should search room and navigate', async() => { - await element(by.id('rooms-list-view-list')).swipe('down'); - await waitFor(element(by.id('rooms-list-view-search'))).toBeVisible().withTimeout(2000); - await expect(element(by.id('rooms-list-view-search'))).toBeVisible(); + // await element(by.id('rooms-list-view-list')).swipe('down'); + // await waitFor(element(by.id('rooms-list-view-search'))).toBeVisible().withTimeout(2000); + // await expect(element(by.id('rooms-list-view-search'))).toBeVisible(); + + await waitFor(element(by.id('rooms-list-view-search'))).toExist().withTimeout(2000); + await element(by.id('rooms-list-view-search')).replaceText('rocket.cat'); await waitFor(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible().withTimeout(60000); await expect(element(by.id('rooms-list-view-item-rocket.cat'))).toBeVisible(); @@ -49,7 +52,7 @@ describe('Rooms list screen', () => { await expect(element(by.id('room-view'))).toBeVisible(); await waitFor(element(by.text('rocket.cat'))).toBeVisible().withTimeout(60000); await expect(element(by.text('rocket.cat'))).toBeVisible(); - await tapBack('Messages'); + await tapBack(2); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('rooms-list-view'))).toBeVisible(); await element(by.id('rooms-list-view-search')).replaceText(''); diff --git a/e2e/06-createroom.spec.js b/e2e/06-createroom.spec.js index 19d153548..dc056586b 100644 --- a/e2e/06-createroom.spec.js +++ b/e2e/06-createroom.spec.js @@ -3,37 +3,70 @@ const { } = require('detox'); const { takeScreenshot } = require('./helpers/screenshot'); const data = require('./data'); -const { tapBack } = require('./helpers/app'); +const { tapBack, sleep } = require('./helpers/app'); describe('Create room screen', () => { before(async() => { + await sleep(5000); + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await device.reloadReactNative(); await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); }); - describe('Render', async() => { - it('should have select users screen', async() => { - await expect(element(by.id('select-users-view'))).toBeVisible(); - }); + describe('New Message', async() => { + describe('Render', async() => { + it('should have new message screen', async() => { + await expect(element(by.id('new-message-view'))).toBeVisible(); + }); + + it('should have search input', async() => { + await waitFor(element(by.id('new-message-view-search'))).toExist().withTimeout(2000); + await expect(element(by.id('new-message-view-search'))).toExist(); + }); + + after(async() => { + takeScreenshot(); + }); + }) - it('should have search input', async() => { - await expect(element(by.id('select-users-view-search'))).toBeVisible(); - }); + describe('Usage', async() => { + it('should back to rooms list', async() => { + await tapBack(); + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await expect(element(by.id('rooms-list-view'))).toBeVisible(); + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); + }); - after(async() => { - takeScreenshot(); - }); - }); + it('should search user and navigate', async() => { + await element(by.id('new-message-view-search')).replaceText('rocket.cat'); + await waitFor(element(by.id('new-message-view-item-rocket.cat'))).toBeVisible().withTimeout(60000); + await expect(element(by.id('new-message-view-item-rocket.cat'))).toBeVisible(); + await element(by.id('new-message-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(10000); + await expect(element(by.id('room-view'))).toBeVisible(); + await waitFor(element(by.text('rocket.cat'))).toBeVisible().withTimeout(60000); + await expect(element(by.text('rocket.cat'))).toBeVisible(); + await tapBack(2); + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await element(by.id('rooms-list-view-create-channel')).tap(); + }); - describe('Usage', async() => { - it('should back to rooms list', async() => { - await tapBack('Messages'); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); - await expect(element(by.id('rooms-list-view'))).toBeVisible(); - await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); - }); + it('should navigate to select users', async() => { + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); + await expect(element(by.id('select-users-view'))).toBeVisible(); + }); + + + after(async() => { + takeScreenshot(); + }); + }) + }); + describe('Select Users', async() => { it('should search users', async() => { await element(by.id('select-users-view-search')).replaceText('rocket.cat'); await waitFor(element(by.id(`select-users-view-item-rocket.cat`))).toBeVisible().withTimeout(10000); @@ -57,52 +90,65 @@ describe('Create room screen', () => { 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 expect(element(by.id('create-channel-name'))).toBeVisible(); - await expect(element(by.id('create-channel-type'))).toBeVisible(); - await expect(element(by.id('create-channel-readonly'))).toBeVisible(); - await expect(element(by.id('create-channel-broadcast'))).toExist(); - await expect(element(by.id('create-channel-submit'))).toExist(); }); + }) - it('should get invalid room', async() => { - await element(by.id('create-channel-name')).replaceText('general'); - await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('create-channel-error'))).toBeVisible().withTimeout(60000); - await expect(element(by.id('create-channel-error'))).toBeVisible(); - }); - - it('should create public room', async() => { - await element(by.id('create-channel-name')).replaceText(`public${ data.random }`); - await element(by.id('create-channel-type')).tap(); - await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); - await expect(element(by.id('room-view'))).toBeVisible(); - await waitFor(element(by.text(`public${ data.random }`))).toBeVisible().withTimeout(60000); - await expect(element(by.text(`public${ data.random }`))).toBeVisible(); - await tapBack('Messages'); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); - await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible().withTimeout(60000); - await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible(); - }); + describe('Create Channel', async() => { + describe('Render', async() => { + it('should render all fields', async() => { + await expect(element(by.id('create-channel-name'))).toBeVisible(); + await expect(element(by.id('create-channel-type'))).toBeVisible(); + await expect(element(by.id('create-channel-readonly'))).toBeVisible(); + await expect(element(by.id('create-channel-broadcast'))).toExist(); + }) + }) - it('should create private room', async() => { - await element(by.id('rooms-list-view-create-channel')).tap(); - await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); - await element(by.id('select-users-view-item-rocket.cat')).tap(); - await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(5000); - await element(by.id('selected-users-view-submit')).tap(); - await waitFor(element(by.id('create-channel-view'))).toBeVisible().withTimeout(5000); - await element(by.id('create-channel-name')).replaceText(`private${ data.random }`); - await element(by.id('create-channel-submit')).tap(); - await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); - await expect(element(by.id('room-view'))).toBeVisible(); - await waitFor(element(by.text(`private${ data.random }`))).toBeVisible().withTimeout(60000); - await expect(element(by.text(`private${ data.random }`))).toBeVisible(); - await tapBack('Messages'); - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); - 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(); - }); + describe('Usage', async() => { + it('should get invalid room', async() => { + await element(by.id('create-channel-name')).replaceText('general'); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.text(`A channel with name 'general' exists`))).toBeVisible().withTimeout(60000); + await expect(element(by.text(`A channel with name 'general' exists`))).toBeVisible(); + await element(by.text('OK')).tap(); + }); + + it('should create public room', async() => { + await element(by.id('create-channel-name')).replaceText(`public${ data.random }`); + await element(by.id('create-channel-type')).tap(); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); + await expect(element(by.id('room-view'))).toBeVisible(); + await waitFor(element(by.text(`public${ data.random }`))).toBeVisible().withTimeout(60000); + await expect(element(by.text(`public${ data.random }`))).toBeVisible(); + await tapBack(2); + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible().withTimeout(60000); + await expect(element(by.id(`rooms-list-view-item-public${ data.random }`))).toBeVisible(); + }); + + it('should create private room', async() => { + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + await device.reloadReactNative(); + await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); + await element(by.id('new-message-view-create-channel')).tap(); + await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); + await element(by.id('select-users-view-item-rocket.cat')).tap(); + await waitFor(element(by.id('selected-user-rocket.cat'))).toBeVisible().withTimeout(5000); + await element(by.id('selected-users-view-submit')).tap(); + await waitFor(element(by.id('create-channel-view'))).toBeVisible().withTimeout(5000); + await element(by.id('create-channel-name')).replaceText(`private${ data.random }`); + await element(by.id('create-channel-submit')).tap(); + await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(60000); + await expect(element(by.id('room-view'))).toBeVisible(); + await waitFor(element(by.text(`private${ data.random }`))).toBeVisible().withTimeout(60000); + await expect(element(by.text(`private${ data.random }`))).toBeVisible(); + await tapBack(2); + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); + 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(); + }); + }) afterEach(async() => { takeScreenshot(); diff --git a/e2e/07-room.spec.js b/e2e/07-room.spec.js index 2100ad0f2..69a726837 100644 --- a/e2e/07-room.spec.js +++ b/e2e/07-room.spec.js @@ -74,7 +74,7 @@ describe('Room screen', () => { describe('Usage', async() => { describe('Header', async() => { it('should back to rooms list', async() => { - await tapBack('Messages'); + await tapBack(2); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('rooms-list-view'))).toBeVisible(); await navigateToRoom(); @@ -84,7 +84,7 @@ describe('Room screen', () => { await element(by.id('room-view-header-actions')).tap(); await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('room-actions-view'))).toBeVisible(); - await tapBack(`private${ data.random }`); + await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); }); }); @@ -287,7 +287,8 @@ describe('Room screen', () => { }); after(async() => { - await tapBack('Messages'); + await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); + await tapBack(2); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('rooms-list-view'))).toBeVisible(); }); diff --git a/e2e/08-roomactions.spec.js b/e2e/08-roomactions.spec.js index cc314a8fb..a6b939e48 100644 --- a/e2e/08-roomactions.spec.js +++ b/e2e/08-roomactions.spec.js @@ -21,15 +21,16 @@ async function navigateToRoomActions(type) { await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000); } -async function backToActions() { - await tapBack('Actions'); - await waitFor(element(by.id('rooms-actions-view'))).toBeVisible().withTimeout(2000); +async function backToActions(index = 0) { + await tapBack(index); + await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); + await expect(element(by.id('room-actions-view'))).toBeVisible(); } -async function backToRoomsList(room) { - await tapBack(room); +async function backToRoomsList() { + await tapBack(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); - await tapBack('Messages'); + await tapBack(2); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); } @@ -98,7 +99,7 @@ describe('Room actions screen', () => { }); after(async() => { - await backToRoomsList('rocket.cat'); + await backToRoomsList(); }); }); @@ -247,10 +248,10 @@ describe('Room actions screen', () => { await element(by.id('room-actions-search')).tap(); await waitFor(element(by.id('search-messages-view'))).toExist().withTimeout(2000); await expect(element(by.id('search-message-view-input'))).toBeVisible(); - await element(by.id('search-message-view-input')).tap(); await element(by.id('search-message-view-input')).replaceText(`/${ data.random }message/`); await waitFor(element(by.text(`${ data.random }message`).withAncestor(by.id('search-messages-view'))).atIndex(0)).toBeVisible().withTimeout(60000); await expect(element(by.text(`${ data.random }message`).withAncestor(by.id('search-messages-view'))).atIndex(0)).toBeVisible(); + await element(by.traits(['button'])).atIndex(0).tap(); await backToActions(); }); @@ -284,7 +285,7 @@ describe('Room actions screen', () => { await expect(element(by.text('You are the last owner. Please set new owner before leaving the room.'))).toBeVisible(); await takeScreenshot(); await element(by.text('OK')).tap(); - await waitFor(element(by.id('rooms-actions-view'))).toBeVisible().withTimeout(2000); + await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); }); describe('Add User', async() => { @@ -304,7 +305,7 @@ describe('Room actions screen', () => { await element(by.id('room-members-view-toggle-status')).tap(); await waitFor(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible().withTimeout(60000); await expect(element(by.id(`room-members-view-item-${ data.alternateUser }`))).toBeVisible(); - await backToActions(); + await backToActions(1); }); after(async() => { @@ -363,8 +364,8 @@ describe('Room actions screen', () => { await expect(element(by.id('room-view'))).toBeVisible(); await waitFor(element(by.text(data.alternateUser))).toBeVisible().withTimeout(60000); await expect(element(by.text(data.alternateUser))).toBeVisible(); - await tapBack('Messages'); - await waitFor(element(by.id('room-list-view'))).toBeVisible().withTimeout(2000); + await tapBack(2); + await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); }); afterEach(async() => { diff --git a/e2e/09-roominfo.spec.js b/e2e/09-roominfo.spec.js index b2c424307..0638d0123 100644 --- a/e2e/09-roominfo.spec.js +++ b/e2e/09-roominfo.spec.js @@ -158,7 +158,7 @@ describe('Room info screen', () => { await expect(element(by.text('Settings succesfully changed!'))).toBeVisible(); await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000); await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible(); - await tapBack('Room Info'); + await tapBack(); await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('room-info-view-name'))).toHaveText(`${ room }new`).withTimeout(60000); await expect(element(by.id('room-info-view-name'))).toHaveText(`${ room }new`); @@ -206,7 +206,7 @@ describe('Room info screen', () => { await expect(element(by.text('Settings succesfully changed!'))).toBeVisible(); await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000); await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible(); - await tapBack('Room Info'); + await tapBack(); await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('room-info-view-description'))).toHaveText('new description').withTimeout(60000); await expect(element(by.id('room-info-view-description'))).toHaveText('new description'); @@ -223,7 +223,7 @@ describe('Room info screen', () => { await expect(element(by.text('Settings succesfully changed!'))).toBeVisible(); await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000); await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible(); - await tapBack('Room Info'); + await tapBack(); await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('room-info-view-topic'))).toHaveText('new topic').withTimeout(60000); await expect(element(by.id('room-info-view-topic'))).toHaveText('new topic'); @@ -240,7 +240,7 @@ describe('Room info screen', () => { await expect(element(by.text('Settings succesfully changed!'))).toBeVisible(); await waitFor(element(by.text('Settings succesfully changed!'))).toBeNotVisible().withTimeout(10000); await expect(element(by.text('Settings succesfully changed!'))).toBeNotVisible(); - await tapBack('Room Info'); + await tapBack(); await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('room-info-view-announcement'))).toHaveText('new announcement').withTimeout(60000); await expect(element(by.id('room-info-view-announcement'))).toHaveText('new announcement'); diff --git a/e2e/11-broadcast.spec.js b/e2e/11-broadcast.spec.js index d959e539c..582cb8935 100644 --- a/e2e/11-broadcast.spec.js +++ b/e2e/11-broadcast.spec.js @@ -12,6 +12,8 @@ describe('Broadcast room', () => { it('should create broadcast room', async() => { await element(by.id('rooms-list-view-create-channel')).tap(); + await waitFor(element(by.id('new-message-view'))).toBeVisible().withTimeout(2000); + await element(by.id('new-message-view-create-channel')).tap(); await waitFor(element(by.id('select-users-view'))).toBeVisible().withTimeout(2000); await element(by.id(`select-users-view-item-${ data.alternateUser }`)).tap(); await waitFor(element(by.id(`selected-user-${ data.alternateUser }`))).toBeVisible().withTimeout(5000); @@ -30,14 +32,14 @@ describe('Broadcast room', () => { await waitFor(element(by.id('room-info-view'))).toBeVisible().withTimeout(2000); await waitFor(element(by.id('room-info-view-broadcast'))).toBeVisible().withTimeout(2000); await expect(element(by.id('room-info-view-broadcast'))).toBeVisible(); - await tapBack('Actions'); + await tapBack(1); await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); - await tapBack(`broadcast${ data.random }`); + await tapBack(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); - await tapBack('Messages'); + 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 }`))).toBeVisible().withTimeout(60000); - await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible(); + 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() => { @@ -51,7 +53,7 @@ describe('Broadcast room', () => { }); it('should login as user without write message authorization and enter room', async() => { - await tapBack('Messages'); + await tapBack(2); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('rooms-list-view'))).toBeVisible(); await logout(); @@ -61,7 +63,8 @@ describe('Broadcast room', () => { await element(by.id('login-view-submit')).tap(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); // await device.reloadReactNative(); // remove after fix logout - await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + // await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(10000); + await element(by.id('rooms-list-view-search')).replaceText(`broadcast${ data.random }`); await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible().withTimeout(60000); await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toBeVisible(); await element(by.id(`rooms-list-view-item-broadcast${ data.random }`)).tap(); @@ -107,7 +110,7 @@ describe('Broadcast room', () => { after(async() => { // log back as main test user and left screen on RoomsListView - await tapBack('Messages'); + await tapBack(2); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await logout(); await navigateToLogin(); diff --git a/e2e/helpers/app.js b/e2e/helpers/app.js index 873dd0be1..1da062cab 100644 --- a/e2e/helpers/app.js +++ b/e2e/helpers/app.js @@ -36,12 +36,8 @@ async function logout() { await expect(element(by.id('onboarding-view'))).toBeVisible(); } -async function tapBack(label) { - try { - return element(by.traits(['button']).and(by.label(label || 'Back'))).atIndex(0).tap(); - } catch (err) { - return element(by.type('_UIModernBarButton').and(by.label(label || 'Back'))).tap(); - } +async function tapBack(index) { + await element(by.type('_UIModernBarButton')).atIndex(index || 0).tap(); } async function sleep(ms) { diff --git a/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/Contents.json b/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/Contents.json new file mode 100644 index 000000000..d984b4ae4 --- /dev/null +++ b/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "plus.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "plus@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "plus@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus.png b/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..cce622415373cec9bcd8d00b0abb6808f50dc088 GIT binary patch literal 276 zcmV+v0qg#WP)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv00004XF*Lt006O% z3;baP0002XNkl<ZNDX6P7*Rl>^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(0000<MNUMnLSTaR$8d1~ literal 0 HcmV?d00001 diff --git a/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus@2x.png b/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus@2x.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|E<v{yJ7T^G-#YNxt3twW`r-le{Ga*!CC-l%}7xm>FA@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+k<X*|oBn-iE2}FP<E2ySuAFw{m*K7lXHoOVxW5gZ93E?p7~e zb1~+(pjOvE_R38Tca=Jr7Bwg{7q!T6mb7&<ofEMV6JSNKKP+eXk!?ELDQ|sKb;67K r<eS^SewFLdQs!bl)L;-WsWJXzs_ThNA>F&cSYhyV^>bP0l+XkKqf@le literal 0 HcmV?d00001 diff --git a/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus@3x.png b/ios/RocketChatRN/Images.xcassets/Icons/plus.imageset/plus@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..71db08c7acda5cadd0b6c5da25b931113c969f01 GIT binary patch literal 621 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!Ea{HEjtmSN`?>!lvNA9*sd&0L zhEy=Vy}j3q*-?b`L3O&Lk5a@9&VLFotZyvcc;Qz2hE2v%%Y;<R!t9zOxXO~0?V4NH zWWHI`6E$P6UqFGfqWX@-GmmZ-h<LuEJl*bnp0QgCJFA8RBUeBJ6C=wk-{Q1oj9#qT zf@f2nNzK(g`{YA$YVWtew<<TZxJ9~{PCP#2wJcZA+wR(;So6S{yYF9bXUgU-TFD)> 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@4pkjXl<Dw*2Q>X!1Ss*4CHzmYWDabJ2vvpU46R)_>~{SXcawt*<{FsR&Fz44$rj JF6*2UngArW_jUjP literal 0 HcmV?d00001 diff --git a/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/Contents.json b/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/Contents.json new file mode 100644 index 000000000..18d7cebfd --- /dev/null +++ b/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "textinput_search.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "textinput_search@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "textinput_search@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search.png b/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search.png new file mode 100644 index 0000000000000000000000000000000000000000..4eefb38cdf08f3356af850ae8bdf538b1b147dab GIT binary patch literal 458 zcmV;*0X6=KP)<h;3K|Lk000e1NJLTq000gE000gM1^@s6A4o0H00004XF*Lt006O% z3;baP0004jNkl<ZC>4#8KS)AR6vofFuil_ji<6r<6txARw6{f*MA&SgsRT8&<WP<F zLdC5iLd?CXse*>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 ze<j&Xbc7nE&e$G<qX5pMZT4#+oIBdpxa|wA^^Ml=W476^rIb;+>6YzNBA#oFOSajs zY4{=~p4h&iFR(?^V#Q)<*<LW3eS+p(SL_;(EAne^uM$^E9g)1GrW)eVJ%EfVrIR#t zo_yX}Jsb`t6N!XmhA#(lx%5RK;7-uugHpJvlsr=k3Uq`gL^sS3{YeY0*2`g|(Qugg zN@Vfh!_5paY?228d8iH3aSjFs4cu-&?~{&v0yRXI>V3?_@&Et;07*qoM6N<$f&ePc A)&Kwi literal 0 HcmV?d00001 diff --git a/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search@2x.png b/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a8d16731036c8544af8aa30e3f2f98458f8e6e8b GIT binary patch literal 959 zcmV;w13>(VP)<h;3K|Lk000e1NJLTq000~S000~a1^@s6at+^<00004XF*Lt006O% z3;baP000AaNkl<ZNDZ}C-%FEG82--rK9#M^SyZOxRnQLtZ;Ob22pVY#UFeNB(uL4M znMG+3NCw@hvo=~yqNp3Kv`9;Z3X0I1h^RjxY8fi#ujbV4J3a5VZ)|R7o23WbcfRL+ z-uF50`@QEJnIah-9e1}2v3N!ZrGhbK4`_LqFDr`hC<^<;x#%;QGW+Z5>b#N=31PMp z?ds~vo}FE22HqfqFt4=7zO!GPvzGk)t?sI-sz9tQ@mq|b!{OMqv?QKGXfM%`*$@2o zBD43Z%6<aM2n0kC@DgCBk!?<h#|F*jjA~>%L+{B(L@+Ei0<kR+nZwGjVC8qP@})!p zTrO9R*Sk!WxCkE=i}<3dxrgfOYi1Mi4J;@aF5_q@GxiO}^2WyVUky^SXIq=26q!E8 zUp5vxV6$0IF~)S64<`eCGZF)F9<OPOk#A$8b%-%ujVVOLNxR)yL%3{k>P8qFhycOn zbTf+%XtSMr3DKK`38A-kcXww-+oR_^)cV3q4Pr8Bu4v<G=4P{Y9sC6}Q_<Mi#BrG~ z1eJ=Ak@cd>u+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?2<r<QE9u0M5mVvq?e{D~uoJ{XNrz>8u4(KAl>nWUJ2SIzNk&YK z|7n609dF}>h1-lI;@r5^YMsX^aV=yKO?JEESR5+_Kt5RT1)`y1t+E3@Xs4gz;(fP~ z<!ekeUDu!5?YBw@PiK&ru8Sw&YUomLW+r=-%rA01x!vx~b925ASm|y!__2UCMcH`6 zW~-e}6ri*7;U-<5z6>$?44T1#=%}h%N@dqnM35YuPG_#qr}yAngfnDwIDrPx1aBEr zzjLl*J*)sCM?o(K&$xiSFhu@4NG2L1NTN9DRgY)<EP}N_Xa`|Q#uS3LW@YjFi;H?c zL=PHVnP{A#;Q(Qi$1_=mYmN?LoQ<LYPn3?0<4OB$*kakxQ(If>!(5>f2LjWtQi)0h zDadLEiTeG&2jO?X@LiednhKIgB2mC2nwDEejkM;1sYKUBP$r7<K3g|IiRje23L2v5 h-UsO3!?-D~{sp^Xe6r9atNH){002ovPDHLkV1jmU#s&ZY literal 0 HcmV?d00001 diff --git a/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search@3x.png b/ios/RocketChatRN/Images.xcassets/Icons/textinput_search.imageset/textinput_search@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..1c9db2d094d48f7ad826836ebd8b5a78421251af GIT binary patch literal 1544 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 literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 7158b588c..398ad21e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15999,7 +15999,7 @@ } }, "react-native-navigation": { - "version": "git+https://github.com/RocketChat/react-native-navigation.git#1a428f14ddda77511676d0c6d863083ce6225e32", + "version": "git+https://github.com/RocketChat/react-native-navigation.git#024095e7679afa0b4e9475f4ffce45d9e50ca5ad", "from": "git+https://github.com/RocketChat/react-native-navigation.git", "requires": { "lodash": "4.x.x" -- GitLab