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 707f021c..80faa15a 100644 Binary files a/android/app/src/main/res/drawable-hdpi/new_channel.png and b/android/app/src/main/res/drawable-hdpi/new_channel.png differ diff --git a/android/app/src/main/res/drawable-hdpi/search.png b/android/app/src/main/res/drawable-hdpi/search.png index 65995582..d3e07e2f 100644 Binary files a/android/app/src/main/res/drawable-hdpi/search.png and b/android/app/src/main/res/drawable-hdpi/search.png differ diff --git a/android/app/src/main/res/drawable-hdpi/settings.png b/android/app/src/main/res/drawable-hdpi/settings.png index d537c71b..df79dd7b 100644 Binary files a/android/app/src/main/res/drawable-hdpi/settings.png and b/android/app/src/main/res/drawable-hdpi/settings.png differ 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 62d1ce59..dc0571d0 100644 Binary files a/android/app/src/main/res/drawable-mdpi/new_channel.png and b/android/app/src/main/res/drawable-mdpi/new_channel.png differ diff --git a/android/app/src/main/res/drawable-mdpi/search.png b/android/app/src/main/res/drawable-mdpi/search.png index 8482814b..c34a1158 100644 Binary files a/android/app/src/main/res/drawable-mdpi/search.png and b/android/app/src/main/res/drawable-mdpi/search.png differ diff --git a/android/app/src/main/res/drawable-mdpi/settings.png b/android/app/src/main/res/drawable-mdpi/settings.png index 912ab962..eca8e274 100644 Binary files a/android/app/src/main/res/drawable-mdpi/settings.png and b/android/app/src/main/res/drawable-mdpi/settings.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/new_channel.png b/android/app/src/main/res/drawable-xhdpi/new_channel.png index 14da6769..580e9579 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/new_channel.png and b/android/app/src/main/res/drawable-xhdpi/new_channel.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/search.png b/android/app/src/main/res/drawable-xhdpi/search.png index a2966e0c..d8bd3ec7 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/search.png and b/android/app/src/main/res/drawable-xhdpi/search.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/settings.png b/android/app/src/main/res/drawable-xhdpi/settings.png index 1dd8592d..c4a3f1b9 100644 Binary files a/android/app/src/main/res/drawable-xhdpi/settings.png and b/android/app/src/main/res/drawable-xhdpi/settings.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/new_channel.png b/android/app/src/main/res/drawable-xxhdpi/new_channel.png index 82d882ea..6f84de25 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/new_channel.png and b/android/app/src/main/res/drawable-xxhdpi/new_channel.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/search.png b/android/app/src/main/res/drawable-xxhdpi/search.png index 1d380f01..07d0430d 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/search.png and b/android/app/src/main/res/drawable-xxhdpi/search.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/settings.png b/android/app/src/main/res/drawable-xxhdpi/settings.png index 501b6a4b..0d3c6ad4 100644 Binary files a/android/app/src/main/res/drawable-xxhdpi/settings.png and b/android/app/src/main/res/drawable-xxhdpi/settings.png differ 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 7086e713..1091224d 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/new_channel.png and b/android/app/src/main/res/drawable-xxxhdpi/new_channel.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/search.png b/android/app/src/main/res/drawable-xxxhdpi/search.png index 16e8b3b5..c043dfa2 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/search.png and b/android/app/src/main/res/drawable-xxxhdpi/search.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/settings.png b/android/app/src/main/res/drawable-xxxhdpi/settings.png index cca523a1..61ffa633 100644 Binary files a/android/app/src/main/res/drawable-xxxhdpi/settings.png and b/android/app/src/main/res/drawable-xxxhdpi/settings.png differ 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 00000000..5357f91a Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back.png differ 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 00000000..4d21bc60 Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back@2x.png differ diff --git a/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back@3x.png b/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back@3x.png new file mode 100644 index 00000000..f087b5ac Binary files /dev/null and b/ios/RocketChatRN/Images.xcassets/Icons/back.imageset/back@3x.png differ diff --git a/package-lock.json b/package-lock.json index b2b6b214..625fc665 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17191,9 +17191,8 @@ } }, "react-native-fabric": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/react-native-fabric/-/react-native-fabric-0.5.2.tgz", - "integrity": "sha512-uarBq3XtADCJIGwzeHMzG7LCTfElGuqUj4UqMxsWbPsXaYForfNw+EMkEQ+6eBq/a2k4PKfuVQXQAC7rfgo/Vg==" + "version": "github:corymsmith/react-native-fabric#523a4edab3b2bf55ea9eeea2cf0dde82c5c29dd4", + "from": "github:corymsmith/react-native-fabric#523a4edab3b2bf55ea9eeea2cf0dde82c5c29dd4" }, "react-native-fast-image": { "version": "5.0.11", @@ -17317,10 +17316,34 @@ } }, "react-native-navigation": { - "version": "git+https://github.com/RocketChat/react-native-navigation.git#f7e36cda91404884851b1cb4e95fc34cdf2ef1dc", - "from": "git+https://github.com/RocketChat/react-native-navigation.git", + "version": "2.0.2588", + "resolved": "https://registry.npmjs.org/react-native-navigation/-/react-native-navigation-2.0.2588.tgz", + "integrity": "sha512-ljVz3RWNv8Cj2vRcedkWAUuMbDac9ZduBRgYPCQ3GXFVhmpthLSaZLWuMfqqkpznHyPGy0kwEG+XX6Z93cqemw==", "requires": { - "lodash": "4.x.x" + "hoist-non-react-statics": "3.x.x", + "lodash": "4.x.x", + "prop-types": "15.x.x", + "react-lifecycles-compat": "2.0.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.0.1.tgz", + "integrity": "sha512-1kXwPsOi0OGQIZNVMPvgWJ9tSnGMiMfJdihqEzrPEXlHOBh9AAHXX/QYmAJTXztnz/K+PQ8ryCb4eGaN6HlGbQ==", + "requires": { + "react-is": "^16.3.2" + } + }, + "react-is": { + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.5.2.tgz", + "integrity": "sha512-hSl7E6l25GTjNEZATqZIuWOgSnpXb3kD0DVCujmg46K5zLxsbiKaaT6VO9slkSBDPZfYs30lwfJwbOFOnoEnKQ==" + }, + "react-lifecycles-compat": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-2.0.0.tgz", + "integrity": "sha512-txfpPCQYiazVdcbMRhatqWKcAxJweUu2wDXvts5/7Wyp6+Y9cHojqXHsLPEckzutfHlxZhG8Oiundbmp8Fd6eQ==" + } } }, "react-native-notifications": { @@ -17373,6 +17396,21 @@ "resolved": "https://registry.npmjs.org/react-native-safari-view/-/react-native-safari-view-2.1.0.tgz", "integrity": "sha1-HgzRLGK855vBdZx+KBZGsIthyVk=" }, + "react-native-safe-area-view": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/react-native-safe-area-view/-/react-native-safe-area-view-0.11.0.tgz", + "integrity": "sha512-N3nElaahu1Me2ltnfc9acpgt1znm6pi8DSadKy79kvdzKwvVIzw0IXueA/Hjr51eCW1BsfNw7D1SgBT9U6qEkA==", + "requires": { + "hoist-non-react-statics": "^2.3.1" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + } + } + }, "react-native-scrollable-tab-view": { "version": "git+https://github.com/skv-headless/react-native-scrollable-tab-view.git#2419c25a03f0fb346af8ce2c39fca869f259e716", "from": "git+https://github.com/skv-headless/react-native-scrollable-tab-view.git", diff --git a/package.json b/package.json index 6feac282..ff6f0d92 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "react-native-audio": "^4.2.2", "react-native-device-info": "^0.24.0", "react-native-dialog": "^5.4.0", - "react-native-fabric": "^0.5.2", + "react-native-fabric": "github:corymsmith/react-native-fabric#523a4edab3b2bf55ea9eeea2cf0dde82c5c29dd4", "react-native-fast-image": "^5.0.11", "react-native-gesture-handler": "^1.0.8", "react-native-i18n": "^2.0.15", @@ -52,12 +52,13 @@ "react-native-markdown-renderer": "^3.2.8", "react-native-meteor": "^1.4.0", "react-native-modal": "^6.5.0", - "react-native-navigation": "git+https://github.com/RocketChat/react-native-navigation.git", + "react-native-navigation": "^2.0.2588", "react-native-notifications": "^1.1.20", "react-native-optimized-flatlist": "^1.0.4", "react-native-picker-select": "^4.4.0", "react-native-responsive-ui": "^1.1.1", "react-native-safari-view": "^2.1.0", + "react-native-safe-area-view": "^0.11.0", "react-native-scrollable-tab-view": "git+https://github.com/skv-headless/react-native-scrollable-tab-view.git", "react-native-slider": "^0.11.0", "react-native-vector-icons": "^5.0.0",