Update navigation library (#501)

* v2

* Working on Android 0.57.3

* Drawer working

* Removing v1 navigator

* - Splash screen
- Icons changed

* Deeplink

* Remove EventEmitter from CreateChannelView

* Android search

* Android notifications

* OAuth

* Fix search props

* Lint and tests fixed

* Fix android build

* Improvements on iPhone X* usage

* Fix detox

* Fix android build

* Room.f added to RoomView.shouldComponentUpdate

* Animations on RoomsListView and RoomView

* Fix topbar buttons on Android
This commit is contained in:
Diego Mello 2018-10-23 18:39:48 -03:00 committed by GitHub
parent 461d36b29a
commit 402403f964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 1457 additions and 977 deletions

View File

@ -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": {

View File

@ -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', () => {

View File

@ -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;
}
}

View File

@ -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 NavigationActivity {
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 void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
return splash;
@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;
// }
// }

View File

@ -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,17 +37,28 @@ 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";
// }
protected ReactGateway createReactGateway() {
ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) {
@Override
public String getJSMainModuleName() {
protected String getJSMainModuleName() {
return "index.android";
}
};
return new ReactGateway(this, isDebug(), host);
}
protected List<ReactPackage> getPackages() {
// Add additional packages you require here
@ -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()
);

View File

@ -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<AppVisibilityListener> 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();
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 867 B

After

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 B

After

Width:  |  Height:  |  Size: 664 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 883 B

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 441 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 B

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 568 B

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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 {
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)
}
}
}
}
}
}

View File

@ -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'

39
app/Drawer.js Normal file
View File

@ -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();

View File

@ -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]));

View File

@ -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();

View File

@ -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',

View File

@ -1,8 +0,0 @@
import * as types from './actionsTypes';
export default function setNavigation(navigator = {}) {
return {
type: types.NAVIGATION.SET,
navigator
};
}

View File

@ -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
};
}

View File

@ -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, '')

View File

@ -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 {
});
}
closeDrawer = () => {
const { navigator } = this.props;
navigator.toggleDrawer({
side: 'left',
animated: true,
to: 'close'
setStack = (stack) => {
const { currentStack } = this.state;
if (currentStack !== stack) {
Navigation.setStackRoot('AppRoot', {
component: {
id: stack,
name: stack
}
});
this.setState({ currentStack: stack });
}
}
closeDrawer = () => {
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 => <View key={key} style={styles.separator} />;
renderItem = ({
text, left, onPress, testID
text, left, onPress, testID, current
}) => (
<Touch
key={text}
@ -171,7 +203,7 @@ export default class Sidebar extends Component {
activeOpacity={0.3}
testID={testID}
>
<View style={styles.item}>
<View style={[styles.item, current && styles.itemSelected]}>
<View style={styles.itemLeft}>
{left}
</View>
@ -188,7 +220,7 @@ export default class Sidebar extends Component {
this.renderItem({
text: item.name,
left: <View style={[styles.status, { backgroundColor: STATUS_COLORS[item.id] }]} />,
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: <Icon name='chat-bubble' size={20} />,
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: <Icon name='person' size={20} />,
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: <Icon name='settings' size={20} />,
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({

View File

@ -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: {
Navigation.setRoot({
root: {
sideMenu: {
left: {
screen: 'Sidebar'
component: {
id: 'Sidebar',
name: 'Sidebar'
}
},
animationType: 'fade'
center: {
stack: {
id: 'AppRoot',
children: [{
component: {
id: 'RoomsListView',
name: 'RoomsListView'
}
}]
}
}
}
}
});
};
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 }))

View File

@ -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) => {

View File

@ -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 {

View File

@ -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 {

View File

@ -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;

View File

@ -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 {

View File

@ -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 {

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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,12 +19,16 @@ const navigate = function* go({ params, sameServer = true }) {
if (params.rid) {
const canOpenRoom = yield RocketChat.canOpenRoom(params);
if (canOpenRoom) {
return NavigationActions.push({
screen: 'RoomView',
backButtonTitle: '',
// 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));
if (servers.length && token) {
yield put(selectServerRequest(host));
yield take(types.METEOR.REQUEST);
yield navigate({ params, sameServer: false });
}
} else {
yield put(selectServerRequest(host));
yield put(appStart('outside'));
yield delay(1000);
EventEmitter.emit('NewServer', { server: host });
}
}
};

View File

@ -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'));
}

View File

@ -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
}
}
}
}
});
};

View File

@ -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') }));
}

View File

@ -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);
});

42
app/utils/events.js Normal file
View File

@ -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;

View File

@ -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,46 +115,70 @@ 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') });
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') {
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'create') {
this.submit();
}
}
}
submit = () => {
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}
>
<SafeAreaView testID='create-channel-view' style={styles.container}>
<SafeAreaView testID='create-channel-view' style={styles.container} forceInset={{ bottom: 'never' }}>
<ScrollView {...scrollPersistTaps}>
<View style={sharedStyles.separatorVertical}>
<TextInput
@ -292,7 +324,7 @@ export default class CreateChannelView extends LoggedView {
<Text style={styles.invitedCount}>{userCount === 1 ? I18n.t('1_user') : I18n.t('N_users', { n: userCount })}</Text>
</View>
{this.renderInvitedList()}
<Loading visible={createChannel.isFetching} />
<Loading visible={isFetching} />
</ScrollView>
</SafeAreaView>
</KeyboardView>

View File

@ -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}
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView style={styles.container} testID='forgot-password-view'>
<SafeAreaView style={styles.container} testID='forgot-password-view' forceInset={{ bottom: 'never' }}>
<View>
<TextInput
inputStyle={invalidEmail ? { borderColor: 'red' } : {}}

