From 402403f9649fd70f5c968e64168abebd280358f9 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Tue, 23 Oct 2018 18:39:48 -0300 Subject: [PATCH] Update navigation library (#501) * v2 * Working on Android 0.57.3 * Drawer working * Removing v1 navigator * - Splash screen - Icons changed * Deeplink * Remove EventEmitter from CreateChannelView * Android search * Android notifications * OAuth * Fix search props * Lint and tests fixed * Fix android build * Improvements on iPhone X* usage * Fix detox * Fix android build * Room.f added to RoomView.shouldComponentUpdate * Animations on RoomsListView and RoomView * Fix topbar buttons on Android --- .eslintrc.js | 1 + __tests__/RoomItem.js | 2 +- android/app/build.gradle | 4 +- .../chat/rocket/reactnative/MainActivity.java | 39 +++- .../rocket/reactnative/MainApplication.java | 29 ++- .../NotificationsLifecycleFacade.java | 91 -------- .../main/res/drawable-hdpi/new_channel.png | Bin 867 -> 647 bytes .../app/src/main/res/drawable-hdpi/search.png | Bin 722 -> 664 bytes .../src/main/res/drawable-hdpi/settings.png | Bin 883 -> 711 bytes .../main/res/drawable-mdpi/new_channel.png | Bin 591 -> 441 bytes .../app/src/main/res/drawable-mdpi/search.png | Bin 396 -> 445 bytes .../src/main/res/drawable-mdpi/settings.png | Bin 568 -> 459 bytes .../main/res/drawable-xhdpi/new_channel.png | Bin 1031 -> 893 bytes .../src/main/res/drawable-xhdpi/search.png | Bin 715 -> 878 bytes .../src/main/res/drawable-xhdpi/settings.png | Bin 1169 -> 815 bytes .../main/res/drawable-xxhdpi/new_channel.png | Bin 1537 -> 1299 bytes .../src/main/res/drawable-xxhdpi/search.png | Bin 1176 -> 1314 bytes .../src/main/res/drawable-xxhdpi/settings.png | Bin 1749 -> 1320 bytes .../main/res/drawable-xxxhdpi/new_channel.png | Bin 1961 -> 1723 bytes .../src/main/res/drawable-xxxhdpi/search.png | Bin 1523 -> 1747 bytes .../main/res/drawable-xxxhdpi/settings.png | Bin 2363 -> 1602 bytes android/build.gradle | 36 +-- android/settings.gradle | 2 +- app/Drawer.js | 39 ++++ app/Icons.js | 6 +- app/Navigation.js | 19 -- app/actions/actionsTypes.js | 5 +- app/actions/navigator.js | 8 - app/actions/rooms.js | 12 + app/containers/MessageActions.js | 2 +- app/containers/Sidebar.js | 76 +++++-- app/index.js | 81 +++++-- app/lib/methods/getRooms.js | 2 +- app/lib/methods/loadMessagesForRoom.js | 2 +- app/lib/methods/loadMissedMessages.js | 2 +- app/lib/methods/subscriptions/room.js | 2 +- app/lib/rocketchat.js | 25 +-- app/reducers/createChannel.js | 6 +- app/reducers/index.js | 2 - app/reducers/navigator.js | 12 - app/reducers/rooms.js | 13 +- app/sagas/createChannel.js | 12 - app/sagas/deepLinking.js | 35 +-- app/sagas/login.js | 4 +- app/sagas/messages.js | 24 +- app/sagas/rooms.js | 11 +- app/sagas/selectServer.js | 17 +- app/utils/events.js | 42 ++++ app/views/CreateChannelView.js | 82 ++++--- app/views/ForgotPasswordView.js | 14 +- app/views/LoginSignupView.js | 70 ++++-- app/views/LoginView.js | 45 ++-- app/views/MentionedMessagesView/index.js | 17 +- app/views/NewMessageView.js | 71 +++--- app/views/NewServerView.js | 22 +- app/views/OAuthView.js | 40 ++-- app/views/OnboardingView/index.js | 84 ++++--- app/views/PinnedMessagesView/index.js | 17 +- app/views/PrivacyPolicyView.js | 3 +- app/views/ProfileView/index.js | 60 ++--- app/views/RegisterView.js | 45 ++-- app/views/RoomActionsView/index.js | 39 +++- app/views/RoomFilesView/index.js | 17 +- app/views/RoomInfoEditView/index.js | 15 +- app/views/RoomInfoView/index.js | 81 +++---- app/views/RoomMembersView/index.js | 92 ++++---- app/views/RoomView/index.js | 162 +++++++------- .../RoomsListView/Header/Header.android.js | 43 +++- app/views/RoomsListView/Header/index.js | 36 ++- app/views/RoomsListView/ServerDropdown.js | 52 ++--- app/views/RoomsListView/SortDropdown.js | 2 +- app/views/RoomsListView/index.js | 209 ++++++++++-------- app/views/SearchMessagesView/index.js | 17 +- app/views/SelectedUsersView.js | 100 ++++----- app/views/SettingsView/index.js | 73 +++--- app/views/SnippetedMessagesView/index.js | 17 +- app/views/StarredMessagesView/index.js | 17 +- app/views/TermsServiceView.js | 3 +- app/views/View.js | 16 -- app/views/index.js | 16 +- e2e/05-roomslist.spec.js | 2 +- e2e/06-createroom.spec.js | 8 +- e2e/07-room.spec.js | 4 +- e2e/08-roomactions.spec.js | 8 +- e2e/11-broadcast.spec.js | 8 +- e2e/helpers/app.js | 4 +- ios/RocketChatRN.xcodeproj/project.pbxproj | 71 +++--- ios/RocketChatRN/AppDelegate.m | 11 +- .../Icons/back.imageset/Contents.json | 23 ++ .../Icons/back.imageset/back.png | Bin 0 -> 409 bytes .../Icons/back.imageset/back@2x.png | Bin 0 -> 718 bytes .../Icons/back.imageset/back@3x.png | Bin 0 -> 1117 bytes package-lock.json | 50 ++++- package.json | 5 +- 94 files changed, 1457 insertions(+), 977 deletions(-) delete mode 100644 android/app/src/main/java/chat/rocket/reactnative/NotificationsLifecycleFacade.java create mode 100644 app/Drawer.js delete mode 100644 app/Navigation.js delete mode 100644 app/actions/navigator.js delete mode 100644 app/reducers/navigator.js create mode 100644 app/utils/events.js create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/back.imageset/Contents.json create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back.png create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back@2x.png create mode 100644 ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back@3x.png diff --git a/.eslintrc.js b/.eslintrc.js index bec3d178..db0ad983 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -129,6 +129,7 @@ module.exports = { "global-require": "off", "react-native/no-unused-styles": 2, "react/jsx-one-expression-per-line": 0, + "require-await": 2, "func-names": 0 }, "globals": { diff --git a/__tests__/RoomItem.js b/__tests__/RoomItem.js index a2dfc92f..f9117195 100644 --- a/__tests__/RoomItem.js +++ b/__tests__/RoomItem.js @@ -12,7 +12,7 @@ import RoomItem from '../app/presentation/RoomItem'; // Note: test renderer must be required after react-native. import renderer from 'react-test-renderer'; -const date = new Date(2017, 10, 10, 10); +const date = '2017-10-10T10:00:00Z'; const onPress = () => {}; it('renders correctly', () => { diff --git a/android/app/build.gradle b/android/app/build.gradle index 59bf1411..3020cf6a 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -106,7 +106,7 @@ android { ndk { abiFilters "armeabi-v7a", "x86" } - missingDimensionStrategy "RNN.reactNativeVersion", "reactNative55" + missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57" vectorDrawables.useSupportLibrary = true multiDexEnabled true } @@ -215,7 +215,7 @@ dependencies { implementation 'com.facebook.fresco:animated-gif:1.10.0' implementation 'com.facebook.fresco:animated-webp:1.10.0' implementation 'com.facebook.fresco:webpsupport:1.10.0' - implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') { + implementation('com.crashlytics.sdk.android:crashlytics:2.9.5@aar') { transitive = true; } } diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java index 169d7a9c..81c94210 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java @@ -3,15 +3,38 @@ package chat.rocket.reactnative; import android.graphics.drawable.Drawable; import android.support.v4.content.ContextCompat; import android.widget.LinearLayout; -import com.reactnativenavigation.controllers.SplashActivity; +// import com.reactnativenavigation.controllers.SplashActivity; +import com.reactnativenavigation.NavigationActivity; +import android.view.View; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; -public class MainActivity extends SplashActivity { - @Override - public LinearLayout createSplashLayout() { - LinearLayout splash = new LinearLayout(this); - Drawable launch_screen_bitmap = ContextCompat.getDrawable(getApplicationContext(),R.drawable.launch_screen_bitmap); - splash.setBackground(launch_screen_bitmap); +public class MainActivity extends NavigationActivity { - return splash; + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + } + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + View view = new View(this); + view.setBackgroundResource(R.drawable.launch_screen_bitmap); + setContentView(view); } } + +// public class MainActivity extends SplashActivity { +// @Override +// public LinearLayout createSplashLayout() { +// LinearLayout splash = new LinearLayout(this); +// Drawable launch_screen_bitmap = ContextCompat.getDrawable(getApplicationContext(),R.drawable.launch_screen_bitmap); +// splash.setBackground(launch_screen_bitmap); + +// return splash; +// } +// } diff --git a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java index becf7e8d..48a05a70 100644 --- a/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java @@ -13,9 +13,12 @@ import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import com.oblador.vectoricons.VectorIconsPackage; import com.reactnativenavigation.NavigationApplication; +import com.facebook.react.ReactNativeHost; import com.remobile.toast.RCTToastPackage; import com.rnim.rn.audio.ReactNativeAudioPackage; import com.smixx.fabric.FabricPackage; +import com.reactnativenavigation.react.NavigationReactNativeHost; +import com.reactnativenavigation.react.ReactGateway; import com.wix.reactnativekeyboardinput.KeyboardInputPackage; import com.wix.reactnativenotifications.RNNotificationsPackage; import com.wix.reactnativenotifications.core.AppLaunchHelper; @@ -34,18 +37,29 @@ import io.realm.react.RealmReactPackage; public class MainApplication extends NavigationApplication implements INotificationsApplication { - private NotificationsLifecycleFacade notificationsLifecycleFacade; + // private NotificationsLifecycleFacade notificationsLifecycleFacade; @Override public boolean isDebug() { return BuildConfig.DEBUG; } - @Override - public String getJSMainModuleName() { - return "index.android"; + // @Override + // public String getJSMainModuleName() { + // return "index.android"; + // } + + protected ReactGateway createReactGateway() { + ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) { + @Override + protected String getJSMainModuleName() { + return "index.android"; + } + }; + return new ReactGateway(this, isDebug(), host); } + protected List getPackages() { // Add additional packages you require here // No need to add RnnPackage and MainReactPackage @@ -79,11 +93,6 @@ public class MainApplication extends NavigationApplication implements INotificat public void onCreate() { super.onCreate(); Fabric.with(this, new Crashlytics()); - - // Create an object of the custom facade impl - notificationsLifecycleFacade = new NotificationsLifecycleFacade(); - // Attach it to react-native-navigation - setActivityCallbacks(notificationsLifecycleFacade); } @Override @@ -91,7 +100,7 @@ public class MainApplication extends NavigationApplication implements INotificat return new CustomPushNotification( context, bundle, - notificationsLifecycleFacade, // Instead of defaultFacade!!! + defaultFacade, defaultAppLaunchHelper, new JsIOHelper() ); diff --git a/android/app/src/main/java/chat/rocket/reactnative/NotificationsLifecycleFacade.java b/android/app/src/main/java/chat/rocket/reactnative/NotificationsLifecycleFacade.java deleted file mode 100644 index 343710fc..00000000 --- a/android/app/src/main/java/chat/rocket/reactnative/NotificationsLifecycleFacade.java +++ /dev/null @@ -1,91 +0,0 @@ -package chat.rocket.reactnative; - -import android.app.Activity; -import android.util.Log; - -import com.facebook.react.bridge.ReactContext; -import com.reactnativenavigation.NavigationApplication; -import com.reactnativenavigation.controllers.ActivityCallbacks; -import com.reactnativenavigation.react.ReactGateway; -import com.wix.reactnativenotifications.core.AppLifecycleFacade; - -import java.util.Set; -import java.util.concurrent.CopyOnWriteArraySet; - -public class NotificationsLifecycleFacade extends ActivityCallbacks implements AppLifecycleFacade { - - private static final String TAG = NotificationsLifecycleFacade.class.getSimpleName(); - - private Activity mVisibleActivity; - private Set mListeners = new CopyOnWriteArraySet<>(); - - @Override - public void onActivityResumed(Activity activity) { - switchToVisible(activity); - } - - @Override - public void onActivityPaused(Activity activity) { - switchToInvisible(activity); - } - - @Override - public void onActivityStopped(Activity activity) { - switchToInvisible(activity); - } - - @Override - public void onActivityDestroyed(Activity activity) { - switchToInvisible(activity); - } - - @Override - public boolean isReactInitialized() { - return NavigationApplication.instance.isReactContextInitialized(); - } - - @Override - public ReactContext getRunningReactContext() { - final ReactGateway reactGateway = NavigationApplication.instance.getReactGateway(); - if (reactGateway == null || !reactGateway.isInitialized()) { - return null; - } - - return reactGateway.getReactContext(); - } - - @Override - public boolean isAppVisible() { - return mVisibleActivity != null; - } - - @Override - public synchronized void addVisibilityListener(AppVisibilityListener listener) { - mListeners.add(listener); - } - - @Override - public synchronized void removeVisibilityListener(AppVisibilityListener listener) { - mListeners.remove(listener); - } - - private synchronized void switchToVisible(Activity activity) { - if (mVisibleActivity == null) { - mVisibleActivity = activity; - Log.d(TAG, "Activity is now visible ("+activity+")"); - for (AppVisibilityListener listener : mListeners) { - listener.onAppVisible(); - } - } - } - - private synchronized void switchToInvisible(Activity activity) { - if (mVisibleActivity == activity) { - mVisibleActivity = null; - Log.d(TAG, "Activity is now NOT visible ("+activity+")"); - for (AppVisibilityListener listener : mListeners) { - listener.onAppNotVisible(); - } - } - } -} \ No newline at end of file diff --git a/android/app/src/main/res/drawable-hdpi/new_channel.png b/android/app/src/main/res/drawable-hdpi/new_channel.png index 707f021cbca4ce70514b3f9ad9f8e0a9ee8b2b65..80faa15a574d9a0fc9d6ed89088af8136f71762c 100644 GIT binary patch literal 647 zcmV;20(kw2P)n3jF_{caKcggDCm2LTCe6^FqlPlO_)q+M5CrE*>(oyW|DtPi z_(k**EucqeM0S%!OUTDoDT*0|?h~!K39Joi4twQUUqE z>Hyuo4AQ*RXl53BL0G8x)=4jN+?Sy|NRty`1Yg+#KKwsQ*r-3K3e3*SrBaC2RRpoY zcjyOt8r3r~i#|pIauS^ALT8k$1L74yx)}k9y^iW{=iy%$)~@lt_-vHSfO%Dr!gjUC z8>^R7Kgs$uxXtN(slX0m1IT_Ma;&+heNvyISUFg~W?cc2pOtR(#Hp+qB(*UZ&7To( z)y9MB#hs!}^pz*tl0dB%gVDgAN_ril4U|VeffVp{TN3Os-9}NK=Erq+PAC-EKO~nN zf&2k5jZeeu;&frn1Sa>67igOAYZ71X4#JX`Js_(WwQ2V6s0u(2x-VOfKsJCRz+lyQ>f@bxB-6oz6<+WfouQ(002ovPDHLkV1l{V9&G>s literal 867 zcmV-p1DyPcP)6bUVopM-T0D3Y{R?VEPtt=o zFJ3%Ztk6pjz4RtpstDECi?$#V3M%TU2F28mVns=l?2gZ~*;zK5Y_i+z1`odQ*1Z5-ED=|MUhgDqudzK2fP60g%BpRBZ8y()#`L+K%D7K3p(ydmfniYwQ=#gSult& z?<{cMV<)MzAobZ*m+uFZoUx!fKgFeD}tn5IeH~aGNA>8QqwM)?hmU^#C{wuqh+3m1(eK_PKEthR6g- z*%@|&VgeVu$+0#DlmN;k?LA(nDCS76Ix}kLD?#VIxzUj)ShG3?&5lJ+$VEnN(+|`k z!N;&=S=1w&_MAS^5^6$b0yRc}0Jy-( zo(vp2x@E|!Zj(B|6Sz5d6@ez2ZwBk_0L`cRDfq9}`6;Slw~S-}^#ravGj#>*34GmW zrlB)V%uWc!Mux1y5{i{fSVFOr2}7tWWI_^}vbQ&q1AP0T)C-&DHZij-&yfoySyO?2 z`I$hOX?3+hny-Urz#y;%G+u2iQU?1)nmQpWd#Nd5ofaV8iMVGFogEo zF~w%dq9DV(fe4`(pRT@yq9Q{=LC+BPSov;%K)+3p+sN8jpYq#*s-N zLMZwM6^Ym-=$6G!L>2f?NOi(b|C1f!)zzeFrup zadWL;Tvvv;^)6lN{N9&RlO-dSbL!2lRBn{3);|OAM(DmQu2q*EG8kBYlHAG3Lwd4u t91o@4ZeyHdLu%!1L(5+pO|Q7?`~}4;njkCDw24^XOfet<;f2Pkwz6r!V}Qb_m# zZW|#Y;=1E`%{iN6W_D+0ci-fhbLM=vxt#rGy@nL>eZLX69_WQHxPgM_c|K$9qJRhU zunR8`)*TDmFi}%A5{$taxGHi|e}HFq(aiT42Qn8WXc<0Ck~hXpn1mLajCniE!4CX@ zqzjp{K8YjFDr9xQq%SE5pAon)LGO%XNihv_0((bW%yWrB#sOd5=`l#Rpv@F7jM@zL z9qEoz7qS_N>3}Pcw(WXk#Hd{-=^Z6Lx<~@EKY4h&F;+kn~9V`3$Bt z9o5RDZi+Ss)8MFL>UC06Fw}gs1=Kf-ZZ;LOSJKIqw7JGA=lv#5PwL0bp<)HNH;dD* zvC6s4aUPD(na{+&F)W51f3lqOWOVThCgvzM7N-f2)KrIKol-0kyQC!O6}rlXVAN+q zQd4a&>sZD>%n&?+v{A+_SjJ%h^qG*;-kJ1nOO^!6`gI5wv49KZI5Ix9d2szF_J32F066roVL+Rkwl_t2o9` s!6(i;9drGsrl1PV)<1hpM)!_>0j?$iIUrftrT_o{07*qoM6N<$f_*J0cK`qY delta 717 zcmV;;0y6!W1=0l}iBL{Q4GJ0x0000DNk~Le0000U0000T2nGNE0KKH#43Qxde~C#% zK~zW$wU*CI6;Tw&-}np3MWP!M19xtOXeoq2X^N|a7XA%MShROh8@JLzmw^zpYSAVl zbSEM7gNTs!X^V^&>B6Gv^Bw1m^WM96=Dz26;Nv~-p6@wl=H2VPFRBGv9LGIKFToHT z!d?_bKN+hxn)SmHY{6e>v~NtTf5BuW-6_-s58(hp?G?iDwjY4x0eF+^_6vU<=3o?h zVGyps1K0+$Bb>D zV{N3;H7zVgkQ$HEdOsLDy?TOMsc_vZY9T! zv$|F*Uq7qakzQ>UypcuDf8emOv}f$b`s^)*P*ur{1A=G^;(=F3qn&1(TdsjtLoAAg^rxA{WZY7y+ncmZAm`BbG`~lfU zSQaY83rWh!ADuk5GfQir)tg$=Px$xXVrilY5HErqp}J*s;163oe?Kzc3ED`urnvDt zcqRw?00a4)LLc!o=sq>mfv<1iDXhQ~cmZ~m;wHAi-wUAp<7B3d=6xhCgRZtB(?e}S z-A3Z`Jb0T&g|AMKWE}?AU<$P6+3#w69S$!!g!UPmOwE~m4&Cq(Zl2q3af;Vj*_+pi ztNzf?{5_w$s_x;fBiGP;%qu*P0L`z$ZPoq{S$a2GInd4u00000NkvXXu0mjf@?BUK diff --git a/android/app/src/main/res/drawable-hdpi/settings.png b/android/app/src/main/res/drawable-hdpi/settings.png index d537c71bb83802a909fd221aa1a4a9f0f6f60b40..df79dd7b1642b99f6a9e4284a58a11abdf97ac0d 100644 GIT binary patch literal 711 zcmV;&0yzDNP)PVIk*RVV#4#gDcS*1vVe16{>n}!#R{-yI?bT3!fp6td?QXu z>g5haQsg1(pvkI2Z-*6V@I3FU{KQCKgBI-f@@0@^>KymuNge}NrozQ|c+w5XuwGIv ze`n|+cq<1|2aVDcJHk5}lM^}jPx(9UF?L$D8W~7~3eX9ZEWDu}LQPPKV(@sp(Y0<(2aJ?~cSlf(|)>`&nyl z9&pmm>ltx7QJ+A+42$6Z)}fQ+CpC`RqhBC8?z0}Zl}OO{ra4lxN`rk|N^h96M?^kz zqR+6dhO;!dOQTxBFP$2RYX$vhoB>@;)9C+O{sO6>qhR66)pP&=002ovPDHLkV1ksKNP_?X literal 883 zcmV-(1C0EMP)sC~4CJYLi41LJNr& zZEO)iwaJCM7Dc4lA`(Q17QLWNFS4jjks4*$6SD_$^Y;5+&bb`Mxs!LCiT?2OJJ0Vs z_sp4Zj$ToSGMUT-x_7_|sDKC1=6T*LZ1Q3OYvBd>^__;g#8*I&nefF#AHZIQv(RPQ zWVR=|5CXKCU_IDs8;o)pTs3_ku%)(dG}gnBhl2w!*Ku@^avAL5`CoL}ix@wR`gLa>%D8LEagxa}kGr;3SnINGF^DcPBPcSDzlm6Ud24n3Ux%FSwvA{vKjLs~#yGlB+; zux%^jbbk6_Gw0x~nb(Ey-IQm87En)J*;Z1X(3weV0afq~;*bvRFyXi%kMrlW7c2h{ zL2%X@=I*Zz`gK9d*FLDh*;ka+=xeACQ&iP3u?YrX5nJ429qE@+R(f9rt#BA5Ev%QZ z+Jw=IK;@w4FFk_|Pzv+m3P^few2p27vl>zF=0p4CtpEuwE0j%wf&1nCluCB7-~RtZoIHix{Sd<^QK4XjSa8`bBH zC^qHl>Oa}-rhe3{LZ`mU?JKnG;b0t-8o|BZSwHJ3D2MFjeLiO!Wtpe<|08H|x+3;L zAyS^u_3pHsgJ$#cpw40s1dY(uJi1&Vv$=)xOvR`yCQ# zA>6kS9jg>w?;jtf9(vKW4hfc`tPHslro$uX;CU8$4w&|z>JRy&D8D&VfDixx002ov JPDHLkV1gxMnS}rV diff --git a/android/app/src/main/res/drawable-mdpi/new_channel.png b/android/app/src/main/res/drawable-mdpi/new_channel.png index 62d1ce59c0e7795d05fd78dbed8c5af0fdc7ac5b..dc0571d03495a1b4b83eba7f1202283100d72b2b 100644 GIT binary patch literal 441 zcmV;q0Y?6bP)|+qPrkYE)Gu4#X%e#9IJKn4=7a!{{*)# zF5>9opdjulilY@2K?fm&i=~5T&+m8Km9)8N29FHRgM81n;m7R#M{O2ZZ9n7P$)rHCJ>`9&-KrjAOmw3C>{- zW_uX(BJO+10pk{&!7v=ct3--*-w?_?_mZkx#Ul5b@Cb^E7nMT-*VHBPbu`zl!kEq; zX&w}DKTA^WlDIbpMY$wdqTG^@2uVdtB<#i}_9Xpt{KE{E=K7tPckNruym>p@ILYtbbMN`O_rCKoV;ach zvI^%IcCF}M^0^~t;iZpKs+6^12*WCjo2GfsXc%)*dl82?WF*sTsw5HUJO~QGKG?7Y zA`vJM7ptow*d2|jvz^1o8FyeR{WfUbY8y%F&}GLTpoLvWS?fpt4&R{9U_s5V+sjOR zi6n;;kkb#kgPD(lNKL?P$W$jVgl`0bumR(&^)hNXca&V;Cm@mRT<-i7nxGhd!X5NN zJ6ysln7#@S@KBre1z^APO9t8QWKP4POxZ=&j*NNLY6ka2Q}OL+ob>7pBdz;;(~BQClr2wfEM0YvD#6#54C2`pW^@(FwiDdMJX z)P=4@#JaFfzmsO-Bu$!J_&PK9o;jIjZhS8#j4^f6J!rxLp5V^+{V&G+z+ew1@C%A> z_z7ml%o(qsw$TZ}3A}0|B-6xhLeqmk7}{JglBk2;$sktPus}P8cPK?N7MGwnjFLGMv|Vi=+Eyw@UGbc& zeYVL0<724N-XLV8KKVc5DxddT4h5f9C<8UyHpgt)WP$NG)J!8S20Ne|e;Y-~oC(@n zn61f3s$gpyq4kkdm)S)B3;+NC07*qoM6N<$f-OP4NdN!< delta 388 zcmV-~0ek+v1B?S9iBL{Q4GJ0x0000DNk~Le0000K0000J2nGNE0FaC=wUHqce?v(` zK~y*qt&}}V1VIpmw*@uWP$MzaOfb{Tz)QHf9%PQBnP4Co8VG`T0L?^iAdKJFKi~|# zV=rsL>td?jdtI~JJz-XfzV8F&6L1W6z!!K1k0FGf+!h*vOYjD2VcotPP6wnpxBPXYcH2Ve}f7q;%t>?ZxP3x1=xB^C3GaA?AMK-TL?#*a8oxhE* zQ=M!ql9&1FhEq~F1;%15oO+fYRp@{7HhA!87!N5muZ3UqQK+Hk%=k>Hc`f{+k3tPS zw8m~q&1>NoeH3cw4KU79YF-P!=#!8i_J99nZ87ya8C}|DdX*R1wR5PHTBcKa@>jVj zckuU@ZcTI5yL^l00~}46I%(6_2HpX92LsdLwaHsSvi|4yP*ZsKU>ltCt$ZP`7n*`H i>EU{AS)EN^$rnGvT(5YZ9k{Uo0000+Le}YLw zK~y*qjg-$TL{SvS-!+rPn4-i2nGGvqp;#$4vUnwSO4%q&QWk9N%x=m*U}T|WCuOBk zcC(TVnT<>oP0d1@gs#uw-MZt>d-J~a>6~|d-8=WaJDz7rYn_HXRN)cUtaN;r#v>42 zLh0AjdMVBXMe#6&r$KQBd#;r7e~G8@pP>a_AxbMrvL8?is(P>;43TCT)Cv_adEAD2 zuZuDu2cj~(!8gp*6~%d=DFNG1gbVOZAAT?FLLL@DnOtm3lMJbV=pV52#elXS`#Ejt>30vte?uAKw}rl4kR;RS!t uDf}034o&hj_G9KiF7yfi8U|Htq<#Rh+k}_b7@h|J0000J5lT9bbh}-WNk;ZBQdg8!`cN;1sX)U8_k?QLL!9 zcO%fN({1>5+~z^A$|TdMj64pta0L4D5|Xq_pyTNjI&Rxh0!(y!Hb5(!0&SxHNgkj$ zs=>k{jiBL{Q4GJ0x0000DNk~Le0000a0000a2nGNE0O0_bn2{k9e>6!% zK~z`?y_n5!O<@qm?`?^VuLeydBtlqdNl5Ihx}ghJBzBVi0UFYZk`N&__y>fgXt2S8 zMOTfaNQ4bZ#Yba-L>iulBYB8%)B#C&m715wp41NR4SEj za1Ts?NiYJAw2;C=D;oEMX^hhxWEX7FY$#yyjrF_0H!K=u<3g|LW zefrtphu>hjvV0`uV&mk61R+2%Z&B6Z1FJ#sdH*VZu5 zC#!kRnVS$b7LCbBgqqwAf1r_hIa|wf-du#(pP7uBl0V>j%3L55C0M*+LTqd{cnxx{%h?ucK*Co_4GFb>gW{V>& zv-aPCf6`_XZ+uL)IU}JHj?i_#H?*^oA+H_W0Jni1&gRSEFi9L4e?c9@O}au7wk9F- zlOgwj6+|Dm8ddEAiwF^017@O}kFkwzK|C`VZ6p6!R8y+ol`83QlqLrWE{_r(CM8mE z0RL4NJICS6pP^mo|E5liX>!;FV}u~0@%4dIE@w$(RHrT>8Y-q+f#eq$!`Fj81VnTz z@UmnwDpr>evHhS!e@Te0OFBcZ)gnQfLPmAd2>Ee^XKmfZMuEoohV>B{6!ZkiG=7l1G*saw$Sbg#YIS$LVdn#pKyr0 zPsnYitJaI6@k=S=(M?jK%fCK^g$g}ZfoH&EuWwk!tB>2QPsllk)|ld~qhJ$M8#Q#D zw$H~a?0rmRHdY%_cZ_Zewf?}-9V6lw1p9!-HwB6kUE9Zjjvk%9zuEr)=X~(mYz;qI P00000NkvXXu0mjf`4O1p delta 1028 zcmV+f1pE8_28ReCiBL{Q4GJ0x0000DNk~Le0000m0000k2nGNE0B+uzY>^=of4)gX zK~!i3?OIJ})IbzYw%byQKdrKgpx{M{2(4NXK|y--TCsW*Q9SF(n-`^cQ4oLdP!EC^ z5!8dIv{)(iQr1J2f*}5Y)LMT^QEct*Ch>dOec9RBY<82)BoX@H_07CD^WOJmCfWQ{ z*Aga{WvvCI{m`AzpU_vju4iFge?@2Sl`(5;XPY_DqM7U17lv)0NuM7a)Qv`R0sLW% zy8+!GjemmvDG-^F{Kf9$*M%1a!!nT|bh$QR?QktBIZ=41PVw)E_e#?=?S8g!x6Z_- z!Z*ird#j2{4iu2VzlHB2bQrn}W%+?d1&Q>oE&2fGdpBn;E-Y%lxF3c!fArP`uRH)W zg!nmO%p!Bm?^&tKgZA~-G^47UDVXd~E5&a?XP*EPD(<+yT zuMave41C~>5uBFejG%$KGWb#Ws=zZ8#P&h+P?l|WjC*8xuxg7^GI%Qg1={UZ8}WNI zup~!mY|3kbiWrK^d%jSrf3pd83~?_K;`zDCo>yXH8x?@XoG2n6dD`;_p}(Olt}B6H z21=JwmfevOKvg&J_MS)V0JJ6v#JZqaD9bb&>BtD6SWWP}LKx#p0)82M0lxBY3p$Q) zvM?=@*9Fh@Fjgip3*Q?Dpk-h3Pw;r#3c=8?Lde}Pcm{aNAP_0|un9yCK1>3!fDem6Y~aHn z5G(j*3B(Q_1d8_)#Kfl%cubL>q>kW8DPJ!Y=F-#E2(+&>mM-BZG52<UNX0CV4kq+ClZp-3Cn>iS81C;nk0g^H47#PT<<86e1u<{X z=K;ERXPH7n$&u#*UN$HJ@x9Hoif!WsT8srHuu(`dC$wFgSbJZ4-Ze=Lr*Z-+!4oH? z1XP1>hJbhud~;jfD8N(QpuYp+InWKT8RQ}#DUl<)9l9Axf8u$aFAfRzHw*%o>y%ao zp6dEO2XM6Ml{_8sMnoxi0uW>c=mjg0pqHz(|LHQyD)(odm#`S5LV^&WuO@8(W~u<> zH>Ym`08ZP2G!f$a97qIe0mKRc;b6qlTv>rw4!cnF|6hT{T=|lCeKg-MkV{09K0J1I zXlodZp{3@zT5a2@&xZK*4|fX575W%Y|4|?kNM%17XAQHw3Suy6?Pt@*_?dqFQx&e^ y@!WA*i9gHbZ^lVk2l|fve$H5;|0t*e4#z+KJtk*xmWu)a0000E|pahQii_q@@L*NEj1>VYZCOy9?pI{}k z=oRtjz;LJ#+zId&c$*51Pw-IiE3-xadV&zxA+8TR0A~MiE(eBJX#`xgXj&z6p(%kB zG8(s?=1=^vm4>L@&z0e$xn5RVh6mw zEkfc7mQXn!?MuuS-`VRVx1GP^T0*L4WQTeBd#Y|nKJ89Od^7C|wFxPi<7vc##)h2E zN9XUjmXInvvy0|*p!M1-nflzO)A0*S&1xpGjR|xQ0Tp$!??5GSwn0$YX*Q9~Y;6YN zJ0?y$-B1Nm$pxsmDs0E0bpqJx{8c|Bp>Bw(fz9qW3<@A1rn2UpiPFvlgjFe!M}TTz zGZj}+us{uwSS9n+#Ax-SV4#MU-z8wu3CLA()t?_#KTC{mlV2ISl~-V}p50)Cpedjl z*i6Nh>Y4nVfi4{FCOyFR%-|;M1-;CqGH(Y#D#-7F{lM%C&SS6y)_~3?9S3>?>!qjn z%z5B9gm{WO1I%9HjB${Evw@(Nq=vx)XvMUfX21X#1X>wyxhs{MAS$Ckzs*$XHL@QN zra@wB(+^52^OmJ$h^WfWap3=K(5bD1QfJc&cpg`WxJgq|j64Bn-Arq@&k3~UhOSgw z7il&)BdO(PbqeLzB&DBV{x%kRtzfY@spVafOjg$a0mfh-;D9WmKL7v#07*qoM6N<$ Eg2s%P0{{R3 literal 715 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 JPDHLkV1kWOXn6nt literal 1537 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 X<&9Q2P5m+d00000NkvXXu0mjfWwMU$ delta 1174 zcmV;H1Zn%C3YZBYiBL{Q4GJ0x0000DNk~Le0000y0000v2nGNE0BSKv!I2>oe@{t7 zK~!i3?V3GL6+sk+xe`q*#1Ku49|dBJSjcaH!UUCAm>5E9LkB;=KVWNTNkwO&iI7kj z_zx7K5iLXuF)=am10?vo_wFvs$?Rt5?k8DK^0IekXU=)won?0R%nl5+%sNHuJvcZR z#ODaO0j_{E;57IJet=J46MXD+e>(ddkDnoO2+V*b@Ev3^wNrcb8#)%MAvy_Gz#ovu z)L(uk8z~%O<6s$-Ve&iP2;mSJ28&=Hlxgy>IKxE?&pc@ne2m~P!Sxgpdz@PX%iulu z48DLJa2{L)m%ue}7fgVYAj#fw;y#nxo1_M%)FXBZq)kTaoSy|}eF&(1f6!0;lT2}^ zeEjBWORzccH-XIt_0zcm1fAsv$e+s;f3Ao)sXhpzk#&jPqkbNoNOjMNDw>BE(O)y$)G`Ba!3F2`$V&YZo?2SV` z60GTpS0XmN1d0b-BfS{5f609$fEE?@IjjGvQg7oV;))CG|FrlOvpB(#YXvo*M5u~- zIB6oYre}RV!=tXIH9aZ{osE52Dn4*Mvhv+^IMo8)S|QI_Uy-&wq^2z~$}6^A<}#i2 zif7+?)-yY(pdtM1icEaHUM=Id>K!DC|ElJ)|6~(qA3w*&Bfog>6Sym z+WxTCRKYG@F8)?ew;T%AUXQg=ThWlRQoPn&{B4`VGOLx=th6H4h;GGNI=rPN{6(a` z_d`1Z+8t<;iI3f>u)MSJWrSPtK78bAAi6yn4=G1*yK-8Ae`r_Ma^c9WP;r5QC?{(f z2yRziIuPBSorgehyY`j>(e2qilwcW?MIHOY^Tf4#NmOHsA4P)J`kfZ>Z-KACn)gV1$tRi?C$1HlCDKn>eiJvlIaiNpT_J*N?Opk|mbHow zgzMFE22}PnZNH*3Pg+^;d5CCZOM6w?{?LwwlH|%8Y%7(j?BX_14(dbR@1-RgKd*sJ z-Ih0z5v*40ccg12oj3S@(OiL;mHWubJ6d`ABQoic^_z1y^Z0IT-xSf08%sJbu>{c% o^ZIRcf8D}h4Qzuap!*2^2QN}8akYU}djJ3c07*qoM6N<$f`T?RbN~PV diff --git a/android/app/src/main/res/drawable-xxhdpi/settings.png b/android/app/src/main/res/drawable-xxhdpi/settings.png index 501b6a4b4825ecd4742eb075add52ebbe68a1fb6..0d3c6ad45c027f0ab24dfe4402c902f21200de37 100644 GIT binary patch literal 1320 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 diff --git a/android/app/src/main/res/drawable-xxxhdpi/new_channel.png b/android/app/src/main/res/drawable-xxxhdpi/new_channel.png index 7086e7134cf8d7bd1cb24dacd906080435489e56..1091224d22ccd52cc23de9bcdeee36c15e7107b7 100644 GIT binary patch literal 1723 zcmV;s21NOZP)&pqeNIp4i=?=UlO`}=AxQ&UqbC~+Ma1XqDe!BOxd7zOY5_xJzFadrvB z+y=%$nw-R7E0{lP7^2umz!?xCUtzqs_9Ri{vmva+Y!B1FrnV$eTs!0#u0&G(W>N-n zj$<Oc6&}sRV6HNQ7PD>_32miF0voS3UF%|;fOnN5r$LMmf7j#qBL`yv@gX+y3kib^OBNw zjp36vbKT#FF1Nbh9v42nsvX$!I|za99^=Um%d zgO=_}t8JR6v@kyhT=E;rT$g}}UgVKn(q_(xs|ZW?qAwq8E&<0ZThshT1noCH>!=0m zZePG|>&Yc;&3RHeVYk_;!PR`ll{<#7xs1{6QqVsfgP*$s^+HE(X)~umj>!%CjX(>u zPj)j9-P6&20{H9)MbKFgmtk|lULHv*h#ZqEevbjywr%Qsi^d#~M9zUmJmV2N?ldRs z)scL1kz?}3|6$RO`5B>$A@@N#(EtMYJ!-YPP7vpAYt1oa_>RQi zW!2|u&LpQS1d(k=W{x3?_#1$4K^9qVa>_su$(5I!UK3P$a(YQnCCKR&L6sq=7X(#` zoNf~YIi23h^Y2z7!zCo`lEUwUI7#)$5n(QN&=pEhWy$FVL6s(_(*#wXoK6x{3vxO| zP;JQR1VObTr)Yv|M^2Fh)smc|2!fo&AnAF_v{y4aZ-@}P_&emR32<_k1l=eSK(9}H zQeARHqNl1*f<`@YI!(^ztP{c5k>~YbI~WGbjoy32$R(Kx!ddUNU7y?@@}4xq05J={ ztcbZ3=(}hmdbzkJF&$$WJBARnF6L{xFC?BgVUPfYlcUoS_f?aeb=;SbN?-8zIgViZ zmeD06E@hfdb7UuLyV zlc|Mo+RO=FbLB{#(aN!#qYuswKvs+nV+G`d~&^gi>vr~eQi|G#n5h7d-(xepRD9Ky|QM7*JaF3(P zgIr$68NY(D2Y_bEN^lv-Dy1Pu3FjgRzttXJeRsW|v1MQ`2$fQiqvZ1v6X_~nVJ zTONY66NQL==b^1%q-YU8AL86=+dKsM)*n-gexPyyG)RGvpUprOk3j^n{@g;51CB!f zLiZ8SEE$886Aqh7s)7ijLVNa1&^MRPIHdenNd{7q4kbvxsoDZYAmrPW*tHlAA_!8R zu0_=WMd)$Il*2yHU9cle=~S+|5_eS*qbI!pUaDB)sAR&Si&P>=+jCs>e=9zy3rW;*C-uA&FS6Np)gPJm4NUxu4VJ_-nTX0JwehN7(NbWn>-IWFDDgk2+{+sFTu@8#x)!J zlzLV{P~va*wFdQJ^fw2+qyA3)Ss>%oxIT>>@z5Yh4UCU)(6#y|sN#A<+Np8|i*4Y3 z>h-QWc9g~?W-HWgF5C~%)Pu1Bptq+^Tl8fX{h{?Q0rf;d&!OIhn4kx9{{v**gGBxz R>MZ~O002ovPDHLkV1k`2I;sEw literal 1961 zcmV;a2UhrrP)b87(Jn0nqPM}exEaQ zz1^GHo!z_L?CkEqli8hl=9zgu-~E*%dr75|;)(!c%z#}5OS>AFID01m*!RK)U}s=+ zFe*jZZ?Lyu55uzFXGlg02>|MP*w-+XG6ts|!az2ShPAK%b9Q5UTk(ZltFf+SxsgME zs86-gsr3E3HyG!vc{I8Yn0pYTAC_?$>s9zaZx}|&)wuoNvSoum6b~0>%Vib0!{T$` ztNABgC92?z-1-~L?|qD4rZ82ia^31X%-VXcDgV5-GjpxVpfLFuMyBp8Ze)D`w-4i= zX(R^5J_tL6SP$V$Sg?y#xxB3FET350nbr=zU1DDPT)>}Eh`${^B!cakkUnsVkEbugUaIS#D>C6z&+Eerch)&atbyRe>_pR1(6u=Mo9O$A5Lo%*tU(bCjwan4v z6rmmD>vZJPHC^elBg2C69WaU`Dtk@iL>|;;a*D_f(sF{&aqP!NL%-l*{Bt~*r5-ln zv~*T?O->QmL1L=#IfI5oK#)6lAWQv|38$sAx@&UMdB_ClN5t3H}7d=JQl*Gzv@A zL6Z$6(J*|TDq^`30sc<#r{Qxuk8i5cAuJ}RknbRYrorc_BBrVcPVi?D>pmX0t`^cq zW1VA28?e--!{@0Ywu%S~GKOfkhffp92J=OPxyIUkgU|EE94aD+FgnAhNkk*5jV~aq zHJ0c*e4abzaw~!we42n@iwd7Sj5XF97<`^T=2R6y9Xl=67zBtl{G}Ek_VAZdfCN6#M*#?7`M=knpO`Ru zpK5wURe4X?<5*9_2tEedy@xxBe(5_|o+e!!gt_S*E_#=%MG|KY?Y(&K>k~=8wE2xM zSWoMU@)oN@VYc+q?oG?Tv$j|Fg@->Hs}4=vK-lfF`4-!i-^p^jEX4Qc^ZdnyymP#G zP?Q_qLLIYPtMJuHy8#XMt+pAj<&kfbk5X6A0<=q36-)TUB4`23f0%k%jRDzvxR+hr zZ_#B3qkOsMyI+lXM9>1r&VWhr^1Qd|0H3mpqyW0W50e1GuL1CK{ObgNDFl$+1DbLP z(hYu~0su}MCM2_#dE9{iu{OHh*AYJD800-bb^~ts@}n#KNC==a{GbJB`t@IT_(2OG zy99Z<0Y?G8wgN;2J_!)y$I-GgKzI`d#0YS=^oRZ>3rB?9;aRyAxQfGR1U{1O3t0;?J}5kQrcPkxC2K7myYn+Tvv z$|t`>0H46BhD`)eCFPS}B7jd|Rl_C%sFL!@j|33D5&6n7cBM~qMlx(RBmFz2vR%UI zJigak8doR$1kuC>>QFl?*yjj7@M5V_Sr>9PyUs8dg5Q|6f+MvjjB9`?%J3geEXYn zZ*nF$;Zo{n!{g;sL*wbLr+O-Xj9@#+^badv`oBEd(&rGtjXQ$ZS$2Vl(R7LS0i)L;zBpl0%00000NkvXXu0mjfnv|aY diff --git a/android/app/src/main/res/drawable-xxxhdpi/search.png b/android/app/src/main/res/drawable-xxxhdpi/search.png index 16e8b3b526fa94f2e3a41fb0544497a184fb3b79..c043dfa216e7a672371d0c602173502d498c272c 100644 GIT binary patch literal 1747 zcmV;^1}yoBP);p{uu(Yze84`-%OsP;PgcL{=*qt!h zMg0W|6PQpS4CIB~=&ex+-t;*Ini17h5=vMzXdw<5IQD^-)9(S#VRPnv_t|^x&y(kY zhjZp#YrXHY-o5uaYp=DYudjz*2C`(JKTG2zZD(g^7gKK-J_J|76>tz<53hq4;S4+n zr{HmT3?70L{r&xy`22r?q^yLa@H==3hT<#@_rqu4!miE|9he6Ha(=ev|L zL=3`DVJDoU!cMpvX1PlhZ!gr~5<)l^Q_D;LVngEH->@gvVef zY|9cSs4a?gB|9bO9qQa26d++E{Ob|i0?)uWY)$Agdo@%@)F&}&G)}ynJyLQH#9Ia@ zVl-W(Y)x{H8EO(JZa~)LZEXhk!A*%f0Cm@TkhVha=sp6Bc+s){1n&v%VGa$6-w#Kj zhaV|eltZYwE^w?#pl($Qx;m96C#o1NyDgEIZTq5ibNh~SZ-%Tfr;^9Y>l}I(K+T&x zG`zM!cA~uCMZ#X{fSj}g=voYac61-bJ};@S*m`mVy%*2@v5hM(ME4N%mRItbL=r$t z78W!KEcRC+hXkW>pCumK_b_-?-;#zCZ#`$MRjI~b(0>Q~8(PRVcH&X56BhJtmHX>M zQF%t|8&)TMvNur(yK<2;6eU^z6_8nT7e!A>h`NGCqDI)ZIO z8cD66J{oHG;X$MEZ`i&v>~%N2=Lmelv76>rf+!t^)+m2~)w)@;b-j%T9C?pBcGDb9 zkoK)Cyl)xnZsVe!MB_Jx&Q&}uR~dHG6itve^(=g88N0-$_3CzL&HJu)qxW|-LE7s_jY3!{jFVE3szsPdVSFZX^?J*b|1PKlaBa7$3Dj#O^~);+M)gOZic7d>Z{q$ zXo7T*pdIJi4PExPr{C(U*|lhbE*Lemsn^Z$^lQU15uypQ8YR9J6Gbb(lBfSnQYp~{ zSh^NFUZa{m@eq4xBE8+E%u( zG2C-`EfKDLy&tR&U>nJ-1SdLbYe9#XYCk7JAvh}`O;4QCNuX`bsLrQOcIoia z?VgV;cKm9Fth>gW=TKy#)Ff?1f_L(@sZO`OuCTSw7L+4=>D|hEeW0i#N=?!lu1>9UOyc?--hx2uf1WT!!#khSlkb`%-lLYwBvBUrW1_P@4qW zx6;;(b{{m;T>)1?tpr+&U6|+KUo5Y~ariEbo5cP>`Fd6qKjX7lkfcRWV@{ROB(OEl z;d=w>PKiU%ofMo+lr-ScI7J1N>D_7voHIkl)_}E3l73+{70^-JBB3RV?NE=S3(6a? zUP+Q#MCqSP*229B1X3k2baqnzSd(}?hq?_|za%v;E8+&=2B;+ntpIpyOcc$*B$l_~ zb1--fnYIm>DM>-UV$CF^`9!EzBkzRzrP8a3ez|nYVg_m>uf;U`RJSPAE literal 1523 zcmVRK}Cv)gdzpSRd5mtMgD1A_WqHA|gdVmm)cdhAJN-K++%t%kKgE&bK$OXLdZc$HuSp$lmdI z-sgGN>yMf7&Sq0g4)p23ysPAk#bS+IZ-MjRHE<4`0w)Tvm^~}-gyjQpAKU}q%;)pR zEdO7DY3pDI{0ROAv7vp{hT2*mwTlQm0j_|1psi5b%Auoo9D$gVU>p1bx(ek|&Xa?6 z5%P1e2_AvILb=7EvvYcqFATz`!M7ln5SP!}C0Su7 zL`e9Kyg#Xi>HTMGi4ZqXcTU{2_Dxbwa&-(? z&$hba^>)Ds6)29tx;jf+dmRuot{x66u7rx{yB-2`TV=&|EBwf&bBMZ$T(L)CXZLwc z1&S*WXIDsL`g1sq)_rTOKVdXAZFzt6=!wOO^uH!DSv|?Om&py~Gn({V?h1{!qzf{v zyRsBz7t&?wwk7zh1Hy`QV=QFYF|yq4R&x4m=(4)PcA8{6wzTubrO)NZ&&rhnFD(t0 z=GCO~4Xpud=rOu8bTt<3)kd2-WnCf7Yq6QDN1z71U5_|>bdUcWn;@6{3Jn@kSPp5H z_Nb^Ds2KyU&=|&8!qQ@C_v)@1s6iZ8XaHy|VQI0ndv#Y0)La!;X!L9>VQI0ndv#Y0 z)I=XwXy|b)VQI0ndv#Y0w4Mt!ZabE+v{>4`xs2fe+G4WNRhC5eFp(4Qa0>Jgay z+sRMvzf(tCv@Gqex`)a_4Afkev4o{1iGi9{>xwh3!hx1qS}g5enbjjubCO*VeUs^G ztmiUIi>2L5w`!nulc_0YV+l)(rQNH$YM|9SoBar!SLHzkON*ubU{Bwgx^%$Qo#iuX zY5V>WSiFa^yR^Oh_-?r};H9Nux6mFXZQsCR?^{MI?-}JoDt_M}YiTyB*A(*;o(7d_ z{0Z8GiPNxVkW)q_44ekm4R3;qF>spN5#-c=m6k6eGpo-h z;(DRgwXInhN8vQRt(k?h8758>ESs2!fzuSOq?c}CopjXBd6CKniz?JV|dZ# zLv&mMZ#MBFdS4O``Bkqk;Qt(yaf_AqCYifjCgD(DHvAIqzm?8amfy49i;2D?vE-P` z>#lE*zU#+P7%Knq|u7Lm_Tk1=p8Z|Lf4!!yg)% z=$a4c`UM{;gM(h&y9u&?40$?6Y1C^YBix+55LAWXab=xeq~gKx_l+Ehjc559LzMpsOkM z@;0$BQvHd#XjnblY-${8TRB#qRmDE}n^>L?AiECq?nzdEK9p_n7!}U~>AQTVvuU{V*C}R!mfH(+5C*Cv_iO1^a*f Z@(*KC4r1Hv!n6PY002ovPDHLkV1jpa-`M~F diff --git a/android/app/src/main/res/drawable-xxxhdpi/settings.png b/android/app/src/main/res/drawable-xxxhdpi/settings.png index cca523a1bbe89b247dd0f38c2302332113ee0466..61ffa633ceef04aa6dcefff7a283ad8cd90d514f 100644 GIT binary patch literal 1602 zcmV-I2EF--P)1=nfbUGS@eJux}<4T8onQS`~A(MGjsNyvu4iBxo75{ z1%J-$wbx#I|JUp@`&sFik@WQR^vBgKFcaJkt^^l@{h%9c0G*XeMZbMZ2+lmP6IkRM zjPC9e5rXIt&}oT^!)67#N@)WHxk2Cq2k|7T6{QRi5$F%zP7;Y2+hwH-5@eUe5KfbI zU#kWw@izeWBOvn&xG09ycJelXonS1O3T^;C*+s?}M!1tcgTm+u(9w) z04xP3fKTp?mTQG{s0Y#Vg*57lIt`P}D38r7dchh`UO)UlLEcNA{B7tvJ$cPmzYdSR zHI+i3%lCQ>`c*X!BMN*}z!rnA-Zq&~ZMtT^S;_nL9Pw6qu=%i_T`T2h8K7^C#H@Bk zclnxS86d5_EMm2@_{!ZT%K%L>w$^HAbeFH$z=-?5NY{S7!$!kNM$5@!F<(}F{r?D? zpHGoLj?{zaHKC_tqz;5My_ZNoAA{t|m?35@d6&9)p}z*~h$*|oRjzP42;LDxQdzsf z0x%j}06KtrmnM-u(FF3U!UeHi6CHsKGwzH>`DI3RxXd`$QAehroa;1D*$hM?u`YY<<<<`WqU>a6X!X!>omK zw9~gNifHIB#Urp5jfB`85rX zC}_oF4YnkgP5VeQb4eb=Yu(cmY*vEwn_hvYXSwAXaGTqsCY$=3#0oaYG59Xl$e3BQ z02ygzXI8WTjpy4E8dzMRz-BOd?SD5cMGKHFOw2Q|UVzOYgI!&d(AyyL?P31^khzEX zy5Vf10WYg1CW=j@oUAGWWL;`}iEZCoYABo#Z!cEk`V|3cZ+FB6=xi_Q)eW=IC|0kr zY4Yg>no4wlQ9xU#hk(xiyO{B6=}=ejp>pNga)nd}cmrr_!X+nBZUA$E>oP=5;IJ#j zroB!{UjUk{S><~S7t*7)S_%-Tk=VJ8eYF1i3_q=P>{BI0!}U?sQF0nlkD62URk~lu zT>`#N6PZAnV?5tg#^C6h%fD46X$afS*B}%q@O&U;xAUF)X{u8|xy9ekfQKQ}$z5xn}D^6Ga-kmImCEun{`e zb)AH0Yjiq8?x#L~)TeT~H?3P5QBnDQQDu|TGibMNiqzaz)r?g~E?J1?Bi`Vs0`iX# zq~~~i(@~*bURG^!xQ|tx+ic78Lui`x(M+pdETg9)H?_F(il$a7AXI-r@1V!qlTJ%POY z!Bn7mkru;;fsTDP1MMEHX0$rNZ(G_CC}9Nt15MN}JD|%67XSbN07*qoM6N<$f)1Jc AC;$Ke literal 2363 zcmV-B3B>k^P)Jw$gy1O@0(1oGqY7aeT_Rk zJumsvJFi~7s`pKGS9SI4*~*FnOMQKPB~4rmI)Zb-Zt!++z!Go* zsDr%#gMmrZ&C6Y9+K||1Ja1aEQV^o~%wY@~yL5xoI zGeMcnsW+3)=Ol_iw_qVd*V?G%HMcesQwFQUfF`VbasY!GumQD z;CC0YGOiBj%2uaH^=If; ze|T7<{-y4i2u{0QVae60u}@Be2p!VynmQME(T?HmP7uy5=`j>h1SjWZsi-*OkdPvn z(IbNAJIa7ZU2ZN5Shs?+=zQun3tGR3>K&jjpE&)=yxvVkxeMF@+BpW6V<+@24P~Ry z%{J)0sD@+YoKczfr}?0M6}xB^Q3GCJ!0rjvSw`Edz$9931-eX7Z>h;4IxXLlB?PVg z1)DtKmkk_Ev^hgR-mkyd+Y@vp*kU0?cVwfz28r!W;2(=G?Yy<}>Z_#e2z-Kt6sfh7 zHXT?eEy~UTQ^0XxlgCPmG{3>X%5obsY5jJN&x5|8J!l8|fTzHE;3nHBw0=Q;`x;Ao z2wA>cAEPoUzeA0QTH>}{vbpX)o#B`8tf0D8yRad3e|at zsRCacNb{;%iQzSc>R-wDg`|;SwO3ZL)6~T!#NV3=>kO6Z(dk)Zt)a|OrJgF&?nXzEb;S7)pxmms z%ez^D`TRiewp3cLyv)gLGBXe10}9$^7RK~%Ev>>x*c^;U_$J0U9Y_(3Lc2_%7uAl_ zCO(GUO9)$E;p_615@!T!`|fC37x%-7kOBC9S(Mps)BFt?R{)!g3eqc_>D4lmY!Biu_H|&wmb|i^GOg)73-(Gsr~@XM<=Igw z8N$R=6B}urelEe&vJ<=O!QOPe+=KR*yO$Dgo?{{KX5g1xMlwEYBwG^^DQga%1V@2Q z-uK)ggJISrXDBav6|87dUYMt&8`3sgIA-n#>#4UUHk%xvpcj)?vY$Ld@+-FC7ecR% zq@gzE6Qzd;%HniXaf>hUrdAYfJ6o=?U*a@E7^&s)082m+ z(R+8peKwgKbI2J7^u9$H(TGlNZh~#NK#C?AkrS27v$d5tJ&cY3Q6=JKenGJt$&s%j zV518oN)H~jTCj;$4B9_f4fMFG4bZA$9B^BQ>hbvqX8k1`lPM7VVdjv_`CAz`kAvnm zb|{+wegihyNI^1J7X;oL*fPtNE7sdaY^EMNy%o9=*yMps`^5YW8?!BpD9cglqMikN zk-W>|??=y$O3BcLkk^C6iNCun1Z#DoyRxE}tVxbe52nXsL6F=uO;mRpB34R(evN9s zp)9Y8K5fOQtHDselC{mCZz`4YDQducVy$b`2?9~dPn7Fc+@<83MveJU;9+LkPh1W^ z(b0xUJoWq^g=XcMAZtYX;K9u_Z2G;Hba9d!>Fgw@!%l*Qet^^M9S6rg$g$zu3{sgo ztpe6-%4%0Sji(6anz0+jE=L*h@mNTIVe5x%K2Y@)PAz8y>+}6U`?67s?(D*d-Xe;V zyvKn3(&=#4HJ59#Ru5W9cuLrCoTQJctLwN)y3wfb8K3^Nz?sf0ZOZ4$92C0ME*43x zB8XGl1cea@(QBwC`7}t2IC|RT44qP(E(qc?)^!N_B?}=^-{&Vd>~8?JjGIM%e3gtX zgmeJf&@qWd-DEGpVmA=D{n*k_FMdkW24Y?YZt@w0-Auuw*Z#wsz?S7mQT<|5-D1^c zO>$9m5(2cIUIpCtb=A}cph-&m!54uZU?6bYimatvKYpy+=i~|`Uk&unNum^OgEqXq zlU#PwkweB{5EaQv#4G2D<&G*yxe9CsVMJfjuw38e&;O0&))1uSn{9PrBVU(<`Jk#y zrUwQfN=vvG!CK%ZJ1Nw!MaAtF*~miVs|aH&IZDXW{)C<)cLcf(J_NMkw1XAG&m7BA hW}wVKnSmrQ@GqTElfi&Qt$zRj002ovPDHLkV1hJLV{!lh diff --git a/android/build.gradle b/android/build.gradle index 0df67837..ba5c375a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,9 +2,9 @@ buildscript { repositories { - google() mavenLocal() - mavenCentral() + google() + // mavenCentral() jcenter() } dependencies { @@ -19,12 +19,12 @@ buildscript { allprojects { repositories { mavenLocal() - mavenCentral() - jcenter() + // mavenCentral() google() - maven { - url 'https://maven.google.com' - } + jcenter() + // maven { + // url 'https://maven.google.com' + // } maven { url "https://jitpack.io" } maven { // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm @@ -37,14 +37,22 @@ allprojects { } } -subprojects { +subprojects { subproject -> afterEvaluate { - android { - compileSdkVersion 27 - buildToolsVersion "27.0.3" - defaultConfig { - targetSdkVersion 27 + if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { + android { + compileSdkVersion 27 + buildToolsVersion "27.0.3" + defaultConfig { + targetSdkVersion 27 + } + variantFilter { variant -> + def names = variant.flavors*.name + if (names.contains("reactNative51") || names.contains("reactNative55") || names.contains("reactNative56") || names.contains("reactNative57WixFork")) { + setIgnore(true) + } + } } } } -} +} \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index c9d018cd..1a592d3b 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -26,7 +26,7 @@ project(':react-native-vector-icons').projectDir = new File(rootProject.projectD include ':realm' project(':realm').projectDir = new File(rootProject.projectDir, '../node_modules/realm/android') include ':react-native-navigation' -project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/android/app/') +project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-navigation/lib/android/app/') include ':reactnativenotifications' project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android') include ':app' diff --git a/app/Drawer.js b/app/Drawer.js new file mode 100644 index 00000000..7b49ae52 --- /dev/null +++ b/app/Drawer.js @@ -0,0 +1,39 @@ +import { Navigation } from 'react-native-navigation'; + +const DRAWER_ID = 'Sidebar'; + +class Drawer { + constructor() { + this.visible = false; + + Navigation.events().registerComponentDidAppearListener(({ componentId }) => { + if (componentId === DRAWER_ID) { + this.visible = true; + } + }); + + Navigation.events().registerComponentDidDisappearListener(({ componentId }) => { + if (componentId === DRAWER_ID) { + this.visible = false; + } + }); + } + + toggle() { + try { + const visibility = !this.visible; + Navigation.mergeOptions(DRAWER_ID, { + sideMenu: { + left: { + visible: visibility + } + } + }); + this.visible = visibility; + } catch (error) { + console.warn(error); + } + } +} + +export default new Drawer(); diff --git a/app/Icons.js b/app/Icons.js index 5a26e3ff..968a7237 100644 --- a/app/Icons.js +++ b/app/Icons.js @@ -9,15 +9,15 @@ const icons = { [`${ prefix }-star`]: [25, Ionicons, 'star'], [`${ prefix }-star-outline`]: [25, Ionicons, 'starOutline'], [`${ prefix }-more`]: [25, Ionicons, 'more'], - [isIOS ? 'ios-create' : 'md-create']: [30, Ionicons, 'create'], - [`${ prefix }-close`]: [30, Ionicons, 'close'] + [isIOS ? 'ios-create' : 'md-create']: [25, Ionicons, 'create'], + [`${ prefix }-close`]: [25, Ionicons, 'close'] }; const iconsMap = {}; const iconsLoaded = async() => { const promises = Object.keys(icons).map((icon) => { const Provider = icons[icon][1]; - return Provider.getImageSource(icon, icons[icon][0]); + return Provider.getImageSource(icon, icons[icon][0], '#FFF'); }); const sources = await Promise.all(promises); Object.keys(icons).forEach((icon, i) => (iconsMap[icons[icon][2]] = sources[i])); diff --git a/app/Navigation.js b/app/Navigation.js deleted file mode 100644 index cabf1e98..00000000 --- a/app/Navigation.js +++ /dev/null @@ -1,19 +0,0 @@ -class NavigationActionsClass { - setNavigator(navigator) { - this.navigator = navigator; - } - - push = params => this.navigator && this.navigator.push(params) - - pop = params => this.navigator && this.navigator.pop(params) - - popToRoot = params => this.navigator && this.navigator.popToRoot(params) - - resetTo = params => this.navigator && this.navigator.resetTo(params) - - toggleDrawer = params => this.navigator && this.navigator.toggleDrawer(params) - - dismissModal = params => this.navigator && this.navigator.dismissModal(params) -} - -export const NavigationActions = new NavigationActionsClass(); diff --git a/app/actions/actionsTypes.js b/app/actions/actionsTypes.js index 42c0e3d6..e7b82839 100644 --- a/app/actions/actionsTypes.js +++ b/app/actions/actionsTypes.js @@ -39,7 +39,9 @@ export const ROOMS = createRequestTypes('ROOMS', [ 'CLOSE_SERVER_DROPDOWN', 'TOGGLE_SERVER_DROPDOWN', 'CLOSE_SORT_DROPDOWN', - 'TOGGLE_SORT_DROPDOWN' + 'TOGGLE_SORT_DROPDOWN', + 'OPEN_SEARCH_HEADER', + 'CLOSE_SEARCH_HEADER' ]); export const ROOM = createRequestTypes('ROOM', [ 'ADD_USER_TYPING', @@ -81,7 +83,6 @@ export const MESSAGES = createRequestTypes('MESSAGES', [ ]); export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]); export const SELECTED_USERS = createRequestTypes('SELECTED_USERS', ['ADD_USER', 'REMOVE_USER', 'RESET', 'SET_LOADING']); -export const NAVIGATION = createRequestTypes('NAVIGATION', ['SET']); export const SERVER = createRequestTypes('SERVER', [ ...defaultTypes, 'SELECT_SUCCESS', diff --git a/app/actions/navigator.js b/app/actions/navigator.js deleted file mode 100644 index b328a235..00000000 --- a/app/actions/navigator.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as types from './actionsTypes'; - -export default function setNavigation(navigator = {}) { - return { - type: types.NAVIGATION.SET, - navigator - }; -} diff --git a/app/actions/rooms.js b/app/actions/rooms.js index 84308ef9..09823718 100644 --- a/app/actions/rooms.js +++ b/app/actions/rooms.js @@ -50,3 +50,15 @@ export function toggleSortDropdown() { type: types.ROOMS.TOGGLE_SORT_DROPDOWN }; } + +export function openSearchHeader() { + return { + type: types.ROOMS.OPEN_SEARCH_HEADER + }; +} + +export function closeSearchHeader() { + return { + type: types.ROOMS.CLOSE_SEARCH_HEADER + }; +} diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js index 8f86c2c7..bc9e7873 100644 --- a/app/containers/MessageActions.js +++ b/app/containers/MessageActions.js @@ -245,7 +245,7 @@ export default class MessageActions extends React.Component { showToast(I18n.t('Copied_to_clipboard')); } - handleShare = async() => { + handleShare = () => { const { actionMessage } = this.props; Share.share({ message: actionMessage.msg.content.replace(/<(?:.|\n)*?>/gm, '') diff --git a/app/containers/Sidebar.js b/app/containers/Sidebar.js index 056f9ee7..bec5be29 100644 --- a/app/containers/Sidebar.js +++ b/app/containers/Sidebar.js @@ -5,6 +5,7 @@ import { } from 'react-native'; import { connect } from 'react-redux'; import Icon from 'react-native-vector-icons/MaterialIcons'; +import { Navigation } from 'react-native-navigation'; import { appStart as appStartAction } from '../actions'; import { logout as logoutAction } from '../actions/login'; @@ -15,9 +16,10 @@ import { STATUS_COLORS } from '../constants/colors'; import RocketChat from '../lib/rocketchat'; import log from '../utils/log'; import I18n from '../i18n'; -import { NavigationActions } from '../Navigation'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import DeviceInfo from '../utils/deviceInfo'; +import Drawer from '../Drawer'; +import EventEmitter from '../utils/events'; const styles = StyleSheet.create({ container: { @@ -38,6 +40,9 @@ const styles = StyleSheet.create({ fontWeight: 'bold', color: '#292E35' }, + itemSelected: { + backgroundColor: '#F7F8FA' + }, separator: { borderBottomWidth: StyleSheet.hairlineWidth, borderColor: '#ddd', @@ -95,7 +100,7 @@ const keyExtractor = item => item.id; export default class Sidebar extends Component { static propTypes = { baseUrl: PropTypes.string, - navigator: PropTypes.object, + componentId: PropTypes.string, server: PropTypes.string.isRequired, user: PropTypes.object, logout: PropTypes.func.isRequired, @@ -105,12 +110,15 @@ export default class Sidebar extends Component { constructor(props) { super(props); this.state = { - showStatus: false + showStatus: false, + currentStack: 'RoomsListView' }; + Navigation.events().bindComponent(this); } componentDidMount() { this.setStatus(); + EventEmitter.addEventListener('ChangeStack', this.handleChangeStack); } componentWillReceiveProps(nextProps) { @@ -120,6 +128,22 @@ export default class Sidebar extends Component { } } + componentWillUnmount() { + EventEmitter.removeListener('ChangeStack', this.handleChangeStack); + } + + handleChangeStack = (event) => { + const { stack } = event; + this.setStack(stack); + } + + navigationButtonPressed = ({ buttonId }) => { + if (buttonId === 'cancel') { + const { componentId } = this.props; + Navigation.dismissModal(componentId); + } + } + setStatus = () => { setTimeout(() => { this.setState({ @@ -140,13 +164,21 @@ export default class Sidebar extends Component { }); } + setStack = (stack) => { + const { currentStack } = this.state; + if (currentStack !== stack) { + Navigation.setStackRoot('AppRoot', { + component: { + id: stack, + name: stack + } + }); + this.setState({ currentStack: stack }); + } + } + closeDrawer = () => { - const { navigator } = this.props; - navigator.toggleDrawer({ - side: 'left', - animated: true, - to: 'close' - }); + Drawer.toggle(); } toggleStatus = () => { @@ -154,15 +186,15 @@ export default class Sidebar extends Component { this.setState(prevState => ({ showStatus: !prevState.showStatus })); } - sidebarNavigate = (screen, title) => { + sidebarNavigate = (stack) => { this.closeDrawer(); - NavigationActions.resetTo({ screen, title }); + this.setStack(stack); } renderSeparator = key => ; renderItem = ({ - text, left, onPress, testID + text, left, onPress, testID, current }) => ( - + {left} @@ -188,7 +220,7 @@ export default class Sidebar extends Component { this.renderItem({ text: item.name, left: , - selected: user.status === item.id, + current: user.status === item.id, onPress: () => { this.closeDrawer(); this.toggleStatus(); @@ -205,26 +237,30 @@ export default class Sidebar extends Component { } renderNavigation = () => { + const { currentStack } = this.state; const { logout } = this.props; return ( [ this.renderItem({ text: I18n.t('Chats'), left: , - onPress: () => this.sidebarNavigate('RoomsListView', I18n.t('Messages')), - testID: 'sidebar-chats' + onPress: () => this.sidebarNavigate('RoomsListView'), + testID: 'sidebar-chats', + current: currentStack === 'RoomsListView' }), this.renderItem({ text: I18n.t('Profile'), left: , - onPress: () => this.sidebarNavigate('ProfileView', I18n.t('Profile')), - testID: 'sidebar-profile' + onPress: () => this.sidebarNavigate('ProfileView'), + testID: 'sidebar-profile', + current: currentStack === 'ProfileView' }), this.renderItem({ text: I18n.t('Settings'), left: , - onPress: () => this.sidebarNavigate('SettingsView', I18n.t('Settings')), - testID: 'sidebar-settings' + onPress: () => this.sidebarNavigate('SettingsView'), + testID: 'sidebar-settings', + current: currentStack === 'SettingsView' }), this.renderSeparator('separator-logout'), this.renderItem({ diff --git a/app/index.js b/app/index.js index 8427d44c..9cc33760 100644 --- a/app/index.js +++ b/app/index.js @@ -1,5 +1,5 @@ import { Component } from 'react'; -import { Linking } from 'react-native'; +import { Linking, Platform, Dimensions } from 'react-native'; import { Navigation } from 'react-native-navigation'; import store from './lib/createStore'; @@ -8,35 +8,51 @@ import { iconsLoaded } from './Icons'; import { registerScreens } from './views'; import { deepLinkingOpen } from './actions/deepLinking'; import parseQuery from './lib/methods/helpers/parseQuery'; -import I18n from './i18n'; import { initializePushNotifications } from './push'; +const isAndroid = () => Platform.OS === 'android'; + const startLogged = () => { - Navigation.startSingleScreenApp({ - screen: { - screen: 'RoomsListView', - title: I18n.t('Messages') - }, - drawer: { - left: { - screen: 'Sidebar' + Navigation.setRoot({ + root: { + sideMenu: { + left: { + component: { + id: 'Sidebar', + name: 'Sidebar' + } + }, + center: { + stack: { + id: 'AppRoot', + children: [{ + component: { + id: 'RoomsListView', + name: 'RoomsListView' + } + }] + } + } } - }, - animationType: 'fade' + } }); }; const startNotLogged = () => { - Navigation.startSingleScreenApp({ - screen: { - screen: 'OnboardingView', - navigatorStyle: { - navBarHidden: true + Navigation.setRoot({ + root: { + stack: { + children: [{ + component: { + name: 'OnboardingView' + } + }], + options: { + layout: { + orientation: ['portrait'] + } + } } - }, - animationType: 'fade', - appStyle: { - orientation: 'portrait' } }); }; @@ -63,6 +79,29 @@ export default class App extends Component { store.subscribe(this.onStoreUpdate.bind(this)); initializePushNotifications(); + Navigation.events().registerAppLaunchedListener(() => { + Navigation.setDefaultOptions({ + topBar: { + backButton: { + icon: { uri: 'back', scale: Dimensions.get('window').scale } + }, + title: { + color: isAndroid() ? '#FFF' : undefined + }, + background: { + color: isAndroid() ? '#2F343D' : undefined + }, + buttonColor: '#FFF' + }, + sideMenu: { + left: { + enabled: false + } + } + }); + store.dispatch(appInit()); + store.subscribe(this.onStoreUpdate.bind(this)); + }); Linking .getInitialURL() .then(url => handleOpenURL({ url })) diff --git a/app/lib/methods/getRooms.js b/app/lib/methods/getRooms.js index ffc09050..e1fbf299 100644 --- a/app/lib/methods/getRooms.js +++ b/app/lib/methods/getRooms.js @@ -31,7 +31,7 @@ const getRoomDpp = async function() { } }; -export default async function() { +export default function() { const { database: db } = database; return new Promise(async(resolve, reject) => { diff --git a/app/lib/methods/loadMessagesForRoom.js b/app/lib/methods/loadMessagesForRoom.js index 48f81616..8d57d2c8 100644 --- a/app/lib/methods/loadMessagesForRoom.js +++ b/app/lib/methods/loadMessagesForRoom.js @@ -34,7 +34,7 @@ async function loadMessagesForRoomDDP(...args) { } } -export default async function loadMessagesForRoom(...args) { +export default function loadMessagesForRoom(...args) { const { database: db } = database; return new Promise(async(resolve, reject) => { try { diff --git a/app/lib/methods/loadMissedMessages.js b/app/lib/methods/loadMissedMessages.js index c12ab34e..225dc6f1 100644 --- a/app/lib/methods/loadMissedMessages.js +++ b/app/lib/methods/loadMissedMessages.js @@ -25,7 +25,7 @@ async function loadMissedMessagesDDP(...args) { } } -export default async function loadMissedMessages(...args) { +export default function loadMissedMessages(...args) { const { database: db } = database; return new Promise(async(resolve, reject) => { try { diff --git a/app/lib/methods/subscriptions/room.js b/app/lib/methods/subscriptions/room.js index 4331bcda..9a6a2344 100644 --- a/app/lib/methods/subscriptions/room.js +++ b/app/lib/methods/subscriptions/room.js @@ -23,7 +23,7 @@ const stop = () => { clearTimeout(timer); }; -export default async function subscribeRoom({ rid, t }) { +export default function subscribeRoom({ rid, t }) { if (promises) { promises.then(unsubscribe); promises = false; diff --git a/app/lib/rocketchat.js b/app/lib/rocketchat.js index 0cfa6083..92c42788 100644 --- a/app/lib/rocketchat.js +++ b/app/lib/rocketchat.js @@ -151,17 +151,19 @@ const RocketChat = { } }, connect(url, login) { - return new Promise(async() => { + return new Promise(() => { if (this.ddp) { RocketChat.disconnect(); this.ddp = null; } + SDK.api.setBaseUrl(url); + if (login) { SDK.api.setAuth({ authToken: login.token, userId: login.id }); + RocketChat.setApiUser({ userId: login.id, authToken: login.token }); } - SDK.api.setBaseUrl(url); SDK.driver.connect({ host: url, useSsl: true }, (err, ddp) => { if (err) { return console.warn(err); @@ -191,11 +193,7 @@ const RocketChat = { // SDK.driver.on('background', () => this.getRooms().catch(e => log('background getRooms', e))); SDK.driver.on('logged', protectedFunction((error, user) => { - SDK.api.setAuth({ authToken: user.token, userId: user.id }); - SDK.api.currentLogin = { - userId: user.id, - authToken: user.token - }; + RocketChat.setApiUser({ userId: user.id, authToken: user.token }); this.loginSuccess(user); this.getRooms().catch(e => log('logged getRooms', e)); this.subscribeRooms(user.id); @@ -506,13 +504,12 @@ const RocketChat = { } catch (error) { console.warn(error); } - SDK.api.setAuth({ authToken: null, userId: null }); - SDK.api.currentLogin = { - userId: null, - authToken: null - }; + RocketChat.setApiUser({ userId: null, authToken: null }); + }, + setApiUser({ userId, authToken }) { + SDK.api.setAuth({ userId, authToken }); + SDK.api.currentLogin = { userId, authToken }; }, - registerPushToken(userId) { const deviceToken = getDeviceToken(); if (deviceToken) { @@ -566,6 +563,8 @@ const RocketChat = { data = data.filtered('t != $0', 'd'); } data = data.slice(0, 7); + const array = Array.from(data); + data = JSON.parse(JSON.stringify(array)); const usernames = data.map(sub => sub.name); try { diff --git a/app/reducers/createChannel.js b/app/reducers/createChannel.js index f71826c6..bbae1ac0 100644 --- a/app/reducers/createChannel.js +++ b/app/reducers/createChannel.js @@ -3,8 +3,8 @@ import { CREATE_CHANNEL } from '../actions/actionsTypes'; const initialState = { isFetching: false, failure: false, - result: '', - error: '' + result: {}, + error: {} }; export default function messages(state = initialState, action) { @@ -14,7 +14,7 @@ export default function messages(state = initialState, action) { ...state, isFetching: true, failure: false, - error: '' + error: {} }; case CREATE_CHANNEL.SUCCESS: return { diff --git a/app/reducers/index.js b/app/reducers/index.js index 2c6c6c58..86b6bcaa 100644 --- a/app/reducers/index.js +++ b/app/reducers/index.js @@ -6,7 +6,6 @@ import messages from './messages'; import room from './room'; import rooms from './rooms'; import server from './server'; -import navigator from './navigator'; import selectedUsers from './selectedUsers'; import createChannel from './createChannel'; import app from './app'; @@ -26,7 +25,6 @@ export default combineReducers({ meteor, messages, server, - navigator, selectedUsers, createChannel, app, diff --git a/app/reducers/navigator.js b/app/reducers/navigator.js deleted file mode 100644 index 5660551c..00000000 --- a/app/reducers/navigator.js +++ /dev/null @@ -1,12 +0,0 @@ -import * as types from '../actions/actionsTypes'; - -const initialState = {}; - -export default function navigations(state = initialState, action) { - switch (action.type) { - case types.NAVIGATION.SET: - return action.navigator; - default: - return state; - } -} diff --git a/app/reducers/rooms.js b/app/reducers/rooms.js index 09506865..0e16f13e 100644 --- a/app/reducers/rooms.js +++ b/app/reducers/rooms.js @@ -6,7 +6,8 @@ const initialState = { searchText: '', showServerDropdown: false, closeServerDropdown: false, - showSortDropdown: false + showSortDropdown: false, + showSearchHeader: false }; export default function login(state = initialState, action) { @@ -53,6 +54,16 @@ export default function login(state = initialState, action) { ...state, showSortDropdown: !state.showSortDropdown }; + case types.ROOMS.OPEN_SEARCH_HEADER: + return { + ...state, + showSearchHeader: true + }; + case types.ROOMS.CLOSE_SEARCH_HEADER: + return { + ...state, + showSearchHeader: false + }; default: return state; } diff --git a/app/sagas/createChannel.js b/app/sagas/createChannel.js index 7a7df363..7768aa22 100644 --- a/app/sagas/createChannel.js +++ b/app/sagas/createChannel.js @@ -1,8 +1,6 @@ -import { delay } from 'redux-saga'; import { select, put, call, take, takeLatest } from 'redux-saga/effects'; -import { NavigationActions } from '../Navigation'; import { CREATE_CHANNEL, LOGIN } from '../actions/actionsTypes'; import { createChannelSuccess, createChannelFailure } from '../actions/createChannel'; @@ -20,16 +18,6 @@ const handleRequest = function* handleRequest({ data }) { } const result = yield call(create, data); yield put(createChannelSuccess(result)); - yield delay(300); - const { rid, name } = result; - NavigationActions.dismissModal(); - yield delay(600); - NavigationActions.push({ - screen: 'RoomView', - title: name, - backButtonTitle: '', - passProps: { rid } - }); } catch (err) { yield put(createChannelFailure(err)); } diff --git a/app/sagas/deepLinking.js b/app/sagas/deepLinking.js index 0a80cd55..83247379 100644 --- a/app/sagas/deepLinking.js +++ b/app/sagas/deepLinking.js @@ -1,14 +1,16 @@ import { AsyncStorage } from 'react-native'; +import { delay } from 'redux-saga'; import { takeLatest, take, select, put } from 'redux-saga/effects'; +import { Navigation } from 'react-native-navigation'; import * as types from '../actions/actionsTypes'; import { appStart } from '../actions'; import { selectServerRequest } from '../actions/server'; import database from '../lib/realm'; import RocketChat from '../lib/rocketchat'; -import { NavigationActions } from '../Navigation'; +import EventEmitter from '../utils/events'; const navigate = function* go({ params, sameServer = true }) { if (!sameServer) { @@ -17,11 +19,15 @@ const navigate = function* go({ params, sameServer = true }) { if (params.rid) { const canOpenRoom = yield RocketChat.canOpenRoom(params); if (canOpenRoom) { - return NavigationActions.push({ - screen: 'RoomView', - backButtonTitle: '', - passProps: { - rid: params.rid + // Make sure current stack is RoomsListView before navigate to RoomView + EventEmitter.emit('ChangeStack', { stack: 'RoomsListView' }); + yield Navigation.popToRoot('RoomsListView'); + Navigation.push('RoomsListView', { + component: { + name: 'RoomView', + passProps: { + rid: params.rid + } } }); } @@ -66,17 +72,14 @@ const handleOpen = function* handleOpen({ params }) { } else { // if deep link is from a different server // search if deep link's server already exists const servers = yield database.databases.serversDB.objects('servers').filtered('id = $0', host); // TODO: need better test - if (servers.length) { - const deepLinkServer = servers[0].id; - if (!token) { - yield put(appStart('outside')); - } else { - yield put(selectServerRequest(deepLinkServer)); - yield take(types.METEOR.REQUEST); - yield navigate({ params, sameServer: false }); - } - } else { + if (servers.length && token) { yield put(selectServerRequest(host)); + yield take(types.METEOR.REQUEST); + yield navigate({ params, sameServer: false }); + } else { + yield put(appStart('outside')); + yield delay(1000); + EventEmitter.emit('NewServer', { server: host }); } } }; diff --git a/app/sagas/login.js b/app/sagas/login.js index 2cda9fdb..7d4b2519 100644 --- a/app/sagas/login.js +++ b/app/sagas/login.js @@ -3,6 +3,7 @@ import { delay } from 'redux-saga'; import { put, call, take, takeLatest, select, all } from 'redux-saga/effects'; +import { Navigation } from 'react-native-navigation'; import * as types from '../actions/actionsTypes'; import { appStart } from '../actions'; @@ -25,7 +26,6 @@ import { import RocketChat from '../lib/rocketchat'; import log from '../utils/log'; import I18n from '../i18n'; -import { NavigationActions } from '../Navigation'; const getUser = state => state.login.user; const getServer = state => state.server.server; @@ -48,7 +48,7 @@ const handleLoginSuccess = function* handleLoginSuccess() { } else { yield delay(300); if (adding) { - NavigationActions.dismissModal(); + yield Navigation.dismissAllModals(); } else { yield put(appStart('inside')); } diff --git a/app/sagas/messages.js b/app/sagas/messages.js index ece23915..c244960b 100644 --- a/app/sagas/messages.js +++ b/app/sagas/messages.js @@ -1,5 +1,6 @@ import { delay } from 'redux-saga'; import { takeLatest, put, call } from 'redux-saga/effects'; +import { Navigation } from 'react-native-navigation'; import { MESSAGES } from '../actions/actionsTypes'; import { @@ -18,7 +19,6 @@ import { import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; import log from '../utils/log'; -import { NavigationActions } from '../Navigation'; const deleteMessage = message => RocketChat.deleteMessage(message); const editMessage = message => RocketChat.editMessage(message); @@ -76,13 +76,21 @@ const handleTogglePinRequest = function* handleTogglePinRequest({ message }) { }; const goRoom = function* goRoom({ rid, name }) { - NavigationActions.popToRoot(); - yield delay(1000); - NavigationActions.push({ - screen: 'RoomView', - title: name, - backButtonTitle: '', - passProps: { rid } + yield Navigation.popToRoot('RoomsListView'); + Navigation.push('RoomsListView', { + component: { + name: 'RoomView', + passProps: { + rid + }, + options: { + topBar: { + title: { + text: name + } + } + } + } }); }; diff --git a/app/sagas/rooms.js b/app/sagas/rooms.js index 6eabf280..7032b408 100644 --- a/app/sagas/rooms.js +++ b/app/sagas/rooms.js @@ -4,6 +4,7 @@ import { } from 'redux-saga/effects'; import { delay } from 'redux-saga'; import { BACKGROUND } from 'redux-enhancer-react-native-appstate'; +import { Navigation } from 'react-native-navigation'; import * as types from '../actions/actionsTypes'; // import { roomsSuccess, roomsFailure } from '../actions/rooms'; @@ -13,7 +14,6 @@ import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; import log from '../utils/log'; import I18n from '../i18n'; -import { NavigationActions } from '../Navigation'; const leaveRoom = rid => RocketChat.leaveRoom(rid); const eraseRoom = rid => RocketChat.eraseRoom(rid); @@ -141,9 +141,8 @@ const updateLastOpen = function* updateLastOpen() { yield put(setLastOpen()); }; -const goRoomsListAndDelete = function* goRoomsListAndDelete(rid) { - NavigationActions.popToRoot(); - yield delay(1000); +const goRoomsListAndDelete = function* goRoomsListAndDelete(rid, type) { + yield Navigation.popToRoot(type === 'erase' ? 'RoomActionsView' : 'RoomInfoEditView'); try { database.write(() => { const messages = database.objects('messages').filtered('rid = $0', rid); @@ -160,7 +159,7 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid }) { try { sub.stop(); yield call(leaveRoom, rid); - yield goRoomsListAndDelete(rid); + yield goRoomsListAndDelete(rid, 'delete'); } catch (e) { if (e.error === 'error-you-are-last-owner') { Alert.alert(I18n.t(e.error)); @@ -174,7 +173,7 @@ const handleEraseRoom = function* handleEraseRoom({ rid }) { try { sub.stop(); yield call(eraseRoom, rid); - yield goRoomsListAndDelete(rid); + yield goRoomsListAndDelete(rid, 'erase'); } catch (e) { Alert.alert(I18n.t('There_was_an_error_while_action', { action: I18n.t('erasing_room') })); } diff --git a/app/sagas/selectServer.js b/app/sagas/selectServer.js index 1ce6c017..dadc11dd 100644 --- a/app/sagas/selectServer.js +++ b/app/sagas/selectServer.js @@ -3,7 +3,6 @@ import { AsyncStorage } from 'react-native'; import { Navigation } from 'react-native-navigation'; import { Provider } from 'react-redux'; -import { NavigationActions } from '../Navigation'; import { SERVER } from '../actions/actionsTypes'; import * as actions from '../actions'; import { connectRequest } from '../actions/connect'; @@ -50,11 +49,23 @@ const handleServerRequest = function* handleServerRequest({ server }) { try { if (LoginSignupView == null) { LoginSignupView = require('../views/LoginSignupView').default; - Navigation.registerComponent('LoginSignupView', () => LoginSignupView, store, Provider); + Navigation.registerComponentWithRedux('LoginSignupView', () => LoginSignupView, Provider, store); } yield call(validate, server); - yield call(NavigationActions.push, { screen: 'LoginSignupView', title: server, backButtonTitle: '' }); + yield Navigation.push('NewServerView', { + component: { + name: 'LoginSignupView', + options: { + topBar: { + title: { + text: server + } + } + } + } + }); + database.databases.serversDB.write(() => { database.databases.serversDB.create('servers', { id: server }, true); }); diff --git a/app/utils/events.js b/app/utils/events.js new file mode 100644 index 00000000..1e9df675 --- /dev/null +++ b/app/utils/events.js @@ -0,0 +1,42 @@ +import log from './log'; + +class EventEmitter { + constructor() { + this.events = {}; + } + + addEventListener(event, listener) { + if (typeof this.events[event] !== 'object') { + this.events[event] = []; + } + this.events[event].push(listener); + return listener; + } + + removeListener(event, listener) { + if (typeof this.events[event] === 'object') { + const idx = this.events[event].indexOf(listener); + if (idx > -1) { + this.events[event].splice(idx, 1); + } + if (this.events[event].length === 0) { + delete this.events[event]; + } + } + } + + emit(event, ...args) { + if (typeof this.events[event] === 'object') { + this.events[event].forEach((listener) => { + try { + listener.apply(this, args); + } catch (e) { + log('EventEmitter.emit', e); + } + }); + } + } +} + +const events = new EventEmitter(); +export default events; diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js index 7a4b1887..41f6f76c 100644 --- a/app/views/CreateChannelView.js +++ b/app/views/CreateChannelView.js @@ -2,8 +2,10 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { - View, Text, Switch, SafeAreaView, ScrollView, TextInput, StyleSheet, FlatList, Platform + View, Text, Switch, ScrollView, TextInput, StyleSheet, FlatList, Platform } from 'react-native'; +import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import Loading from '../containers/Loading'; import LoggedView from './View'; @@ -72,7 +74,10 @@ const styles = StyleSheet.create({ @connect(state => ({ baseUrl: state.settings.Site_Url || state.server ? state.server.server : '', - createChannel: state.createChannel, + error: state.createChannel.error, + failure: state.createChannel.failure, + isFetching: state.createChannel.isFetching, + result: state.createChannel.result, users: state.selectedUsers.users }), dispatch => ({ create: data => dispatch(createChannelRequestAction(data)), @@ -81,11 +86,14 @@ const styles = StyleSheet.create({ /** @extends React.Component */ export default class CreateChannelView extends LoggedView { static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, baseUrl: PropTypes.string, create: PropTypes.func.isRequired, removeUser: PropTypes.func.isRequired, - createChannel: PropTypes.object.isRequired, + error: PropTypes.object, + failure: PropTypes.bool, + isFetching: PropTypes.bool, + result: PropTypes.object, users: PropTypes.array.isRequired }; @@ -97,7 +105,7 @@ export default class CreateChannelView extends LoggedView { readOnly: false, broadcast: false }; - props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); + Navigation.events().bindComponent(this); } componentDidMount() { @@ -107,36 +115,60 @@ export default class CreateChannelView extends LoggedView { } componentDidUpdate(prevProps) { - const { createChannel } = this.props; + const { + isFetching, failure, error, result, componentId + } = this.props; - if (createChannel.error && prevProps.createChannel.error !== createChannel.error) { - setTimeout(() => { - const msg = createChannel.error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') }); - showErrorAlert(msg); + if (!isFetching && isFetching !== prevProps.isFetching) { + setTimeout(async() => { + if (failure) { + const msg = error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') }); + showErrorAlert(msg); + } else { + const { rid, name } = result; + await Navigation.dismissModal(componentId); + Navigation.push('RoomsListView', { + component: { + name: 'RoomView', + passProps: { + rid + }, + options: { + topBar: { + title: { + text: name + } + } + } + } + }); + } }, 300); } } onChangeText = (channelName) => { - const { navigator } = this.props; - + const { componentId } = this.props; const rightButtons = []; if (channelName.trim().length > 0) { rightButtons.push({ id: 'create', - title: 'Create', - testID: 'create-channel-submit' + text: 'Create', + testID: 'create-channel-submit', + color: Platform.OS === 'android' ? '#FFF' : undefined }); } - navigator.setButtons({ rightButtons }); + Navigation.mergeOptions(componentId, { + topBar: { + rightButtons + } + }); this.setState({ channelName }); } - async onNavigatorEvent(event) { - if (event.type === 'NavBarButtonPress') { - if (event.id === 'create') { - this.submit(); - } + navigationButtonPressed = ({ buttonId }) => { + if (buttonId === 'create') { + this.submit(); } } @@ -144,9 +176,9 @@ export default class CreateChannelView extends LoggedView { const { channelName, type, readOnly, broadcast } = this.state; - const { users: usersProps, createChannel, create } = this.props; + const { users: usersProps, isFetching, create } = this.props; - if (!channelName.trim() || createChannel.isFetching) { + if (!channelName.trim() || isFetching) { return; } @@ -256,7 +288,7 @@ export default class CreateChannelView extends LoggedView { render() { const { channelName } = this.state; - const { users, createChannel } = this.props; + const { users, isFetching } = this.props; const userCount = users.length; return ( @@ -264,7 +296,7 @@ export default class CreateChannelView extends LoggedView { contentContainerStyle={[sharedStyles.container, styles.container]} keyboardVerticalOffset={128} > - + {userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })} {this.renderInvitedList()} - + diff --git a/app/views/ForgotPasswordView.js b/app/views/ForgotPasswordView.js index 3ea5cbca..1d1fc4e5 100644 --- a/app/views/ForgotPasswordView.js +++ b/app/views/ForgotPasswordView.js @@ -1,9 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - Text, View, SafeAreaView, ScrollView -} from 'react-native'; +import { Text, View, ScrollView } from 'react-native'; import { connect } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import LoggedView from './View'; import { forgotPasswordInit as forgotPasswordInitAction, forgotPasswordRequest as forgotPasswordRequestAction } from '../actions/login'; @@ -25,7 +25,7 @@ import I18n from '../i18n'; /** @extends React.Component */ export default class ForgotPasswordView extends LoggedView { static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, forgotPasswordInit: PropTypes.func.isRequired, forgotPasswordRequest: PropTypes.func.isRequired, login: PropTypes.object @@ -46,9 +46,9 @@ export default class ForgotPasswordView extends LoggedView { } componentDidUpdate() { - const { login, navigator } = this.props; + const { login, componentId } = this.props; if (login.success) { - navigator.pop(); + Navigation.pop(componentId); setTimeout(() => { showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert')); }); @@ -84,7 +84,7 @@ export default class ForgotPasswordView extends LoggedView { keyboardVerticalOffset={128} > - + { if (OAuthView == null) { OAuthView = require('./OAuthView').default; - Navigation.registerComponent('OAuthView', () => OAuthView, store, Provider); + Navigation.registerComponentWithRedux('OAuthView', () => OAuthView, Provider, store); } - const { navigator } = this.props; - navigator.showModal({ - screen: 'OAuthView', - title: 'OAuth', - passProps: { - oAuthUrl + Navigation.showModal({ + stack: { + children: [{ + component: { + name: 'OAuthView', + passProps: { + oAuthUrl + }, + options: { + topBar: { + title: { + text: 'OAuth' + } + } + } + } + }] } }); } @@ -205,28 +217,42 @@ export default class LoginSignupView extends LoggedView { login = () => { if (LoginView == null) { LoginView = require('./LoginView').default; - Navigation.registerComponent('LoginView', () => LoginView, store, Provider); + Navigation.registerComponentWithRedux('LoginView', () => LoginView, Provider, store); } - const { navigator, server } = this.props; - navigator.push({ - screen: 'LoginView', - title: server, - backButtonTitle: '' + const { componentId, server } = this.props; + Navigation.push(componentId, { + component: { + name: 'LoginView', + options: { + topBar: { + title: { + text: server + } + } + } + } }); } register = () => { if (RegisterView == null) { RegisterView = require('./RegisterView').default; - Navigation.registerComponent('RegisterView', () => RegisterView, store, Provider); + Navigation.registerComponentWithRedux('RegisterView', () => RegisterView, Provider, store); } - const { navigator, server } = this.props; - navigator.push({ - screen: 'RegisterView', - title: server, - backButtonTitle: '' + const { componentId, server } = this.props; + Navigation.push(componentId, { + component: { + name: 'RegisterView', + options: { + topBar: { + title: { + text: server + } + } + } + } }); } @@ -335,7 +361,7 @@ export default class LoginSignupView extends LoggedView { style={[sharedStyles.container, sharedStyles.containerScrollView]} {...scrollPersistTaps} > - + {I18n.t('Welcome_title_pt_1')} {I18n.t('Welcome_title_pt_2')} diff --git a/app/views/LoginView.js b/app/views/LoginView.js index 83b59915..cf8df7af 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - Keyboard, Text, ScrollView, View, SafeAreaView + Keyboard, Text, ScrollView, View } from 'react-native'; import { connect, Provider } from 'react-redux'; import { Navigation } from 'react-native-navigation'; import { Answers } from 'react-native-fabric'; +import SafeAreaView from 'react-native-safe-area-view'; import RocketChat from '../lib/rocketchat'; import KeyboardView from '../presentation/KeyboardView'; @@ -35,7 +36,7 @@ let ForgotPasswordView = null; /** @extends React.Component */ export default class LoginView extends LoggedView { static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, loginSubmit: PropTypes.func.isRequired, login: PropTypes.object, server: PropTypes.string, @@ -76,28 +77,42 @@ export default class LoginView extends LoggedView { register = () => { if (RegisterView == null) { RegisterView = require('./RegisterView').default; - Navigation.registerComponent('RegisterView', () => RegisterView, store, Provider); + Navigation.registerComponentWithRedux('RegisterView', () => RegisterView, Provider, store); } - const { navigator, server } = this.props; - navigator.push({ - screen: 'RegisterView', - title: server, - backButtonTitle: '' + const { componentId, server } = this.props; + Navigation.push(componentId, { + component: { + name: 'RegisterView', + options: { + topBar: { + title: { + text: server + } + } + } + } }); } forgotPassword = () => { if (ForgotPasswordView == null) { ForgotPasswordView = require('./ForgotPasswordView').default; - Navigation.registerComponent('ForgotPasswordView', () => ForgotPasswordView, store, Provider); + Navigation.registerComponentWithRedux('ForgotPasswordView', () => ForgotPasswordView, Provider, store); } - const { navigator } = this.props; - navigator.push({ - screen: 'ForgotPasswordView', - title: I18n.t('Forgot_Password'), - backButtonTitle: '' + const { componentId } = this.props; + Navigation.push(componentId, { + component: { + name: 'ForgotPasswordView', + options: { + topBar: { + title: { + text: I18n.t('Forgot_Password') + } + } + } + } }); } @@ -132,7 +147,7 @@ export default class LoginView extends LoggedView { key='login-view' > - + Login + { - const { navigator, onPressItem } = this.props; - navigator.dismissModal(); + const { onPressItem } = this.props; + this.dismiss(); setTimeout(() => { onPressItem(item); }, 600); } + navigationButtonPressed = ({ buttonId }) => { + if (buttonId === 'cancel') { + this.dismiss(); + } + } + + dismiss = () => { + const { componentId } = this.props; + Navigation.dismissModal(componentId); + } + // eslint-disable-next-line react/sort-comp updateState = debounce(() => { this.forceUpdate(); @@ -116,16 +124,23 @@ export default class NewMessageView extends LoggedView { createChannel = () => { if (SelectedUsersView == null) { SelectedUsersView = require('./SelectedUsersView').default; - Navigation.registerComponent('SelectedUsersView', () => SelectedUsersView, store, Provider); + Navigation.registerComponentWithRedux('SelectedUsersView', () => SelectedUsersView, Provider, store); } - const { navigator } = this.props; - navigator.push({ - screen: 'SelectedUsersView', - title: I18n.t('Select_Users'), - backButtonTitle: '', - passProps: { - nextAction: 'CREATE_CHANNEL' + const { componentId } = this.props; + Navigation.push(componentId, { + component: { + name: 'SelectedUsersView', + passProps: { + nextAction: 'CREATE_CHANNEL' + }, + options: { + topBar: { + title: { + text: I18n.t('Select_Users') + } + } + } } }); } @@ -186,7 +201,7 @@ export default class NewMessageView extends LoggedView { } render = () => ( - + {this.renderList()} ); diff --git a/app/views/NewServerView.js b/app/views/NewServerView.js index 5364e0b2..f3c5c5df 100644 --- a/app/views/NewServerView.js +++ b/app/views/NewServerView.js @@ -1,10 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - Text, ScrollView, Keyboard, SafeAreaView, Image, Alert, StyleSheet, TouchableOpacity + Text, ScrollView, Keyboard, Image, Alert, StyleSheet, TouchableOpacity } from 'react-native'; import { connect } from 'react-redux'; import Icon from 'react-native-vector-icons/Ionicons'; +import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import { serverRequest } from '../actions/server'; import sharedStyles from './Styles'; @@ -60,8 +62,17 @@ const defaultServer = 'https://open.rocket.chat'; })) /** @extends React.Component */ export default class NewServerView extends LoggedView { + static options() { + return { + topBar: { + visible: false, + drawBehind: true + } + }; + } + static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, server: PropTypes.string, connecting: PropTypes.bool.isRequired, failure: PropTypes.bool.isRequired, @@ -73,6 +84,7 @@ export default class NewServerView extends LoggedView { this.state = { text: '' }; + Navigation.events().bindComponent(this); } componentDidMount() { @@ -128,7 +140,7 @@ export default class NewServerView extends LoggedView { } renderBack = () => { - const { navigator } = this.props; + const { componentId } = this.props; let top = 15; if (DeviceInfo.getBrand() === 'Apple') { @@ -138,7 +150,7 @@ export default class NewServerView extends LoggedView { return ( navigator.pop()} + onPress={() => Navigation.pop(componentId)} > - + {I18n.t('Sign_in_your_server')} { + if (buttonId === 'cancel') { + this.dismiss(); } } + dismiss = () => { + const { componentId } = this.props; + Navigation.dismissModal(componentId); + } + login = async(params) => { try { await RocketChat.login(params); @@ -50,7 +58,7 @@ export default class OAuthView extends React.PureComponent { } render() { - const { oAuthUrl, navigator } = this.props; + const { oAuthUrl } = this.props; return ( diff --git a/app/views/OnboardingView/index.js b/app/views/OnboardingView/index.js index 8883ccfe..f792e836 100644 --- a/app/views/OnboardingView/index.js +++ b/app/views/OnboardingView/index.js @@ -1,11 +1,12 @@ import React from 'react'; import { - View, Text, Image, SafeAreaView, TouchableOpacity + View, Text, Image, TouchableOpacity } from 'react-native'; import PropTypes from 'prop-types'; import Icon from 'react-native-vector-icons/MaterialIcons'; import { connect, Provider } from 'react-redux'; import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import { selectServerRequest, serverInitAdd, serverFinishAdd } from '../../actions/server'; import I18n from '../../i18n'; @@ -15,6 +16,7 @@ import styles from './styles'; import LoggedView from '../View'; import DeviceInfo from '../../utils/deviceInfo'; import store from '../../lib/createStore'; +import EventEmitter from '../../utils/events'; let NewServerView = null; @@ -28,8 +30,17 @@ let NewServerView = null; })) /** @extends React.Component */ export default class OnboardingView extends LoggedView { + static options() { + return { + topBar: { + visible: false, + drawBehind: true + } + }; + } + static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, previousServer: PropTypes.string, adding: PropTypes.bool, selectServer: PropTypes.func.isRequired, @@ -39,7 +50,7 @@ export default class OnboardingView extends LoggedView { } constructor(props) { - super('CreateChannelView', props); + super('OnboardingView', props); } componentDidMount() { @@ -47,6 +58,7 @@ export default class OnboardingView extends LoggedView { if (previousServer) { initAdd(); } + EventEmitter.addEventListener('NewServer', this.handleNewServerEvent); } componentWillUnmount() { @@ -59,46 +71,48 @@ export default class OnboardingView extends LoggedView { } finishAdd(); } + EventEmitter.removeListener('NewServer', this.handleNewServerEvent); } close = () => { - const { navigator } = this.props; - navigator.dismissModal(); + const { componentId } = this.props; + Navigation.dismissModal(componentId); + } + + newServer = (server) => { + if (NewServerView == null) { + NewServerView = require('../NewServerView').default; + Navigation.registerComponentWithRedux('NewServerView', () => NewServerView, Provider, store); + } + + const { componentId } = this.props; + Navigation.push(componentId, { + component: { + id: 'NewServerView', + name: 'NewServerView', + passProps: { + server + }, + options: { + topBar: { + visible: false + } + } + } + }); + } + + handleNewServerEvent = (event) => { + const { server } = event; + this.newServer(server); } connectServer = () => { - if (NewServerView == null) { - NewServerView = require('../NewServerView').default; - Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); - } - - const { navigator } = this.props; - navigator.push({ - screen: 'NewServerView', - backButtonTitle: '', - navigatorStyle: { - navBarHidden: true - } - }); + this.newServer(); } joinCommunity = () => { - if (NewServerView == null) { - NewServerView = require('../NewServerView').default; - Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); - } - - const { navigator } = this.props; - navigator.push({ - screen: 'NewServerView', - backButtonTitle: '', - passProps: { - server: 'https://open.rocket.chat' - }, - navigatorStyle: { - navBarHidden: true - } - }); + this.newServer('https://open.rocket.chat'); } createWorkspace = () => { @@ -132,7 +146,7 @@ export default class OnboardingView extends LoggedView { render() { return ( - + {I18n.t('Welcome_to_RocketChat')} {I18n.t('Open_Source_Communication')} diff --git a/app/views/PinnedMessagesView/index.js b/app/views/PinnedMessagesView/index.js index 9758947b..a37482a1 100644 --- a/app/views/PinnedMessagesView/index.js +++ b/app/views/PinnedMessagesView/index.js @@ -1,10 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - FlatList, View, Text, SafeAreaView -} from 'react-native'; +import { FlatList, View, Text } from 'react-native'; import { connect } from 'react-redux'; import ActionSheet from 'react-native-actionsheet'; +import SafeAreaView from 'react-native-safe-area-view'; import { openPinnedMessages as openPinnedMessagesAction, closePinnedMessages as closePinnedMessagesAction } from '../../actions/pinnedMessages'; import { togglePinRequest as togglePinRequestAction } from '../../actions/messages'; @@ -33,6 +32,16 @@ const options = [I18n.t('Unpin'), I18n.t('Cancel')]; })) /** @extends React.Component */ export default class PinnedMessagesView extends LoggedView { + static options() { + return { + topBar: { + title: { + text: I18n.t('Pinned') + } + } + }; + } + static propTypes = { rid: PropTypes.string, messages: PropTypes.array, @@ -136,7 +145,7 @@ export default class PinnedMessagesView extends LoggedView { } return ( - + ({ user: { @@ -37,9 +40,29 @@ import Touch from '../../utils/touch'; })) /** @extends React.Component */ export default class ProfileView extends LoggedView { + static options() { + return { + topBar: { + leftButtons: [{ + id: 'settings', + icon: { uri: 'settings', scale: Dimensions.get('window').scale }, + testID: 'rooms-list-view-sidebar' + }], + title: { + text: I18n.t('Profile') + } + }, + sideMenu: { + left: { + enabled: true + } + } + }; + } + static propTypes = { baseUrl: PropTypes.string, - navigator: PropTypes.object, + componentId: PropTypes.string, user: PropTypes.object, Accounts_CustomFields: PropTypes.string } @@ -59,29 +82,12 @@ export default class ProfileView extends LoggedView { avatarSuggestions: {}, customFields: {} }; - props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); - } - - componentWillMount() { - const { navigator } = this.props; - navigator.setButtons({ - leftButtons: [{ - id: 'settings', - icon: { uri: 'settings', scale: Dimensions.get('window').scale } - }] - }); + Navigation.events().bindComponent(this); } async componentDidMount() { - const { navigator } = this.props; - this.init(); - navigator.setDrawerEnabled({ - side: 'left', - enabled: true - }); - try { const result = await RocketChat.getAvatarSuggestion(); this.setState({ avatarSuggestions: result }); @@ -97,15 +103,9 @@ export default class ProfileView extends LoggedView { } } - onNavigatorEvent(event) { - const { navigator } = this.props; - - if (event.type === 'NavBarButtonPress') { - if (event.id === 'settings') { - navigator.toggleDrawer({ - side: 'left' - }); - } + navigationButtonPressed = ({ buttonId }) => { + if (buttonId === 'settings') { + Drawer.toggle(); } } @@ -396,7 +396,7 @@ export default class ProfileView extends LoggedView { testID='profile-view-list' {...scrollPersistTaps} > - + { if (TermsServiceView == null) { TermsServiceView = require('./TermsServiceView').default; - Navigation.registerComponent('TermsServiceView', () => TermsServiceView, store, Provider); + Navigation.registerComponentWithRedux('TermsServiceView', () => TermsServiceView, Provider, store); } - const { navigator } = this.props; - navigator.push({ - screen: 'TermsServiceView', - title: I18n.t('Terms_of_Service'), - backButtonTitle: '' + const { componentId } = this.props; + Navigation.push(componentId, { + component: { + name: 'TermsServiceView', + options: { + topBar: { + title: { + text: I18n.t('Terms_of_Service') + } + } + } + } }); } privacyPolicy = () => { if (PrivacyPolicyView == null) { PrivacyPolicyView = require('./PrivacyPolicyView').default; - Navigation.registerComponent('PrivacyPolicyView', () => PrivacyPolicyView, store, Provider); + Navigation.registerComponentWithRedux('PrivacyPolicyView', () => PrivacyPolicyView, Provider, store); } - const { navigator } = this.props; - navigator.push({ - screen: 'PrivacyPolicyView', - title: I18n.t('Privacy_Policy'), - backButtonTitle: '' + const { componentId } = this.props; + Navigation.push(componentId, { + component: { + name: 'PrivacyPolicyView', + options: { + topBar: { + title: { + text: I18n.t('Privacy_Policy') + } + } + } + } }); } @@ -244,7 +259,7 @@ export default class RegisterView extends LoggedView { return ( - + {I18n.t('Sign_Up')} {this._renderRegister()} {this._renderUsername()} diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 7b7a43cb..59922253 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -1,13 +1,14 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - View, SectionList, Text, Alert, SafeAreaView + View, SectionList, Text, Alert } from 'react-native'; import Icon from 'react-native-vector-icons/Ionicons'; import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import { connect, Provider } from 'react-redux'; import { Navigation } from 'react-native-navigation'; import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; +import SafeAreaView from 'react-native-safe-area-view'; import { leaveRoom as leaveRoomAction } from '../../actions/room'; import LoggedView from '../View'; @@ -37,10 +38,20 @@ const modules = {}; })) /** @extends React.Component */ export default class RoomActionsView extends LoggedView { + static options() { + return { + topBar: { + title: { + text: I18n.t('Actions') + } + } + }; + } + static propTypes = { baseUrl: PropTypes.string, rid: PropTypes.string, - navigator: PropTypes.object, + componentId: PropTypes.string, userId: PropTypes.string, username: PropTypes.string, leaveRoom: PropTypes.func @@ -69,18 +80,22 @@ export default class RoomActionsView extends LoggedView { } onPressTouchable = (item) => { - const { navigator } = this.props; - if (item.route) { if (modules[item.route] == null) { modules[item.route] = item.require(); - Navigation.registerComponent(item.route, () => gestureHandlerRootHOC(modules[item.route]), store, Provider); + Navigation.registerComponentWithRedux(item.route, () => gestureHandlerRootHOC(modules[item.route]), Provider, store); } - navigator.push({ - screen: item.route, - title: item.name, - passProps: item.params, - backButtonTitle: '' + + const { componentId } = this.props; + Navigation.push(componentId, { + component: { + name: item.route, + passProps: item.params + } + // screen: item.route, + // title: item.name, + // passProps: item.params, + // backButtonTitle: '' }); } if (item.event) { @@ -325,7 +340,7 @@ export default class RoomActionsView extends LoggedView { this.setState({ room: this.rooms[0] || {} }); } - toggleBlockUser = async() => { + toggleBlockUser = () => { const { room } = this.state; const { rid, blocker } = room; const { member } = this.state; @@ -440,7 +455,7 @@ export default class RoomActionsView extends LoggedView { render() { return ( - + + - + { this.name = e; }} label={I18n.t('Name')} diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index f66ba3fe..47e120c1 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -1,11 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, Text, ScrollView, SafeAreaView -} from 'react-native'; +import { View, Text, ScrollView } from 'react-native'; import { connect, Provider } from 'react-redux'; import moment from 'moment'; import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import LoggedView from '../View'; import Status from '../../containers/status'; @@ -45,8 +44,18 @@ let RoomInfoEditView = null; })) /** @extends React.Component */ export default class RoomInfoView extends LoggedView { + static options() { + return { + topBar: { + title: { + text: I18n.t('Room_Info') + } + } + }; + } + static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, rid: PropTypes.string, userId: PropTypes.string, baseUrl: PropTypes.string, @@ -67,12 +76,27 @@ export default class RoomInfoView extends LoggedView { roomUser: {}, roles: [] }; - props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); + Navigation.events().bindComponent(this); } componentDidMount() { this.updateRoom(); this.rooms.addListener(this.updateRoom); + + const [room] = this.rooms; + const { componentId } = this.props; + const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid); + if (permissions[PERMISSION_EDIT_ROOM]) { + Navigation.mergeOptions(componentId, { + topBar: { + rightButtons: [{ + id: 'edit', + icon: iconsMap.create, + testID: 'room-info-view-edit-button' + }] + } + }); + } } componentWillUnmount() { @@ -80,24 +104,23 @@ export default class RoomInfoView extends LoggedView { this.sub.unsubscribe(); } - onNavigatorEvent(event) { - const { rid, navigator } = this.props; - if (event.type === 'NavBarButtonPress') { - if (event.id === 'edit') { - if (RoomInfoEditView == null) { - RoomInfoEditView = require('../RoomInfoEditView').default; - Navigation.registerComponent('RoomInfoEditView', () => RoomInfoEditView, store, Provider); - } + navigationButtonPressed = ({ buttonId }) => { + const { rid, componentId } = this.props; + if (buttonId === 'edit') { + if (RoomInfoEditView == null) { + RoomInfoEditView = require('../RoomInfoEditView').default; + Navigation.registerComponentWithRedux('RoomInfoEditView', () => RoomInfoEditView, Provider, store); + } - navigator.push({ - screen: 'RoomInfoEditView', - title: I18n.t('Room_Info_Edit'), - backButtonTitle: '', + Navigation.push(componentId, { + component: { + id: 'RoomInfoEditView', + name: 'RoomInfoEditView', passProps: { rid } - }); - } + } + }); } } @@ -116,7 +139,7 @@ export default class RoomInfoView extends LoggedView { } updateRoom = async() => { - const { userId, activeUsers, navigator } = this.props; + const { userId, activeUsers } = this.props; const [room] = this.rooms; this.setState({ room }); @@ -146,22 +169,6 @@ export default class RoomInfoView extends LoggedView { } catch (e) { log('RoomInfoView.componentDidMount', e); } - } else { - const isVisible = await navigator.screenIsCurrentlyVisible(); - - if (!isVisible) { - return; - } - const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid); - if (permissions[PERMISSION_EDIT_ROOM]) { - navigator.setButtons({ - rightButtons: [{ - id: 'edit', - icon: iconsMap.create, - testID: 'room-info-view-edit-button' - }] - }); - } } } } @@ -278,7 +285,7 @@ export default class RoomInfoView extends LoggedView { } return ( - + {this.renderAvatar(room, roomUser)} { getRoomTitle(room) } diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index 0a732a46..b33d6da0 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -1,10 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - FlatList, View, Vibration, SafeAreaView + FlatList, View, Vibration, Platform } from 'react-native'; import ActionSheet from 'react-native-actionsheet'; import { connect } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import LoggedView from '../View'; import styles from './styles'; @@ -22,16 +24,24 @@ import SearchBox from '../../containers/SearchBox'; })) /** @extends React.Component */ export default class RoomMembersView extends LoggedView { - static navigatorButtons = { - rightButtons: [{ - title: 'All', - id: 'toggleOnline', - testID: 'room-members-view-toggle-status' - }] - }; + static options() { + return { + topBar: { + title: { + text: I18n.t('Members') + }, + rightButtons: [{ + id: 'toggleOnline', + text: I18n.t('Online'), + testID: 'room-members-view-toggle-status', + color: Platform.OS === 'android' ? '#FFF' : undefined + }] + } + }; + } static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, rid: PropTypes.string, members: PropTypes.array, baseUrl: PropTypes.string @@ -39,7 +49,6 @@ export default class RoomMembersView extends LoggedView { constructor(props) { super('MentionedMessagesView', props); - const { navigator } = this.props; this.CANCEL_INDEX = 0; this.MUTE_INDEX = 1; @@ -56,7 +65,7 @@ export default class RoomMembersView extends LoggedView { userLongPressed: {}, room: {} }; - navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); + Navigation.events().bindComponent(this); } componentDidMount() { @@ -67,27 +76,27 @@ export default class RoomMembersView extends LoggedView { this.rooms.removeAllListeners(); } - async onNavigatorEvent(event) { + navigationButtonPressed = async({ buttonId }) => { const { rid, allUsers } = this.state; - const { navigator } = this.props; + const { componentId } = this.props; - if (event.type === 'NavBarButtonPress') { - if (event.id === 'toggleOnline') { - try { - const allUsersFilter = !allUsers; - const membersResult = await RocketChat.getRoomMembers(rid, allUsersFilter); - const members = membersResult.records; - this.setState({ allUsers: allUsersFilter, members }); - navigator.setButtons({ + if (buttonId === 'toggleOnline') { + try { + Navigation.mergeOptions(componentId, { + topBar: { rightButtons: [{ - title: allUsers ? I18n.t('Online') : I18n.t('All'), id: 'toggleOnline', + text: allUsers ? I18n.t('Online') : I18n.t('All'), testID: 'room-members-view-toggle-status' }] - }); - } catch (e) { - log('RoomMembers.onNavigationButtonPressed', e); - } + } + }); + const allUsersFilter = !allUsers; + const membersResult = await RocketChat.getRoomMembers(rid, allUsersFilter); + const members = membersResult.records; + this.setState({ allUsers: allUsersFilter, members }); + } catch (e) { + log('RoomMembers.onNavigationButtonPressed', e); } } } @@ -143,17 +152,24 @@ export default class RoomMembersView extends LoggedView { await this.setState({ room }); } - goRoom = ({ rid, name }) => { - const { navigator } = this.props; - navigator.popToRoot(); - setTimeout(() => { - navigator.push({ - screen: 'RoomView', - title: name, - backButtonTitle: '', - passProps: { rid } - }); - }, 1000); + goRoom = async({ rid, name }) => { + const { componentId } = this.props; + await Navigation.popToRoot(componentId); + Navigation.push('RoomsListView', { + component: { + name: 'RoomView', + passProps: { + rid + }, + options: { + topBar: { + title: { + text: name + } + } + } + } + }); } handleMute = async() => { @@ -200,7 +216,7 @@ export default class RoomMembersView extends LoggedView { render() { const { filtering, members, membersFiltered } = this.state; return ( - + RoomActionsView, store, Provider); - } - - navigator.push({ - screen: 'RoomActionsView', - title: I18n.t('Actions'), - backButtonTitle: '', - passProps: { - rid - } - }); - } else if (event.id === 'star') { - try { - RocketChat.toggleFavorite(rid, f); - } catch (e) { - log('toggleFavorite', e); - } - } - } - } - onEndReached = debounce((lastRowData) => { if (!lastRowData) { - this.setState({ end: true }); + this.internalSetState({ end: true }); return; } @@ -193,7 +162,7 @@ export default class RoomView extends LoggedView { const { room } = this.state; try { const result = await RocketChat.loadMessagesForRoom({ rid: this.rid, t: room.t, latest: lastRowData.ts }); - this.setState({ end: result < 20 }); + this.internalSetState({ end: result < 20 }); } catch (e) { log('RoomView.onEndReached', e); } @@ -218,16 +187,57 @@ export default class RoomView extends LoggedView { } }; - updateRoom = async() => { - const { navigator, openRoom, setLastOpen } = this.props; + internalSetState = (...args) => { + LayoutAnimation.easeInEaseOut(); + this.setState(...args); + } + + navigationButtonPressed = ({ buttonId }) => { + const { room } = this.state; + const { rid, f } = room; + const { componentId } = this.props; + + if (buttonId === 'more') { + if (RoomActionsView == null) { + RoomActionsView = require('../RoomActionsView').default; + Navigation.registerComponentWithRedux('RoomActionsView', () => RoomActionsView, Provider, store); + } + + Navigation.push(componentId, { + component: { + id: 'RoomActionsView', + name: 'RoomActionsView', + passProps: { + rid + } + } + }); + } else if (buttonId === 'star') { + try { + RocketChat.toggleFavorite(rid, f); + } catch (e) { + log('toggleFavorite', e); + } + } + } + + updateRoom = () => { + const { componentId, openRoom, setLastOpen } = this.props; if (this.rooms.length > 0) { const { room: prevRoom } = this.state; const room = JSON.parse(JSON.stringify(this.rooms[0] || {})); - this.setState({ room }); + LayoutAnimation.easeInEaseOut(); + this.internalSetState({ room }); if (!prevRoom.rid) { - navigator.setTitle({ title: room.name }); + Navigation.mergeOptions(componentId, { + topBar: { + title: { + text: room.name + } + } + }); openRoom({ ...room }); @@ -239,7 +249,7 @@ export default class RoomView extends LoggedView { } } else { openRoom({ rid: this.rid }); - this.setState({ joined: false }); + this.internalSetState({ joined: false }); } } @@ -255,7 +265,7 @@ export default class RoomView extends LoggedView { const { rid } = this.props; try { await RocketChat.joinRoom(rid); - this.setState({ + this.internalSetState({ joined: true }); } catch (e) { @@ -380,7 +390,7 @@ export default class RoomView extends LoggedView { const { user, showActions, showErrorActions } = this.props; return ( - + {this.renderList()} {room._id && showActions ? diff --git a/app/views/RoomsListView/Header/Header.android.js b/app/views/RoomsListView/Header/Header.android.js index 07f2a5df..851b993e 100644 --- a/app/views/RoomsListView/Header/Header.android.js +++ b/app/views/RoomsListView/Header/Header.android.js @@ -3,6 +3,7 @@ import { Text, View, TouchableOpacity, Image, StyleSheet } from 'react-native'; import PropTypes from 'prop-types'; +import { TextInput } from 'react-native-gesture-handler'; const styles = StyleSheet.create({ container: { @@ -28,21 +29,41 @@ const styles = StyleSheet.create({ } }); -const Header = ({ onPress, serverName, showServerDropdown }) => ( - - - - {serverName} - +const Header = ({ + onPress, serverName, showServerDropdown, setSearchInputRef, showSearchHeader, onSearchChangeText +}) => { + if (showSearchHeader) { + return ( + + - - -); + ); + } + return ( + + + + {serverName} + + + + + ); +}; Header.propTypes = { + showServerDropdown: PropTypes.bool.isRequired, + showSearchHeader: PropTypes.bool.isRequired, onPress: PropTypes.func.isRequired, - serverName: PropTypes.string, - showServerDropdown: PropTypes.bool.isRequired + onSearchChangeText: PropTypes.func.isRequired, + setSearchInputRef: PropTypes.func.isRequired, + serverName: PropTypes.string }; Header.defaultProps = { diff --git a/app/views/RoomsListView/Header/index.js b/app/views/RoomsListView/Header/index.js index b00ef959..24d45b3b 100644 --- a/app/views/RoomsListView/Header/index.js +++ b/app/views/RoomsListView/Header/index.js @@ -2,26 +2,46 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { toggleServerDropdown, closeServerDropdown, closeSortDropdown } from '../../../actions/rooms'; +import { + toggleServerDropdown, closeServerDropdown, closeSortDropdown, setSearch as setSearchAction +} from '../../../actions/rooms'; import Header from './Header'; @connect(state => ({ showServerDropdown: state.rooms.showServerDropdown, showSortDropdown: state.rooms.showSortDropdown, + showSearchHeader: state.rooms.showSearchHeader, serverName: state.settings.Site_Name }), dispatch => ({ close: () => dispatch(closeServerDropdown()), open: () => dispatch(toggleServerDropdown()), - closeSort: () => dispatch(closeSortDropdown()) + closeSort: () => dispatch(closeSortDropdown()), + setSearch: searchText => dispatch(setSearchAction(searchText)) })) export default class RoomsListHeaderView extends Component { static propTypes = { showServerDropdown: PropTypes.bool, showSortDropdown: PropTypes.bool, + showSearchHeader: PropTypes.bool, serverName: PropTypes.string, open: PropTypes.func, close: PropTypes.func, - closeSort: PropTypes.func + closeSort: PropTypes.func, + setSearch: PropTypes.func + } + + componentDidUpdate(prevProps) { + const { showSearchHeader } = this.props; + if (showSearchHeader && prevProps.showSearchHeader !== showSearchHeader) { + setTimeout(() => { + this.searchInputRef.focus(); + }, 300); + } + } + + onSearchChangeText = (text) => { + const { setSearch } = this.props; + setSearch(text.trim()); } onPress = () => { @@ -40,13 +60,21 @@ export default class RoomsListHeaderView extends Component { } } + setSearchInputRef = (ref) => { + this.searchInputRef = ref; + } + + render() { - const { serverName, showServerDropdown } = this.props; + const { serverName, showServerDropdown, showSearchHeader } = this.props; return (
this.onSearchChangeText(text)} /> ); } diff --git a/app/views/RoomsListView/ServerDropdown.js b/app/views/RoomsListView/ServerDropdown.js index 4573ceb8..81e2322a 100644 --- a/app/views/RoomsListView/ServerDropdown.js +++ b/app/views/RoomsListView/ServerDropdown.js @@ -3,7 +3,7 @@ import { View, Text, Animated, Easing, TouchableWithoutFeedback, TouchableOpacity, FlatList, Image, AsyncStorage } from 'react-native'; import PropTypes from 'prop-types'; -import { connect, Provider } from 'react-redux'; +import { connect } from 'react-redux'; import { Navigation } from 'react-native-navigation'; import * as SDK from '@rocket.chat/sdk'; @@ -15,13 +15,11 @@ import database from '../../lib/realm'; import Touch from '../../utils/touch'; import RocketChat from '../../lib/rocketchat'; import I18n from '../../i18n'; -import store from '../../lib/createStore'; +import EventEmitter from '../../utils/events'; const ROW_HEIGHT = 68; const ANIMATION_DURATION = 200; -let NewServerView = null; - @connect(state => ({ closeServerDropdown: state.rooms.closeServerDropdown, server: state.server.server @@ -32,7 +30,6 @@ let NewServerView = null; })) export default class ServerDropdown extends Component { static propTypes = { - navigator: PropTypes.object, closeServerDropdown: PropTypes.bool, server: PropTypes.string, toggleServerDropdown: PropTypes.func, @@ -88,18 +85,28 @@ export default class ServerDropdown extends Component { } addServer = () => { - const { navigator, server } = this.props; + const { server } = this.props; this.close(); setTimeout(() => { - navigator.showModal({ - screen: 'OnboardingView', - passProps: { - previousServer: server - }, - navigatorStyle: { - navBarHidden: true, - orientation: 'portrait' + Navigation.showModal({ + stack: { + children: [{ + component: { + name: 'OnboardingView', + passProps: { + previousServer: server + }, + options: { + topBar: { + visible: false + }, + layout: { + orientation: 'portrait' + } + } + } + }] } }); }, ANIMATION_DURATION); @@ -107,7 +114,7 @@ export default class ServerDropdown extends Component { select = async(server) => { const { - server: currentServer, selectServerRequest, appStart, navigator + server: currentServer, selectServerRequest, appStart } = this.props; this.close(); @@ -120,21 +127,8 @@ export default class ServerDropdown extends Component { } catch (error) { console.warn(error); } - if (NewServerView == null) { - NewServerView = require('../NewServerView').default; - Navigation.registerComponent('NewServerView', () => NewServerView, store, Provider); - } setTimeout(() => { - navigator.push({ - screen: 'NewServerView', - backButtonTitle: '', - passProps: { - server - }, - navigatorStyle: { - navBarHidden: true - } - }); + EventEmitter.emit('NewServer', { server }); }, 1000); } else { selectServerRequest(server); diff --git a/app/views/RoomsListView/SortDropdown.js b/app/views/RoomsListView/SortDropdown.js index c44c8504..f011d716 100644 --- a/app/views/RoomsListView/SortDropdown.js +++ b/app/views/RoomsListView/SortDropdown.js @@ -54,7 +54,7 @@ export default class Sort extends Component { } } - setSortPreference = async(param) => { + setSortPreference = (param) => { const { setSortPreference } = this.props; try { diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index 48416fda..ac8cb425 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -1,11 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - Platform, View, FlatList, BackHandler, ActivityIndicator, SafeAreaView, Text, Image, Dimensions, ScrollView, Keyboard + Platform, View, FlatList, BackHandler, ActivityIndicator, Text, Image, Dimensions, ScrollView, Keyboard, LayoutAnimation } from 'react-native'; import { connect, Provider } from 'react-redux'; import { isEqual } from 'lodash'; import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import SearchBox from '../../containers/SearchBox'; import ConnectionBadge from '../../containers/ConnectionBadge'; @@ -19,8 +20,9 @@ import I18n from '../../i18n'; import SortDropdown from './SortDropdown'; import ServerDropdown from './ServerDropdown'; import Touch from '../../utils/touch'; -import { toggleSortDropdown as toggleSortDropdownAction } from '../../actions/rooms'; +import { toggleSortDropdown as toggleSortDropdownAction, openSearchHeader as openSearchHeaderAction, closeSearchHeader as closeSearchHeaderAction } from '../../actions/rooms'; import store from '../../lib/createStore'; +import Drawer from '../../Drawer'; const ROW_HEIGHT = 70; const SCROLL_OFFSET = 56; @@ -63,24 +65,33 @@ let NewMessageView = null; showUnread: state.sortPreferences.showUnread, useRealName: state.settings.UI_Use_Real_Name }), dispatch => ({ - toggleSortDropdown: () => dispatch(toggleSortDropdownAction()) + toggleSortDropdown: () => dispatch(toggleSortDropdownAction()), + openSearchHeader: () => dispatch(openSearchHeaderAction()), + closeSearchHeader: () => dispatch(closeSearchHeaderAction()) })) /** @extends React.Component */ export default class RoomsListView extends LoggedView { - static navigatorButtons = { - leftButtons, rightButtons - } - - static navigatorStyle = { - navBarCustomView: 'RoomsListHeaderView', - navBarComponentAlignment: 'fill', - navBarBackgroundColor: isAndroid() ? '#2F343D' : undefined, - navBarTextColor: isAndroid() ? '#FFF' : undefined, - navBarButtonColor: isAndroid() ? '#FFF' : undefined + static options() { + return { + topBar: { + leftButtons, + rightButtons, + title: { + component: { + name: 'RoomsListHeaderView', + alignment: isAndroid() ? 'left' : 'center' + } + } + }, + sideMenu: { + left: { + enabled: true + } + } + }; } static propTypes = { - navigator: PropTypes.object, userId: PropTypes.string, baseUrl: PropTypes.string, server: PropTypes.string, @@ -93,7 +104,9 @@ export default class RoomsListView extends LoggedView { showFavorites: PropTypes.bool, showUnread: PropTypes.bool, useRealName: PropTypes.bool, - toggleSortDropdown: PropTypes.func + toggleSortDropdown: PropTypes.func, + openSearchHeader: PropTypes.func, + closeSearchHeader: PropTypes.func } constructor(props) { @@ -111,11 +124,7 @@ export default class RoomsListView extends LoggedView { direct: [], livechat: [] }; - props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); - } - - componentWillMount() { - this.initDefaultHeader(); + Navigation.events().bindComponent(this); } componentDidMount() { @@ -127,7 +136,7 @@ export default class RoomsListView extends LoggedView { if (nextProps.server && loadingServer !== nextProps.loadingServer) { if (nextProps.loadingServer) { - this.setState({ loading: true }); + this.internalSetState({ loading: true }); } else { this.getSubscriptions(); } @@ -169,39 +178,46 @@ export default class RoomsListView extends LoggedView { } } - onNavigatorEvent(event) { - const { navigator } = this.props; - if (event.type === 'NavBarButtonPress') { - if (event.id === 'newMessage') { - if (NewMessageView == null) { - NewMessageView = require('../NewMessageView').default; - Navigation.registerComponent('NewMessageView', () => NewMessageView, store, Provider); - } - - navigator.showModal({ - screen: 'NewMessageView', - title: I18n.t('New_Message'), - passProps: { - onPressItem: this._onPressItem - } - }); - } else if (event.id === 'settings') { - navigator.toggleDrawer({ - side: 'left' - }); - } else if (event.id === 'search') { - this.initSearchingAndroid(); - } else if (event.id === 'cancelSearch' || event.id === 'back') { - this.cancelSearchingAndroid(); + navigationButtonPressed = ({ buttonId }) => { + if (buttonId === 'newMessage') { + if (NewMessageView == null) { + NewMessageView = require('../NewMessageView').default; + Navigation.registerComponentWithRedux('NewMessageView', () => NewMessageView, Provider, store); } - } else if (event.type === 'ScreenChangedEvent' && event.id === 'didAppear') { - navigator.setDrawerEnabled({ - side: 'left', - enabled: true + + Navigation.showModal({ + stack: { + children: [{ + component: { + name: 'NewMessageView', + passProps: { + onPressItem: this._onPressItem + }, + options: { + topBar: { + title: { + text: I18n.t('New_Message') + } + } + } + } + }] + } }); + } else if (buttonId === 'settings') { + Drawer.toggle(); + } else if (buttonId === 'search') { + this.initSearchingAndroid(); + } else if (buttonId === 'back') { + this.cancelSearchingAndroid(); } } + internalSetState = (...args) => { + LayoutAnimation.easeInEaseOut(); + this.setState(...args); + } + getSubscriptions = () => { const { server, sortBy, showUnread, showFavorites, groupByType @@ -227,7 +243,7 @@ export default class RoomsListView extends LoggedView { this.unread = this.data.filtered('archived != true && open == true').filtered('(unread > 0 || alert == true)'); unread = this.removeRealmInstance(this.unread); setTimeout(() => { - this.unread.addListener(() => this.setState({ unread: this.removeRealmInstance(this.unread) })); + this.unread.addListener(() => this.internalSetState({ unread: this.removeRealmInstance(this.unread) })); }); } else { this.removeListener(unread); @@ -237,7 +253,7 @@ export default class RoomsListView extends LoggedView { this.favorites = this.data.filtered('f == true'); favorites = this.removeRealmInstance(this.favorites); setTimeout(() => { - this.favorites.addListener(() => this.setState({ favorites: this.removeRealmInstance(this.favorites) })); + this.favorites.addListener(() => this.internalSetState({ favorites: this.removeRealmInstance(this.favorites) })); }); } else { this.removeListener(favorites); @@ -261,10 +277,10 @@ export default class RoomsListView extends LoggedView { livechat = this.removeRealmInstance(this.livechat); setTimeout(() => { - this.channels.addListener(() => this.setState({ channels: this.removeRealmInstance(this.channels) })); - this.privateGroup.addListener(() => this.setState({ privateGroup: this.removeRealmInstance(this.privateGroup) })); - this.direct.addListener(() => this.setState({ direct: this.removeRealmInstance(this.direct) })); - this.livechat.addListener(() => this.setState({ livechat: this.removeRealmInstance(this.livechat) })); + this.channels.addListener(() => this.internalSetState({ channels: this.removeRealmInstance(this.channels) })); + this.privateGroup.addListener(() => this.internalSetState({ privateGroup: this.removeRealmInstance(this.privateGroup) })); + this.direct.addListener(() => this.internalSetState({ direct: this.removeRealmInstance(this.direct) })); + this.livechat.addListener(() => this.internalSetState({ livechat: this.removeRealmInstance(this.livechat) })); }); this.removeListener(this.chats); } else { @@ -278,7 +294,7 @@ export default class RoomsListView extends LoggedView { setTimeout(() => { this.chats.addListener(() => { - this.setState({ chats: this.removeRealmInstance(this.chats) }); + this.internalSetState({ chats: this.removeRealmInstance(this.chats) }); }); }); this.removeListener(this.channels); @@ -288,12 +304,12 @@ export default class RoomsListView extends LoggedView { } // setState - this.setState({ + this.internalSetState({ chats, unread, favorites, channels, privateGroup, direct, livechat }); } this.timeout = setTimeout(() => { - this.setState({ loading: false }); + this.internalSetState({ loading: false }); }, 200); } @@ -308,46 +324,41 @@ export default class RoomsListView extends LoggedView { } } - initDefaultHeader = () => { - const { navigator } = this.props; - navigator.setButtons({ leftButtons, rightButtons }); - navigator.setStyle({ - navBarCustomView: 'RoomsListHeaderView', - navBarComponentAlignment: 'fill', - navBarBackgroundColor: isAndroid() ? '#2F343D' : undefined, - navBarTextColor: isAndroid() ? '#FFF' : undefined, - navBarButtonColor: isAndroid() ? '#FFF' : undefined - }); - } - initSearchingAndroid = () => { - const { navigator } = this.props; - navigator.setButtons({ - leftButtons: [{ - id: 'cancelSearch', - icon: { uri: 'back', scale: Dimensions.get('window').scale } - }], - rightButtons: [] - }); - navigator.setStyle({ - navBarCustomView: 'RoomsListSearchView', - navBarComponentAlignment: 'fill' + const { openSearchHeader } = this.props; + openSearchHeader(); + Navigation.mergeOptions('RoomsListView', { + topBar: { + leftButtons: [{ + id: 'back', + icon: { uri: 'back', scale: Dimensions.get('window').scale }, + testID: 'rooms-list-view-cancel-search' + }], + rightButtons: [] + } }); BackHandler.addEventListener('hardwareBackPress', this.handleBackPress); } - // this is necessary during development (enables Cmd + r) - hasActiveDB = () => database && database.databases && database.databases.activeDB; - cancelSearchingAndroid = () => { if (Platform.OS === 'android') { - this.setState({ search: [] }); - this.initDefaultHeader(); + const { closeSearchHeader } = this.props; + closeSearchHeader(); + Navigation.mergeOptions('RoomsListView', { + topBar: { + leftButtons, + rightButtons + } + }); + this.internalSetState({ search: [] }); Keyboard.dismiss(); BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress); } } + // this is necessary during development (enables Cmd + r) + hasActiveDB = () => database && database.databases && database.databases.activeDB; + handleBackPress = () => { this.cancelSearchingAndroid(); return true; @@ -357,18 +368,26 @@ export default class RoomsListView extends LoggedView { search = async(text) => { const result = await RocketChat.search({ text }); - this.setState({ + this.internalSetState({ search: result }); } goRoom = (rid, name) => { - const { navigator } = this.props; - navigator.push({ - screen: 'RoomView', - title: name, - backButtonTitle: '', - passProps: { rid } + Navigation.push('RoomsListView', { + component: { + name: 'RoomView', + passProps: { + rid + }, + options: { + topBar: { + title: { + text: name + } + } + } + } }); this.cancelSearchingAndroid(); } @@ -559,11 +578,11 @@ export default class RoomsListView extends LoggedView { render = () => { const { - sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown, navigator + sortBy, groupByType, showFavorites, showUnread, showServerDropdown, showSortDropdown } = this.props; return ( - + {this.renderScroll()} {showSortDropdown ? ( diff --git a/app/views/SearchMessagesView/index.js b/app/views/SearchMessagesView/index.js index 7f0c215e..22b48ee5 100644 --- a/app/views/SearchMessagesView/index.js +++ b/app/views/SearchMessagesView/index.js @@ -1,7 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { View, FlatList, SafeAreaView } from 'react-native'; +import { View, FlatList } from 'react-native'; import { connect } from 'react-redux'; +import SafeAreaView from 'react-native-safe-area-view'; import LoggedView from '../View'; import RCTextInput from '../../containers/TextInput'; @@ -26,9 +27,19 @@ import I18n from '../../i18n'; })) /** @extends React.Component */ export default class SearchMessagesView extends LoggedView { + static options() { + return { + topBar: { + title: { + text: I18n.t('Search') + } + } + }; + } + static propTypes = { rid: PropTypes.string, - navigator: PropTypes.object, + componentId: PropTypes.string, user: PropTypes.object, baseUrl: PropTypes.string } @@ -120,7 +131,7 @@ export default class SearchMessagesView extends LoggedView { render() { const { searching, loadingMore, messages } = this.state; return ( - + { this.name = e; }} diff --git a/app/views/SelectedUsersView.js b/app/views/SelectedUsersView.js index 7fbfc601..a70f7be9 100644 --- a/app/views/SelectedUsersView.js +++ b/app/views/SelectedUsersView.js @@ -1,10 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { - View, StyleSheet, SafeAreaView, FlatList, LayoutAnimation, Platform + View, StyleSheet, FlatList, LayoutAnimation, Platform } from 'react-native'; import { connect, Provider } from 'react-redux'; import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import { addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction @@ -49,7 +50,7 @@ let CreateChannelView = null; /** @extends React.Component */ export default class SelectedUsersView extends LoggedView { static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, rid: PropTypes.string, nextAction: PropTypes.string.isRequired, baseUrl: PropTypes.string, @@ -68,35 +69,27 @@ export default class SelectedUsersView extends LoggedView { search: [] }; this.data.addListener(this.updateState); - props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); + Navigation.events().bindComponent(this); } - componentDidMount() { - const { navigator } = this.props; - navigator.setDrawerEnabled({ - side: 'left', - enabled: false - }); - } - - async componentDidUpdate(prevProps) { - const { navigator, users } = this.props; - const isVisible = await navigator.screenIsCurrentlyVisible(); - - if (!isVisible) { - return; - } + componentDidUpdate(prevProps) { + const { componentId, users } = this.props; if (prevProps.users.length !== users.length) { const { length } = users; const rightButtons = []; if (length > 0) { rightButtons.push({ id: 'create', - title: I18n.t('Next'), - testID: 'selected-users-view-submit' + text: I18n.t('Next'), + testID: 'selected-users-view-submit', + color: Platform.OS === 'android' ? '#FFF' : undefined }); } - navigator.setButtons({ rightButtons }); + Navigation.mergeOptions(componentId, { + topBar: { + rightButtons + } + }); } } @@ -107,41 +100,48 @@ export default class SelectedUsersView extends LoggedView { reset(); } - async onNavigatorEvent(event) { - if (event.type === 'NavBarButtonPress') { - if (event.id === 'create') { - const { nextAction, setLoadingInvite, navigator } = this.props; - if (nextAction === 'CREATE_CHANNEL') { - if (CreateChannelView == null) { - CreateChannelView = require('./CreateChannelView').default; - Navigation.registerComponent('CreateChannelView', () => CreateChannelView, store, Provider); - } + onSearchChangeText(text) { + this.search(text); + } - navigator.push({ - screen: 'CreateChannelView', - title: I18n.t('Create_Channel'), - backButtonTitle: '' - }); - } else { - const { rid } = this.props; - try { - setLoadingInvite(true); - await RocketChat.addUsersToRoom(rid); - navigator.pop(); - } catch (e) { - log('RoomActions Add User', e); - } finally { - setLoadingInvite(false); + navigationButtonPressed = async({ buttonId }) => { + if (buttonId === 'create') { + const { nextAction, setLoadingInvite } = this.props; + if (nextAction === 'CREATE_CHANNEL') { + const { componentId } = this.props; + + if (CreateChannelView == null) { + CreateChannelView = require('./CreateChannelView').default; + Navigation.registerComponentWithRedux('CreateChannelView', () => CreateChannelView, Provider, store); + } + + Navigation.push(componentId, { + component: { + name: 'CreateChannelView', + options: { + topBar: { + title: { + text: I18n.t('Create_Channel') + } + } + } } + }); + } else { + const { rid, componentId } = this.props; + try { + setLoadingInvite(true); + await RocketChat.addUsersToRoom(rid); + Navigation.pop(componentId); + } catch (e) { + log('RoomActions Add User', e); + } finally { + setLoadingInvite(false); } } } } - onSearchChangeText(text) { - this.search(text); - } - // eslint-disable-next-line react/sort-comp updateState = debounce(() => { this.forceUpdate(); @@ -271,7 +271,7 @@ export default class SelectedUsersView extends LoggedView { render = () => { const { loading } = this.props; return ( - + {this.renderList()} diff --git a/app/views/SettingsView/index.js b/app/views/SettingsView/index.js index b4fe4945..73e09b98 100644 --- a/app/views/SettingsView/index.js +++ b/app/views/SettingsView/index.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - View, ScrollView, SafeAreaView, Dimensions -} from 'react-native'; +import { View, ScrollView, Dimensions } from 'react-native'; import RNPickerSelect from 'react-native-picker-select'; import { connect } from 'react-redux'; +import { Navigation } from 'react-native-navigation'; +import SafeAreaView from 'react-native-safe-area-view'; import LoggedView from '../View'; import RocketChat from '../../lib/rocketchat'; @@ -18,6 +18,7 @@ import Loading from '../../containers/Loading'; import { showErrorAlert, showToast } from '../../utils/info'; import log from '../../utils/log'; import { setUser as setUserAction } from '../../actions/login'; +import Drawer from '../../Drawer'; @connect(state => ({ userLanguage: state.login.user && state.login.user.language @@ -26,8 +27,28 @@ import { setUser as setUserAction } from '../../actions/login'; })) /** @extends React.Component */ export default class SettingsView extends LoggedView { + static options() { + return { + topBar: { + leftButtons: [{ + id: 'settings', + icon: { uri: 'settings', scale: Dimensions.get('window').scale }, + testID: 'rooms-list-view-sidebar' + }], + title: { + text: I18n.t('Settings') + } + }, + sideMenu: { + left: { + enabled: true + } + } + }; + } + static propTypes = { - navigator: PropTypes.object, + componentId: PropTypes.string, userLanguage: PropTypes.string, setUser: PropTypes.func } @@ -49,35 +70,12 @@ export default class SettingsView extends LoggedView { }], saving: false }; - props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); + Navigation.events().bindComponent(this); } - componentWillMount() { - const { navigator } = this.props; - navigator.setButtons({ - leftButtons: [{ - id: 'settings', - icon: { uri: 'settings', scale: Dimensions.get('window').scale } - }] - }); - } - - componentDidMount() { - const { navigator } = this.props; - navigator.setDrawerEnabled({ - side: 'left', - enabled: true - }); - } - - onNavigatorEvent(event) { - const { navigator } = this.props; - if (event.type === 'NavBarButtonPress') { - if (event.id === 'settings') { - navigator.toggleDrawer({ - side: 'left' - }); - } + navigationButtonPressed = ({ buttonId }) => { + if (buttonId === 'settings') { + Drawer.toggle(); } } @@ -100,7 +98,7 @@ export default class SettingsView extends LoggedView { this.setState({ saving: true }); const { language } = this.state; - const { userLanguage, setUser, navigator } = this.props; + const { userLanguage, setUser } = this.props; if (!this.formIsChanged()) { return; @@ -122,7 +120,14 @@ export default class SettingsView extends LoggedView { showToast(I18n.t('Preferences_saved')); if (params.language) { - navigator.setTitle({ title: I18n.t('Settings') }); + const { componentId } = this.props; + Navigation.mergeOptions(componentId, { + topBar: { + title: { + text: I18n.t('Settings') + } + } + }); } }, 300); } catch (e) { @@ -148,7 +153,7 @@ export default class SettingsView extends LoggedView { testID='settings-view-list' {...scrollPersistTaps} > - + { diff --git a/app/views/SnippetedMessagesView/index.js b/app/views/SnippetedMessagesView/index.js index e856a0e3..55e5eb62 100644 --- a/app/views/SnippetedMessagesView/index.js +++ b/app/views/SnippetedMessagesView/index.js @@ -1,9 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { - FlatList, View, Text, SafeAreaView -} from 'react-native'; +import { FlatList, View, Text } from 'react-native'; import { connect } from 'react-redux'; +import SafeAreaView from 'react-native-safe-area-view'; import { openSnippetedMessages as openSnippetedMessagesAction, closeSnippetedMessages as closeSnippetedMessagesAction } from '../../actions/snippetedMessages'; import LoggedView from '../View'; @@ -26,6 +25,16 @@ import I18n from '../../i18n'; })) /** @extends React.Component */ export default class SnippetedMessagesView extends LoggedView { + static options() { + return { + topBar: { + title: { + text: I18n.t('Snippets') + } + } + }; + } + static propTypes = { rid: PropTypes.string, messages: PropTypes.array, @@ -106,7 +115,7 @@ export default class SnippetedMessagesView extends LoggedView { } return ( - + + Platform.OS === 'android'; /** @extends React.Component */ export default class extends React.Component { - static navigatorStyle = { - navBarBackgroundColor: isAndroid() ? '#2F343D' : undefined, - navBarTextColor: isAndroid() ? '#FFF' : undefined, - navBarButtonColor: isAndroid() ? '#FFF' : undefined - } - - static propTypes = { - navigator: PropTypes.object - } - constructor(name, props) { super(props); - NavigationActions.setNavigator(props.navigator); Answers.logContentView(name); } diff --git a/app/views/index.js b/app/views/index.js index c48953fd..8fe13234 100644 --- a/app/views/index.js +++ b/app/views/index.js @@ -5,19 +5,17 @@ import { gestureHandlerRootHOC } from 'react-native-gesture-handler'; import OnboardingView from './OnboardingView'; import ProfileView from './ProfileView'; import RoomsListHeaderView from './RoomsListView/Header'; -import RoomsListSearchView from './RoomsListView/Search'; import RoomsListView from './RoomsListView'; import RoomView from './RoomView'; import SettingsView from './SettingsView'; import Sidebar from '../containers/Sidebar'; export const registerScreens = (store) => { - Navigation.registerComponent('OnboardingView', () => OnboardingView, store, Provider); - Navigation.registerComponent('ProfileView', () => ProfileView, store, Provider); - Navigation.registerComponent('RoomsListHeaderView', () => RoomsListHeaderView, store, Provider); - Navigation.registerComponent('RoomsListSearchView', () => RoomsListSearchView, store, Provider); - Navigation.registerComponent('RoomsListView', () => gestureHandlerRootHOC(RoomsListView), store, Provider); - Navigation.registerComponent('RoomView', () => gestureHandlerRootHOC(RoomView), store, Provider); - Navigation.registerComponent('SettingsView', () => SettingsView, store, Provider); - Navigation.registerComponent('Sidebar', () => Sidebar, store, Provider); + Navigation.registerComponentWithRedux('OnboardingView', () => OnboardingView, Provider, store); + Navigation.registerComponentWithRedux('ProfileView', () => ProfileView, Provider, store); + Navigation.registerComponentWithRedux('RoomsListHeaderView', () => RoomsListHeaderView, Provider, store); + Navigation.registerComponentWithRedux('RoomsListView', () => gestureHandlerRootHOC(RoomsListView), Provider, store); + Navigation.registerComponentWithRedux('RoomView', () => gestureHandlerRootHOC(RoomView), Provider, store); + Navigation.registerComponentWithRedux('SettingsView', () => SettingsView, Provider, store); + Navigation.registerComponentWithRedux('Sidebar', () => Sidebar, Provider, store); }; diff --git a/e2e/05-roomslist.spec.js b/e2e/05-roomslist.spec.js index 28d0449d..e526fe99 100644 --- a/e2e/05-roomslist.spec.js +++ b/e2e/05-roomslist.spec.js @@ -52,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(2); + 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-search')).replaceText(''); diff --git a/e2e/06-createroom.spec.js b/e2e/06-createroom.spec.js index 897bfc16..673304e6 100644 --- a/e2e/06-createroom.spec.js +++ b/e2e/06-createroom.spec.js @@ -32,7 +32,7 @@ describe('Create room screen', () => { describe('Usage', async() => { it('should back to rooms list', async() => { - await tapBack(); + await element(by.text('Cancel')).tap(); 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(); @@ -48,7 +48,7 @@ describe('Create room 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(2); + await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await element(by.id('rooms-list-view-create-channel')).tap(); }); @@ -120,7 +120,7 @@ describe('Create room screen', () => { 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 tapBack(); 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(); @@ -143,7 +143,7 @@ describe('Create room screen', () => { 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 tapBack(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await element(by.id('rooms-list-view-search')).replaceText(`private${ data.random }`); await waitFor(element(by.id(`rooms-list-view-item-private${ data.random }`))).toBeVisible().withTimeout(60000); diff --git a/e2e/07-room.spec.js b/e2e/07-room.spec.js index 09cd43da..06a59adb 100644 --- a/e2e/07-room.spec.js +++ b/e2e/07-room.spec.js @@ -75,7 +75,7 @@ describe('Room screen', () => { describe('Usage', async() => { describe('Header', async() => { it('should back to rooms list', async() => { - await tapBack(2); + await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('rooms-list-view'))).toBeVisible(); await navigateToRoom(); @@ -290,7 +290,7 @@ describe('Room screen', () => { after(async() => { await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(5000); - await tapBack(2); + await tapBack(); 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 f2f0c0ea..6b0763d2 100644 --- a/e2e/08-roomactions.spec.js +++ b/e2e/08-roomactions.spec.js @@ -23,8 +23,8 @@ async function navigateToRoomActions(type) { await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(5000); } -async function backToActions(index = 0) { - await tapBack(index); +async function backToActions() { + await tapBack(); await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('room-actions-view'))).toBeVisible(); } @@ -32,7 +32,7 @@ async function backToActions(index = 0) { async function backToRoomsList() { await tapBack(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); - await tapBack(2); + await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); } @@ -367,7 +367,7 @@ 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(2); + await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); }); diff --git a/e2e/11-broadcast.spec.js b/e2e/11-broadcast.spec.js index de4383c4..7da696cf 100644 --- a/e2e/11-broadcast.spec.js +++ b/e2e/11-broadcast.spec.js @@ -32,11 +32,11 @@ 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(1); + await tapBack(); await waitFor(element(by.id('room-actions-view'))).toBeVisible().withTimeout(2000); await tapBack(); await waitFor(element(by.id('room-view'))).toBeVisible().withTimeout(2000); - // await tapBack(2); + // await tapBack(); // await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); // await waitFor(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist().withTimeout(60000); // await expect(element(by.id(`rooms-list-view-item-broadcast${ data.random }`))).toExist(); @@ -53,7 +53,7 @@ describe('Broadcast room', () => { }); it('should login as user without write message authorization and enter room', async() => { - await tapBack(2); + await tapBack(); await waitFor(element(by.id('rooms-list-view'))).toBeVisible().withTimeout(2000); await expect(element(by.id('rooms-list-view'))).toBeVisible(); await logout(); @@ -110,7 +110,7 @@ describe('Broadcast room', () => { after(async() => { // log back as main test user and left screen on RoomsListView - await tapBack(2); + await tapBack(); 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 1da062ca..6825c730 100644 --- a/e2e/helpers/app.js +++ b/e2e/helpers/app.js @@ -36,8 +36,8 @@ async function logout() { await expect(element(by.id('onboarding-view'))).toBeVisible(); } -async function tapBack(index) { - await element(by.type('_UIModernBarButton')).atIndex(index || 0).tap(); +async function tapBack() { + await element(by.type('_UIModernBarButton').withAncestor(by.type('_UIBackButtonContainerView'))).tap(); } async function sleep(ms) { diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 4dc7cc7e..9b8f0204 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -54,8 +54,8 @@ 7A309C9C20724870000C6B13 /* Fabric.sh in Resources */ = {isa = PBXBuildFile; fileRef = 7A309C9B20724870000C6B13 /* Fabric.sh */; }; 7A32C246206D791D001C80E9 /* Fabric.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A32C20F206D791D001C80E9 /* Fabric.framework */; }; 7A32C247206D791D001C80E9 /* Crashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A32C245206D791D001C80E9 /* Crashlytics.framework */; }; - 7A3562E620E1569000A4CF66 /* libReactNativeNavigation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A3562E520E1567900A4CF66 /* libReactNativeNavigation.a */; }; 7A430E4F20238C46008F55BC /* libRCTCustomInputController.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */; }; + 7A807B55215EC60500A4348D /* libReactNativeNavigation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A807B52215EC5E500A4348D /* libReactNativeNavigation.a */; }; 7A8DEB5A20ED0BEC00C5DCE4 /* libRNNotifications.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A8DEB5220ED0BDE00C5DCE4 /* libRNNotifications.a */; }; 7AFB806E205AE65700D004E7 /* libRCTToast.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7AFB804C205AE63100D004E7 /* libRCTToast.a */; }; 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; @@ -308,13 +308,6 @@ remoteGlobalIDString = 6463C84C1EBA12A60095B8CD; remoteInfo = "SMXCrashlytics-tvOS"; }; - 7A3562E420E1567900A4CF66 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 7A3562E020E1567900A4CF66 /* ReactNativeNavigation.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = D8AFADBD1BEE6F3F00A4592D; - remoteInfo = ReactNativeNavigation; - }; 7A430E1D20238C02008F55BC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */; @@ -357,6 +350,20 @@ remoteGlobalIDString = 641E28441F0EEC8500443AF6; remoteInfo = "RCTVideo-tvOS"; }; + 7A807B51215EC5E500A4348D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7A807B4C215EC5E400A4348D /* ReactNativeNavigation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = D8AFADBD1BEE6F3F00A4592D; + remoteInfo = ReactNativeNavigation; + }; + 7A807B53215EC5E500A4348D /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7A807B4C215EC5E400A4348D /* ReactNativeNavigation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 7B49FEBB1E95090800DEB3EA; + remoteInfo = ReactNativeNavigationTests; + }; 7A8C915220F39A8000C8F5EE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 0B82BCC462E84F308C5B5CD1 /* RNFetchBlob.xcodeproj */; @@ -541,8 +548,8 @@ 7A30DA4B2D474348824CD05B /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = ""; }; 7A32C20F206D791D001C80E9 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Fabric.framework; path = "../../../../Downloads/com.crashlytics.ios-manual/Fabric.framework"; sourceTree = ""; }; 7A32C245206D791D001C80E9 /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crashlytics.framework; path = "../../../../Downloads/com.crashlytics.ios-manual/Crashlytics.framework"; sourceTree = ""; }; - 7A3562E020E1567900A4CF66 /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = ""; }; 7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCustomInputController.xcodeproj; path = "../node_modules/react-native-keyboard-input/lib/ios/RCTCustomInputController.xcodeproj"; sourceTree = ""; }; + 7A807B4C215EC5E400A4348D /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/lib/ios/ReactNativeNavigation.xcodeproj"; sourceTree = ""; }; 7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNNotifications.xcodeproj; path = "../node_modules/react-native-notifications/RNNotifications/RNNotifications.xcodeproj"; sourceTree = ""; }; 7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTToast.xcodeproj; path = "../node_modules/@remobile/react-native-toast/ios/RCTToast.xcodeproj"; sourceTree = ""; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; @@ -583,8 +590,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7A807B55215EC60500A4348D /* libReactNativeNavigation.a in Frameworks */, 7A8DEB5A20ED0BEC00C5DCE4 /* libRNNotifications.a in Frameworks */, - 7A3562E620E1569000A4CF66 /* libReactNativeNavigation.a in Frameworks */, 7A2D202320726F1400D0AA04 /* libSMXCrashlytics.a in Frameworks */, 7AFB806E205AE65700D004E7 /* libRCTToast.a in Frameworks */, B8971BB2202A093B0000D245 /* libKeyboardTrackingView.a in Frameworks */, @@ -806,14 +813,6 @@ name = Products; sourceTree = ""; }; - 7A3562E120E1567900A4CF66 /* Products */ = { - isa = PBXGroup; - children = ( - 7A3562E520E1567900A4CF66 /* libReactNativeNavigation.a */, - ); - name = Products; - sourceTree = ""; - }; 7A430E1720238C01008F55BC /* Products */ = { isa = PBXGroup; children = ( @@ -848,6 +847,15 @@ name = Products; sourceTree = ""; }; + 7A807B4D215EC5E400A4348D /* Products */ = { + isa = PBXGroup; + children = ( + 7A807B52215EC5E500A4348D /* libReactNativeNavigation.a */, + 7A807B54215EC5E500A4348D /* ReactNativeNavigationTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; 7A8C912120F39A8000C8F5EE /* Products */ = { isa = PBXGroup; children = ( @@ -883,8 +891,8 @@ 832341AE1AAA6A7D00B99B32 /* Libraries */ = { isa = PBXGroup; children = ( + 7A807B4C215EC5E400A4348D /* ReactNativeNavigation.xcodeproj */, 7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */, - 7A3562E020E1567900A4CF66 /* ReactNativeNavigation.xcodeproj */, 7A2D1FE620726EF600D0AA04 /* SMXCrashlytics.xcodeproj */, 7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */, B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */, @@ -1240,8 +1248,8 @@ ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; }, { - ProductGroup = 7A3562E120E1567900A4CF66 /* Products */; - ProjectRef = 7A3562E020E1567900A4CF66 /* ReactNativeNavigation.xcodeproj */; + ProductGroup = 7A807B4D215EC5E400A4348D /* Products */; + ProjectRef = 7A807B4C215EC5E400A4348D /* ReactNativeNavigation.xcodeproj */; }, { ProductGroup = 607D60ED1F325B7D00F639C4 /* Products */; @@ -1508,13 +1516,6 @@ remoteRef = 7A2D202020726EF600D0AA04 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 7A3562E520E1567900A4CF66 /* libReactNativeNavigation.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libReactNativeNavigation.a; - remoteRef = 7A3562E420E1567900A4CF66 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 7A430E1E20238C02008F55BC /* libRCTCustomInputController.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1557,6 +1558,20 @@ remoteRef = 7A7F5C9A1FCC982500024129 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A807B52215EC5E500A4348D /* libReactNativeNavigation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReactNativeNavigation.a; + remoteRef = 7A807B51215EC5E500A4348D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7A807B54215EC5E500A4348D /* ReactNativeNavigationTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = ReactNativeNavigationTests.xctest; + remoteRef = 7A807B53215EC5E500A4348D /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 7A8C915320F39A8000C8F5EE /* libRNFetchBlob.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/ios/RocketChatRN/AppDelegate.m b/ios/RocketChatRN/AppDelegate.m index 1cf81bd0..2f5a81f0 100644 --- a/ios/RocketChatRN/AppDelegate.m +++ b/ios/RocketChatRN/AppDelegate.m @@ -15,7 +15,8 @@ #import #import #import -#import "RCCManager.h" +//#import "RCCManager.h" + #import #import "RNNotifications.h" @@ -30,9 +31,11 @@ jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; #endif - self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; - self.window.backgroundColor = [UIColor whiteColor]; - [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions]; + [ReactNativeNavigation bootstrap:jsCodeLocation launchOptions:launchOptions]; + +// self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; +// self.window.backgroundColor = [UIColor whiteColor]; +// [[RCCManager sharedInstance] initBridgeWithBundleURL:jsCodeLocation launchOptions:launchOptions]; // RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation // moduleName:@"RocketChatRN" diff --git a/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/Contents.json b/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/Contents.json new file mode 100644 index 00000000..3bad3d0c --- /dev/null +++ b/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "back.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "back@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "back@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back.png b/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back.png new file mode 100644 index 0000000000000000000000000000000000000000..5357f91a57ddb89bc8fe7b667a583a26c505ff2e GIT binary patch literal 409 zcmV;K0cQS*P)4#8y-or_5XWcMv$&Jm4hjuV;0yQ)Y6xhpvC;rRYichMFk0A} zXb?Z1!P1Y|85=(mvD3!JyF$Y_bFk-faBy4PZgzgZ{m<12^55mDT5N(Ab1j;_^()F!h2c*Y8wYIN;u7(sx00000NkvXXu0mjf DG{~$$ literal 0 HcmV?d00001 diff --git a/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back@2x.png b/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..4d21bc60d0685c506ceb810c608216dd0315872f GIT binary patch literal 718 zcmV;<0x|uGP)_OP(ceUk-)&t zJ4%`b+m^Nn1Uv+R7vLfg5)xt&5w9U!B*#c4KFo|YRxT6AV^6*CILGJwpZ_y6ju9*$ zCp_BwC+OS+z_wp=9->YCBOdJ!gU)LpI4N`yCgzzfz@Rvn@4_TBtpIf54Dgb_yrWsT zMWe)9b>~IgrUE@0Cte7UMLahkt9UMeiMTPqRNM%VM?5niuebn=-Dp~IhE=uBg-F_w zy3t08*PQ2RqP_P<0&Iv2kQQJ|Tnn%%J_N8W-UrwY@dU74;sT)G2M9=!egTLglOtJ0 zLoN^_B&_;%=b4G%U~b~ztB2p6a%qk~_a~V|#0cq0#SJeG7Mc7`9H^pW_h?_R#lH*y zPRW>X0=VF>I8RlM$v6ZM#Wo>Hi+Z2x%YFP&o+`~D!bcew?E-qt+g0t&%W|y+h(ZCG z=5x{qA=*56JG`7lSmlfah=k7`wj~7MgDl`q0BFMmk_t#5J4@&JWc5L2+;Blc$DFj2 ztgETqr9IH?ydr|r@;+Z({OfJ8wc>2*@>MJ|Kokr?_=abQSJNP4Tcn;ujQ%qIgbCTM z_i>ysB3eJ>hF5y?dasQfs6y%FU#CF;XX2Q2vc#RNs~j~NIZ#Ey2@?~K_%4n~Cu@~k z;iAe>V=f1(c*djMQ4ltWXf}@V*CXIH{(^j@a&kFP#os*q#mde+|7(96$N1|JXtm-7 zODd<310{0!4(;0Nh7G<6&&Dy~gl_ot@~Rbp5S5T9dXCXVoSF*;z)fr1@%Y5FgO*ZI7(TQz6nKeHq)$07*qoM6N<$f{$lE AeEJ5I(9bFuw0nfK+*WMYRw8(aS{ zU&Yz%N#N{9cUS!4i%~WgX_sgH8GV-l#fM-*4*-YXe`W{KGRWEiTEG~OF!ypXe^k|$ zDPkau^LiMp%$HN0@H^BB)yFWMGi7i(7k?01h1@WVDF+BcGZadNyfFD({JynZ0tflQ z1YRM(m_R|zUyD>J<|9~M- z^{0bZoYxa|;%5EV=yjQ`3VJ^ldlpqAdvWk@Vgk;22Y`7qk)GKn#)CSq`Ifm&9Vq-z zA=7m?C=88Tt&o*uG820~UnvG(tr#8WD`m19y{Q@cA1AO@1ENfX>WJd>IiMJ46G5VC zrcC_k1LyP|b7g$S;`A@qzlP6PWe)uC^jO!Oe^EJSt{_U0NbB1HAN)AL3Nd9+D90%i z6BjbE7l7YBlz$Q9SfMDQCi+t|`^9*x=bja^5+p|SZBRCzI8_P%Vci1mB!8{~!p#tdAPL;0L`(=Wv8 z@42Y0j^Z(&jlEPNk==~JXEBbuXT$`!);C-{Xp8%}XbME*Po0mwLe!pms6?uG$cLla zwW%qqz8g>aNAxYGF~6o(iE)}f#2zbTwHvm$$3@E^8h?GNw7FtdXiHda%SR}HmD8@; z;|{m5i#xP(O_l11D7zgi#o ztavCOO4d7E8o