View File

@ -1,13 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet, SafeAreaView
Text, View, ScrollView, TouchableOpacity, LayoutAnimation, Image, StyleSheet
} from 'react-native';
import { connect, Provider } from 'react-redux';
import { Navigation } from 'react-native-navigation';
import Icon from 'react-native-vector-icons/FontAwesome';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { Base64 } from 'js-base64';
import SafeAreaView from 'react-native-safe-area-view';
import { open as openAction, close as closeAction } from '../actions/login';
import LoggedView from './View';
@ -71,7 +72,7 @@ let RegisterView = null;
/** @extends React.Component */
export default class LoginSignupView extends LoggedView {
static propTypes = {
navigator: PropTypes.object,
componentId: PropTypes.string,
open: PropTypes.func.isRequired,
close: PropTypes.func.isRequired,
isFetching: PropTypes.bool,
@ -189,15 +190,26 @@ export default class LoginSignupView extends LoggedView {
openOAuth = (oAuthUrl) => {
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',
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}
>
<SafeAreaView style={sharedStyles.container} testID='welcome-view'>
<SafeAreaView style={sharedStyles.container} testID='welcome-view' forceInset={{ bottom: 'never' }}>
<View style={styles.container}>
<Text style={[sharedStyles.loginText, styles.header, { color: '#81848A' }]}>{I18n.t('Welcome_title_pt_1')}</Text>
<Text style={[sharedStyles.loginText, styles.header]}>{I18n.t('Welcome_title_pt_2')}</Text>

View File

@ -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'
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView style={styles.container} testID='login-view'>
<SafeAreaView style={styles.container} testID='login-view' forceInset={{ bottom: 'never' }}>
<Text style={[styles.loginText, styles.loginTitle]}>Login</Text>
<TextInput
label={I18n.t('Username')}

View File

@ -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 { openMentionedMessages as openMentionedMessagesAction, closeMentionedMessages as closeMentionedMessagesAction } from '../../actions/mentionedMessages';
import LoggedView from '../View';
@ -26,6 +25,16 @@ import I18n from '../../i18n';
}))
/** @extends React.Component */
export default class MentionedMessagesView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Mentions')
}
}
};
}
static propTypes = {
rid: PropTypes.string,
messages: PropTypes.array,
@ -106,7 +115,7 @@ export default class MentionedMessagesView extends LoggedView {
}
return (
<SafeAreaView style={styles.list} testID='mentioned-messages-view'>
<SafeAreaView style={styles.list} testID='mentioned-messages-view' forceInset={{ bottom: 'never' }}>
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -1,10 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
View, StyleSheet, SafeAreaView, FlatList, Text, Platform, Image
View, StyleSheet, FlatList, Text, Platform, Image, Dimensions
} from 'react-native';
import { connect, Provider } from 'react-redux';
import { Navigation } from 'react-native-navigation';
import SafeAreaView from 'react-native-safe-area-view';
import database from '../lib/realm';
import RocketChat from '../lib/rocketchat';
@ -52,15 +53,20 @@ let SelectedUsersView = null;
}))
/** @extends React.Component */
export default class NewMessageView extends LoggedView {
static navigatorButtons = {
static options() {
return {
topBar: {
leftButtons: [{
id: 'cancel',
title: I18n.t('Cancel')
icon: Platform.OS === 'android' ? { uri: 'back', scale: Dimensions.get('window').scale } : undefined,
text: Platform.OS === 'ios' ? I18n.t('Cancel') : undefined
}]
}
};
}
static propTypes = {
navigator: PropTypes.object,
componentId: PropTypes.string,
baseUrl: PropTypes.string,
onPressItem: PropTypes.func.isRequired
};
@ -72,7 +78,7 @@ export default class NewMessageView extends LoggedView {
search: []
};
this.data.addListener(this.updateState);
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
Navigation.events().bindComponent(this);
}
componentWillUnmount() {
@ -80,27 +86,29 @@ export default class NewMessageView extends LoggedView {
this.data.removeAllListeners();
}
async onNavigatorEvent(event) {
const { navigator } = this.props;
if (event.type === 'NavBarButtonPress') {
if (event.id === 'cancel') {
navigator.dismissModal();
}
}
}
onSearchChangeText(text) {
this.search(text);
}
onPressItem = (item) => {
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: '',
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 = () => (
<SafeAreaView style={styles.safeAreaView} testID='new-message-view'>
<SafeAreaView style={styles.safeAreaView} testID='new-message-view' forceInset={{ bottom: 'never' }}>
{this.renderList()}
</SafeAreaView>
);

View File

@ -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 (
<TouchableOpacity
style={[styles.backButton, { top }]}
onPress={() => navigator.pop()}
onPress={() => Navigation.pop(componentId)}
>
<Icon
name='ios-arrow-back'
@ -159,7 +171,7 @@ export default class NewServerView extends LoggedView {
key='login-view'
>
<ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
<SafeAreaView style={sharedStyles.container} testID='new-server-view'>
<SafeAreaView style={sharedStyles.container} testID='new-server-view' forceInset={{ bottom: 'never' }}>
<Image style={styles.image} source={{ uri: 'new_server' }} />
<Text style={styles.title}>{I18n.t('Sign_in_your_server')}</Text>
<TextInput

View File

@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { WebView, Platform } from 'react-native';
import { WebView, Platform, Dimensions } from 'react-native';
import { connect } from 'react-redux';
import { Navigation } from 'react-native-navigation';
import RocketChat from '../lib/rocketchat';
import I18n from '../i18n';
@ -13,15 +14,20 @@ const userAgent = Platform.OS === 'ios' ? 'UserAgent' : userAgentAndroid;
server: state.server.server
}))
export default class OAuthView extends React.PureComponent {
static navigatorButtons = {
static options() {
return {
topBar: {
leftButtons: [{
id: 'cancel',
title: I18n.t('Cancel')
icon: Platform.OS === 'android' ? { uri: 'back', scale: Dimensions.get('window').scale } : undefined,
text: Platform.OS === 'ios' ? I18n.t('Cancel') : undefined
}]
}
};
}
static propTypes = {
navigator: PropTypes.object,
componentId: PropTypes.string,
oAuthUrl: PropTypes.string,
server: PropTypes.string
}
@ -29,16 +35,18 @@ export default class OAuthView extends React.PureComponent {
constructor(props) {
super(props);
this.redirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
Navigation.events().bindComponent(this);
}
onNavigatorEvent(event) {
const { navigator } = this.props;
if (event.type === 'NavBarButtonPress') {
if (event.id === 'close') {
navigator.dismissModal();
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'cancel') {
this.dismiss();
}
}
dismiss = () => {
const { componentId } = this.props;
Navigation.dismissModal(componentId);
}
login = async(params) => {
@ -50,7 +58,7 @@ export default class OAuthView extends React.PureComponent {
}
render() {
const { oAuthUrl, navigator } = this.props;
const { oAuthUrl } = this.props;
return (
<WebView
source={{ uri: oAuthUrl }}
@ -61,7 +69,7 @@ export default class OAuthView extends React.PureComponent {
const parts = url.split('#');
const credentials = JSON.parse(parts[1]);
this.login({ oauth: { ...credentials } });
navigator.dismissModal();
this.dismiss();
}
}}
/>

View File

@ -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 (
<SafeAreaView style={styles.container} testID='onboarding-view'>
<SafeAreaView style={styles.container} testID='onboarding-view' forceInset={{ bottom: 'never' }}>
<Image style={styles.onboarding} source={{ uri: 'onboarding' }} />
<Text style={styles.title}>{I18n.t('Welcome_to_RocketChat')}</Text>
<Text style={styles.subtitle}>{I18n.t('Open_Source_Communication')}</Text>

View File

@ -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 (
<SafeAreaView style={styles.list} testID='pinned-messages-view'>
<SafeAreaView style={styles.list} testID='pinned-messages-view' forceInset={{ bottom: 'never' }}>
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { WebView, SafeAreaView } from 'react-native';
import { WebView } from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import styles from './Styles';
import LoggedView from './View';

View File

@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
View, ScrollView, SafeAreaView, Keyboard, Dimensions
View, ScrollView, Keyboard, Dimensions
} from 'react-native';
import { connect } from 'react-redux';
import Dialog from 'react-native-dialog';
@ -9,6 +9,8 @@ import SHA256 from 'js-sha256';
import Icon from 'react-native-vector-icons/MaterialIcons';
import ImagePicker from 'react-native-image-crop-picker';
import RNPickerSelect from 'react-native-picker-select';
import { Navigation } from 'react-native-navigation';
import SafeAreaView from 'react-native-safe-area-view';
import LoggedView from '../View';
import KeyboardView from '../../presentation/KeyboardView';
@ -24,6 +26,7 @@ import I18n from '../../i18n';
import Button from '../../containers/Button';
import Avatar from '../../containers/Avatar';
import Touch from '../../utils/touch';
import Drawer from '../../Drawer';
@connect(state => ({
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}
>
<SafeAreaView style={sharedStyles.container} testID='profile-view'>
<SafeAreaView style={sharedStyles.container} testID='profile-view' forceInset={{ bottom: 'never' }}>
<View style={styles.avatarContainer} testID='profile-view-avatar'>
<Avatar
text={username}

View File

@ -1,10 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Keyboard, Text, View, ScrollView, SafeAreaView
Keyboard, Text, View, ScrollView
} from 'react-native';
import { connect, Provider } from 'react-redux';
import { Navigation } from 'react-native-navigation';
import SafeAreaView from 'react-native-safe-area-view';
import { registerSubmit as registerSubmitAction, setUsernameSubmit as setUsernameSubmitAction } from '../actions/login';
import TextInput from '../containers/TextInput';
@ -35,7 +36,7 @@ let PrivacyPolicyView = null;
/** @extends React.Component */
export default class RegisterView extends LoggedView {
static propTypes = {
navigator: PropTypes.object,
componentId: PropTypes.string,
server: PropTypes.string,
registerSubmit: PropTypes.func.isRequired,
setUsernameSubmit: PropTypes.func,
@ -104,28 +105,42 @@ export default class RegisterView extends LoggedView {
termsService = () => {
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 (
<KeyboardView contentContainerStyle={styles.container}>
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}>
<SafeAreaView style={styles.container} testID='register-view'>
<SafeAreaView style={styles.container} testID='register-view' forceInset={{ bottom: 'never' }}>
<Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_Up')}</Text>
{this._renderRegister()}
{this._renderUsername()}

View File

@ -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 (
<SafeAreaView style={styles.container} testID='room-actions-view'>
<SafeAreaView style={styles.container} testID='room-actions-view' forceInset={{ bottom: 'never' }}>
<SectionList
style={styles.container}
stickySectionHeadersEnabled={false}

View File

@ -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 { openRoomFiles as openRoomFilesAction, closeRoomFiles as closeRoomFilesAction } from '../../actions/roomFiles';
import LoggedView from '../View';
@ -26,6 +25,16 @@ import I18n from '../../i18n';
}))
/** @extends React.Component */
export default class RoomFilesView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Files')
}
}
};
}
static propTypes = {
rid: PropTypes.string,
messages: PropTypes.array,
@ -106,7 +115,7 @@ export default class RoomFilesView extends LoggedView {
const { loading, loadingMore } = this.state;
return (
<SafeAreaView style={styles.list} testID='room-files-view'>
<SafeAreaView style={styles.list} testID='room-files-view' forceInset={{ bottom: 'never' }}>
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text, View, ScrollView, TouchableOpacity, SafeAreaView, Keyboard, Alert
Text, View, ScrollView, TouchableOpacity, Keyboard, Alert
} from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { eraseRoom as eraseRoomAction } from '../../actions/room';
import LoggedView from '../View';
@ -41,6 +42,16 @@ const PERMISSIONS_ARRAY = [
}))
/** @extends React.Component */
export default class RoomInfoEditView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Room_Info_Edit')
}
}
};
}
static propTypes = {
rid: PropTypes.string,
eraseRoom: PropTypes.func
@ -277,7 +288,7 @@ export default class RoomInfoEditView extends LoggedView {
testID='room-info-edit-view-list'
{...scrollPersistTaps}
>
<SafeAreaView style={sharedStyles.container} testID='room-info-edit-view'>
<SafeAreaView style={sharedStyles.container} testID='room-info-edit-view' forceInset={{ bottom: 'never' }}>
<RCTextInput
inputRef={(e) => { this.name = e; }}
label={I18n.t('Name')}

View File

@ -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') {
navigationButtonPressed = ({ buttonId }) => {
const { rid, componentId } = this.props;
if (buttonId === 'edit') {
if (RoomInfoEditView == null) {
RoomInfoEditView = require('../RoomInfoEditView').default;
Navigation.registerComponent('RoomInfoEditView', () => RoomInfoEditView, store, Provider);
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 (
<ScrollView style={styles.scroll}>
<SafeAreaView style={styles.container} testID='room-info-view'>
<SafeAreaView style={styles.container} testID='room-info-view' forceInset={{ bottom: 'never' }}>
<View style={styles.avatarContainer}>
{this.renderAvatar(room, roomUser)}
<View style={styles.roomTitleContainer}>{ getRoomTitle(room) }</View>

View File

@ -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 = {
static options() {
return {
topBar: {
title: {
text: I18n.t('Members')
},
rightButtons: [{
title: 'All',
id: 'toggleOnline',
testID: 'room-members-view-toggle-status'
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,30 +76,30 @@ 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') {
if (buttonId === 'toggleOnline') {
try {
Navigation.mergeOptions(componentId, {
topBar: {
rightButtons: [{
id: 'toggleOnline',
text: allUsers ? I18n.t('Online') : I18n.t('All'),
testID: 'room-members-view-toggle-status'
}]
}
});
const allUsersFilter = !allUsers;
const membersResult = await RocketChat.getRoomMembers(rid, allUsersFilter);
const members = membersResult.records;
this.setState({ allUsers: allUsersFilter, members });
navigator.setButtons({
rightButtons: [{
title: allUsers ? I18n.t('Online') : I18n.t('All'),
id: 'toggleOnline',
testID: 'room-members-view-toggle-status'
}]
});
} catch (e) {
log('RoomMembers.onNavigationButtonPressed', e);
}
}
}
}
onSearchChangeText = (text) => {
const { members } = this.state;
@ -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 }
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
}
}
}
}
});
}, 1000);
}
handleMute = async() => {
@ -200,7 +216,7 @@ export default class RoomMembersView extends LoggedView {
render() {
const { filtering, members, membersFiltered } = this.state;
return (
<SafeAreaView style={styles.list} testID='room-members-view'>
<SafeAreaView style={styles.list} testID='room-members-view' forceInset={{ bottom: 'never' }}>
<FlatList
data={filtering ? membersFiltered : members}
renderItem={this.renderItem}

View File

@ -1,11 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
Text, View, LayoutAnimation, ActivityIndicator, SafeAreaView
Text, View, LayoutAnimation, ActivityIndicator
} from 'react-native';
import { connect, Provider } from 'react-redux';
import { RectButton } from 'react-native-gesture-handler';
import { Navigation } from 'react-native-navigation';
import SafeAreaView from 'react-native-safe-area-view';
import { openRoom as openRoomAction, closeRoom as closeRoomAction, setLastOpen as setLastOpenAction } from '../../actions/room';
import { toggleReactionPicker as toggleReactionPickerAction, actionsShow as actionsShowAction } from '../../actions/messages';
@ -47,8 +48,24 @@ let RoomActionsView = null;
}))
/** @extends React.Component */
export default class RoomView extends LoggedView {
static options() {
return {
topBar: {
rightButtons: [{
id: 'more',
testID: 'room-view-header-actions',
icon: iconsMap.more
}, {
id: 'star',
testID: 'room-view-header-star',
icon: iconsMap.starOutline
}]
}
};
}
static propTypes = {
navigator: PropTypes.object,
componentId: PropTypes.string,
openRoom: PropTypes.func.isRequired,
setLastOpen: PropTypes.func.isRequired,
user: PropTypes.shape({
@ -76,35 +93,13 @@ export default class RoomView extends LoggedView {
end: false
};
this.onReactionPress = this.onReactionPress.bind(this);
props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
}
componentWillMount() {
const { navigator } = this.props;
navigator.setButtons({
rightButtons: [{
id: 'more',
testID: 'room-view-header-actions',
icon: iconsMap.more
}, {
id: 'star',
testID: 'room-view-header-star',
icon: iconsMap.starOutline
}]
});
Navigation.events().bindComponent(this);
}
componentDidMount() {
const { navigator } = this.props;
this.updateRoom();
this.rooms.addListener(this.updateRoom);
navigator.setDrawerEnabled({
side: 'left',
enabled: false
});
this.setState({ loaded: true });
this.internalSetState({ loaded: true });
}
shouldComponentUpdate(nextProps, nextState) {
@ -115,6 +110,8 @@ export default class RoomView extends LoggedView {
if (room.ro !== nextState.room.ro) {
return true;
} else if (room.f !== nextState.room.f) {
return true;
} else if (loaded !== nextState.loaded) {
return true;
} else if (joined !== nextState.joined) {
@ -129,10 +126,11 @@ export default class RoomView extends LoggedView {
componentDidUpdate(prevProps, prevState) {
const { room } = this.state;
const { navigator } = this.props;
const { componentId } = this.props;
if (prevState.room.f !== room.f) {
navigator.setButtons({
Navigation.mergeOptions(componentId, {
topBar: {
rightButtons: [{
id: 'more',
testID: 'room-view-header-actions',
@ -142,6 +140,7 @@ export default class RoomView extends LoggedView {
testID: 'room-view-header-star',
icon: room.f ? iconsMap.star : iconsMap.starOutline
}]
}
});
}
}
@ -153,39 +152,9 @@ export default class RoomView extends LoggedView {
closeRoom();
}
onNavigatorEvent(event) {
const { room } = this.state;
const { rid, f } = room;
const { navigator } = this.props;
if (event.type === 'NavBarButtonPress') {
if (event.id === 'more') {
if (RoomActionsView == null) {
RoomActionsView = require('../RoomActionsView').default;
Navigation.registerComponent('RoomActionsView', () => 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 (
<SafeAreaView style={styles.container} testID='room-view'>
<SafeAreaView style={styles.container} testID='room-view' forceInset={{ bottom: 'never' }}>
{this.renderList()}
{room._id && showActions
? <MessageActions room={room} user={user} />

View File

@ -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,7 +29,23 @@ const styles = StyleSheet.create({
}
});
const Header = ({ onPress, serverName, showServerDropdown }) => (
const Header = ({
onPress, serverName, showServerDropdown, setSearchInputRef, showSearchHeader, onSearchChangeText
}) => {
if (showSearchHeader) {
return (
<View style={styles.container}>
<TextInput
ref={setSearchInputRef}
style={styles.server}
placeholder='Search'
placeholderTextColor='rgba(255, 255, 255, 0.5)'
onChangeText={onSearchChangeText}
/>
</View>
);
}
return (
<View style={styles.container}>
<TouchableOpacity onPress={onPress} testID='rooms-list-header-server-dropdown-button'>
<View style={styles.button}>
@ -38,11 +55,15 @@ const Header = ({ onPress, serverName, showServerDropdown }) => (
</TouchableOpacity>
</View>
);
};
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 = {

View File

@ -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 (
<Header
onPress={this.onPress}
serverName={serverName}
showServerDropdown={showServerDropdown}
showSearchHeader={showSearchHeader}
setSearchInputRef={this.setSearchInputRef}
onSearchChangeText={text => this.onSearchChangeText(text)}
/>
);
}

View File

@ -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,26 +85,36 @@ export default class ServerDropdown extends Component {
}
addServer = () => {
const { navigator, server } = this.props;
const { server } = this.props;
this.close();
setTimeout(() => {
navigator.showModal({
screen: 'OnboardingView',
Navigation.showModal({
stack: {
children: [{
component: {
name: 'OnboardingView',
passProps: {
previousServer: server
},
navigatorStyle: {
navBarHidden: true,
options: {
topBar: {
visible: false
},
layout: {
orientation: 'portrait'
}
}
}
}]
}
});
}, ANIMATION_DURATION);
}
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);

View File

@ -54,7 +54,7 @@ export default class Sort extends Component {
}
}
setSortPreference = async(param) => {
setSortPreference = (param) => {
const { setSortPreference } = this.props;
try {

View File

@ -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 options() {
return {
topBar: {
leftButtons,
rightButtons,
title: {
component: {
name: 'RoomsListHeaderView',
alignment: isAndroid() ? 'left' : 'center'
}
static navigatorStyle = {
navBarCustomView: 'RoomsListHeaderView',
navBarComponentAlignment: 'fill',
navBarBackgroundColor: isAndroid() ? '#2F343D' : undefined,
navBarTextColor: isAndroid() ? '#FFF' : undefined,
navBarButtonColor: isAndroid() ? '#FFF' : undefined
}
},
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,37 +178,44 @@ export default class RoomsListView extends LoggedView {
}
}
onNavigatorEvent(event) {
const { navigator } = this.props;
if (event.type === 'NavBarButtonPress') {
if (event.id === 'newMessage') {
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'newMessage') {
if (NewMessageView == null) {
NewMessageView = require('../NewMessageView').default;
Navigation.registerComponent('NewMessageView', () => NewMessageView, store, Provider);
Navigation.registerComponentWithRedux('NewMessageView', () => NewMessageView, Provider, store);
}
navigator.showModal({
screen: 'NewMessageView',
title: I18n.t('New_Message'),
Navigation.showModal({
stack: {
children: [{
component: {
name: 'NewMessageView',
passProps: {
onPressItem: this._onPressItem
},
options: {
topBar: {
title: {
text: I18n.t('New_Message')
}
}
}
}
}]
}
});
} else if (event.id === 'settings') {
navigator.toggleDrawer({
side: 'left'
});
} else if (event.id === 'search') {
} else if (buttonId === 'settings') {
Drawer.toggle();
} else if (buttonId === 'search') {
this.initSearchingAndroid();
} else if (event.id === 'cancelSearch' || event.id === 'back') {
} else if (buttonId === 'back') {
this.cancelSearchingAndroid();
}
} else if (event.type === 'ScreenChangedEvent' && event.id === 'didAppear') {
navigator.setDrawerEnabled({
side: 'left',
enabled: true
});
}
internalSetState = (...args) => {
LayoutAnimation.easeInEaseOut();
this.setState(...args);
}
getSubscriptions = () => {
@ -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({
const { openSearchHeader } = this.props;
openSearchHeader();
Navigation.mergeOptions('RoomsListView', {
topBar: {
leftButtons: [{
id: 'cancelSearch',
icon: { uri: 'back', scale: Dimensions.get('window').scale }
id: 'back',
icon: { uri: 'back', scale: Dimensions.get('window').scale },
testID: 'rooms-list-view-cancel-search'
}],
rightButtons: []
});
navigator.setStyle({
navBarCustomView: 'RoomsListSearchView',
navBarComponentAlignment: 'fill'
}
});
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 (
<SafeAreaView style={styles.container} testID='rooms-list-view'>
<SafeAreaView style={styles.container} testID='rooms-list-view' forceInset={{ bottom: 'never' }}>
{this.renderScroll()}
{showSortDropdown
? (

View File

@ -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 (
<SafeAreaView style={styles.container} testID='search-messages-view'>
<SafeAreaView style={styles.container} testID='search-messages-view' forceInset={{ bottom: 'never' }}>
<View style={styles.searchContainer}>
<RCTextInput
inputRef={(e) => { this.name = e; }}

View File

@ -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,27 +100,39 @@ 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: ''
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 } = this.props;
const { rid, componentId } = this.props;
try {
setLoadingInvite(true);
await RocketChat.addUsersToRoom(rid);
navigator.pop();
Navigation.pop(componentId);
} catch (e) {
log('RoomActions Add User', e);
} finally {
@ -136,11 +141,6 @@ export default class SelectedUsersView extends LoggedView {
}
}
}
}
onSearchChangeText(text) {
this.search(text);
}
// eslint-disable-next-line react/sort-comp
updateState = debounce(() => {
@ -271,7 +271,7 @@ export default class SelectedUsersView extends LoggedView {
render = () => {
const { loading } = this.props;
return (
<SafeAreaView style={styles.safeAreaView} testID='select-users-view'>
<SafeAreaView style={styles.safeAreaView} testID='select-users-view' forceInset={{ bottom: 'never' }}>
{this.renderList()}
<Loading visible={loading} />
</SafeAreaView>

View File

@ -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}
>
<SafeAreaView style={sharedStyles.container} testID='settings-view'>
<SafeAreaView style={sharedStyles.container} testID='settings-view' forceInset={{ bottom: 'never' }}>
<RNPickerSelect
items={languages}
onValueChange={(value) => {

View File

@ -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 (
<SafeAreaView style={styles.list} testID='snippeted-messages-view'>
<SafeAreaView style={styles.list} testID='snippeted-messages-view' forceInset={{ bottom: 'never' }}>
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -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 { openStarredMessages as openStarredMessagesAction, closeStarredMessages as closeStarredMessagesAction } from '../../actions/starredMessages';
import { toggleStarRequest as toggleStarRequestAction } from '../../actions/messages';
@ -33,6 +32,16 @@ const options = [I18n.t('Unstar'), I18n.t('Cancel')];
}))
/** @extends React.Component */
export default class StarredMessagesView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Starred')
}
}
};
}
static propTypes = {
rid: PropTypes.string,
messages: PropTypes.array,
@ -136,7 +145,7 @@ export default class StarredMessagesView extends LoggedView {
}
return (
<SafeAreaView style={styles.list} testID='starred-messages-view'>
<SafeAreaView style={styles.list} testID='starred-messages-view' forceInset={{ bottom: 'never' }}>
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -1,7 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { WebView, SafeAreaView } from 'react-native';
import { WebView } from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import styles from './Styles';
import LoggedView from './View';

View File

@ -1,26 +1,10 @@
import React from 'react';
import { Platform } from 'react-native';
import PropTypes from 'prop-types';
import { Answers } from 'react-native-fabric';
import { NavigationActions } from '../Navigation';
const isAndroid = () => 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);
}

View File

@ -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);
};

View File

@ -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('');

View File

@ -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);

View File

@ -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();
});

View File

@ -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);
});

View File

@ -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();

View File

@ -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) {

View File

@ -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 = "<group>"; };
7A32C20F206D791D001C80E9 /* Fabric.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Fabric.framework; path = "../../../../Downloads/com.crashlytics.ios-manual/Fabric.framework"; sourceTree = "<group>"; };
7A32C245206D791D001C80E9 /* Crashlytics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Crashlytics.framework; path = "../../../../Downloads/com.crashlytics.ios-manual/Crashlytics.framework"; sourceTree = "<group>"; };
7A3562E020E1567900A4CF66 /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/ios/ReactNativeNavigation.xcodeproj"; sourceTree = "<group>"; };
7A430E1620238C01008F55BC /* RCTCustomInputController.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTCustomInputController.xcodeproj; path = "../node_modules/react-native-keyboard-input/lib/ios/RCTCustomInputController.xcodeproj"; sourceTree = "<group>"; };
7A807B4C215EC5E400A4348D /* ReactNativeNavigation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReactNativeNavigation.xcodeproj; path = "../node_modules/react-native-navigation/lib/ios/ReactNativeNavigation.xcodeproj"; sourceTree = "<group>"; };
7A8DEB1B20ED0BDE00C5DCE4 /* RNNotifications.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNNotifications.xcodeproj; path = "../node_modules/react-native-notifications/RNNotifications/RNNotifications.xcodeproj"; sourceTree = "<group>"; };
7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTToast.xcodeproj; path = "../node_modules/@remobile/react-native-toast/ios/RCTToast.xcodeproj"; sourceTree = "<group>"; };
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
@ -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 = "<group>";
};
7A3562E120E1567900A4CF66 /* Products */ = {
isa = PBXGroup;
children = (
7A3562E520E1567900A4CF66 /* libReactNativeNavigation.a */,
);
name = Products;
sourceTree = "<group>";
};
7A430E1720238C01008F55BC /* Products */ = {
isa = PBXGroup;
children = (
@ -848,6 +847,15 @@
name = Products;
sourceTree = "<group>";
};
7A807B4D215EC5E400A4348D /* Products */ = {
isa = PBXGroup;
children = (
7A807B52215EC5E500A4348D /* libReactNativeNavigation.a */,
7A807B54215EC5E500A4348D /* ReactNativeNavigationTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
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;

View File

@ -15,7 +15,8 @@
#import <Fabric/Fabric.h>
#import <Crashlytics/Crashlytics.h>
#import <React/RCTLinkingManager.h>
#import "RCCManager.h"
//#import "RCCManager.h"
#import <ReactNativeNavigation/ReactNativeNavigation.h>
#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"

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

50
package-lock.json generated
View File

@ -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",

View File

@ -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",