Switch to react-navigation (#687)

This commit is contained in:
Diego Mello 2019-03-12 13:23:06 -03:00 committed by GitHub
parent eb4b1553bf
commit 38dabfc9ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
249 changed files with 4321 additions and 5856 deletions

View File

@ -228,24 +228,3 @@ $ detox build
```bash
$ detox test
```
## Storybook
- General requirements
- Install storybook
```bash
$ yarn global add @storybook/cli
```
- Running storybook
- Run storybook application
```bash
$ yarn storybook
```
- Run application in other shell
```bash
$ react-native run-ios
```
- Running storybook on browser to help stories navigation
```
open http://localhost:7007/
```

View File

@ -0,0 +1,3 @@
export default {
hide: () => ''
};

View File

@ -73,7 +73,7 @@ import com.android.build.OutputFile
*/
project.ext.react = [
entryFile: "index.android.js",
entryFile: "index.js",
iconFontNames: [ 'custom.ttf' ]
]
@ -107,14 +107,7 @@ android {
ndk {
abiFilters "armeabi-v7a", "x86"
}
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative57_5"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
packagingOptions {
@ -192,6 +185,9 @@ configurations.all {
}
dependencies {
implementation project(':react-native-orientation-locker')
implementation project(':react-native-splash-screen')
implementation project(':react-native-screens')
implementation project(':react-native-action-sheet')
implementation project(':react-native-device-info')
implementation project(':react-native-gesture-handler')
@ -206,7 +202,6 @@ dependencies {
implementation project(':@remobile/react-native-toast')
implementation project(':react-native-fast-image')
implementation project(':realm')
implementation project(':react-native-navigation')
implementation project(':reactnativenotifications')
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.android.support:appcompat-v7:27.1.1"
@ -230,4 +225,3 @@ task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

View File

@ -1,40 +1,47 @@
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.NavigationActivity;
import android.view.View;
import android.content.Intent;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import android.os.Bundle;
import android.support.annotation.Nullable;
import com.facebook.react.ReactFragmentActivity;
import org.devio.rn.splashscreen.SplashScreen;
import android.content.Intent;
import android.content.res.Configuration;
public class MainActivity extends NavigationActivity {
public class MainActivity extends ReactFragmentActivity {
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this);
super.onCreate(null);
}
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "RocketChatRN";
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
};
}
View view = new View(this);
view.setBackgroundResource(R.drawable.launch_screen_bitmap);
setContentView(view);
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
this.sendBroadcast(intent);
}
}
// 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

@ -1,7 +1,14 @@
package chat.rocket.reactnative;
import android.content.Context;
import android.os.Bundle;
import android.app.Application;
import com.facebook.react.ReactApplication;
import org.wonday.orientation.OrientationPackage;
import org.devio.rn.splashscreen.SplashScreenReactPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.AlexanderZaytsev.RNI18n.RNI18nPackage;
import com.reactnative.ivpusic.imagepicker.PickerPackage;
@ -9,16 +16,10 @@ import com.RNFetchBlob.RNFetchBlobPackage;
import com.brentvatne.react.ReactVideoPackage;
import com.crashlytics.android.Crashlytics;
import com.dylanvann.fastimage.FastImageViewPackage;
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;
@ -29,52 +30,34 @@ import com.wix.reactnativenotifications.core.notification.IPushNotification;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.learnium.RNDeviceInfo.RNDeviceInfo;
import com.actionsheet.ActionSheetPackage;
import io.fabric.sdk.android.Fabric;
import io.realm.react.RealmReactPackage;
import com.swmansion.rnscreens.RNScreensPackage;
import android.content.Context;
import android.os.Bundle;
import java.util.Arrays;
import java.util.List;
import io.fabric.sdk.android.Fabric;
import io.realm.react.RealmReactPackage;
public class MainApplication extends NavigationApplication implements INotificationsApplication {
// private NotificationsLifecycleFacade notificationsLifecycleFacade;
public class MainApplication extends Application implements ReactApplication, INotificationsApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean isDebug() {
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
// @Override
// public String getJSMainModuleName() {
// return "index.android";
// }
protected ReactGateway createReactGateway() {
ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) {
@Override
protected String getJSMainModuleName() {
return "index.android";
}
};
return new ReactGateway(this, isDebug(), host);
}
protected List<ReactPackage> getPackages() {
// Add additional packages you require here
// No need to add RnnPackage and MainReactPackage
return Arrays.<ReactPackage>asList(
);
}
@Override
public List<ReactPackage> createAdditionalReactPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new OrientationPackage(),
new SplashScreenReactPackage(),
new RNGestureHandlerPackage(),
new RNScreensPackage(),
new ActionSheetPackage(),
new RNDeviceInfo(),
new RNGestureHandlerPackage(),
new PickerPackage(),
new VectorIconsPackage(),
new RNFetchBlobPackage(),
@ -91,10 +74,22 @@ public class MainApplication extends NavigationApplication implements INotificat
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
SoLoader.init(this, /* native exopackage */ false);
}
@Override

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/launch_screen" android:scaleType="centerCrop" />
</RelativeLayout>

View File

@ -46,12 +46,6 @@ subprojects { subproject ->
defaultConfig {
targetSdkVersion 28
}
variantFilter { variant ->
def names = variant.flavors*.name
if (names.contains("reactNative51") || names.contains("reactNative55") || names.contains("reactNative56") || names.contains("reactNative57") || names.contains("reactNative57WixFork")) {
setIgnore(true)
}
}
}
}
}

View File

@ -16,6 +16,6 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# android.enableAapt2=false
android.enableAapt2=false # commenting this makes notifications to stop working
android.useDeprecatedNdk=true
VERSIONCODE=999999999

View File

@ -1,4 +1,10 @@
rootProject.name = 'RocketChatRN'
include ':react-native-orientation-locker'
project(':react-native-orientation-locker').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-orientation-locker/android')
include ':react-native-splash-screen'
project(':react-native-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-splash-screen/android')
include ':react-native-screens'
project(':react-native-screens').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-screens/android')
include ':react-native-action-sheet'
project(':react-native-action-sheet').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-action-sheet/android')
include ':react-native-device-info'
@ -27,8 +33,6 @@ include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
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/lib/android/app/')
include ':reactnativenotifications'
project(':reactnativenotifications').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-notifications/android')
include ':app'

View File

@ -38,7 +38,7 @@ export const ROOM = createRequestTypes('ROOM', [
'MESSAGE_RECEIVED',
'SET_LAST_OPEN'
]);
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'SET_STACK_ROOT']);
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT']);
export const MESSAGES = createRequestTypes('MESSAGES', [
...defaultTypes,
'ACTIONS_SHOW',

View File

@ -20,13 +20,6 @@ export function appInit() {
};
}
export function setStackRoot(stackRoot) {
return {
type: APP.SET_STACK_ROOT,
stackRoot
};
}
export function setCurrentServer(server) {
return {
type: types.SET_CURRENT_SERVER,

View File

@ -1,28 +0,0 @@
import * as types from './actionsTypes';
export function openSnippetedMessages(rid, limit) {
return {
type: types.SNIPPETED_MESSAGES.OPEN,
rid,
limit
};
}
export function readySnippetedMessages() {
return {
type: types.SNIPPETED_MESSAGES.READY
};
}
export function closeSnippetedMessages() {
return {
type: types.SNIPPETED_MESSAGES.CLOSE
};
}
export function snippetedMessagesReceived(messages) {
return {
type: types.SNIPPETED_MESSAGES.MESSAGES_RECEIVED,
messages
};
}

View File

@ -1,3 +1,5 @@
import { isIOS } from '../utils/deviceInfo';
export const COLOR_DANGER = '#f5455c';
export const COLOR_BUTTON_PRIMARY = '#1d74f5';
export const COLOR_TEXT = '#292E35';
@ -8,3 +10,7 @@ export const STATUS_COLORS = {
away: '#ffd21f',
offline: '#cbced1'
};
export const HEADER_BACKGROUND = isIOS ? '#FFF' : '#2F343D';
export const HEADER_TITLE = isIOS ? '#0C0D0F' : '#FFF';
export const HEADER_BACK = isIOS ? '#1d74f5' : '#FFF';

View File

@ -1,62 +0,0 @@
import { Platform } from 'react-native';
export const DARK_HEADER = {
statusBar: {
backgroundColor: '#2F343D',
style: 'light'
},
topBar: {
backButton: {
showTitle: false,
color: '#fff'
},
background: {
color: '#2F343D'
},
title: {
color: '#FFF'
},
leftButtonStyle: {
color: '#FFF'
},
rightButtonStyle: {
color: '#FFF'
}
}
};
export const LIGHT_HEADER = {
statusBar: {
backgroundColor: '#FFF',
style: 'dark'
},
topBar: {
backButton: {
showTitle: false,
color: '#1d74f5'
},
background: {
color: undefined
},
title: {
color: '#0C0D0F'
},
leftButtonStyle: {
color: '#1d74f5'
},
rightButtonStyle: {
color: '#1d74f5'
}
}
};
export const DEFAULT_HEADER = {
...Platform.select({
ios: {
...LIGHT_HEADER
},
android: {
...DARK_HEADER
}
})
};

View File

@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Text } from 'react-native';
import HeaderButtons, { HeaderButton, Item } from 'react-navigation-header-buttons';
import { CustomIcon } from '../lib/Icons';
import { isIOS } from '../utils/deviceInfo';
const color = isIOS ? '#1D74F5' : '#FFF';
const CustomHeaderButton = React.memo(props => (
<HeaderButton {...props} IconComponent={CustomIcon} iconSize={23} color={color} />
));
export const CustomHeaderButtons = React.memo(props => (
<HeaderButtons
HeaderButtonComponent={CustomHeaderButton}
{...props}
/>
));
export const DrawerButton = React.memo(({ navigation, testID }) => (
<CustomHeaderButtons left>
<Item title='drawer' iconName='customize' onPress={navigation.toggleDrawer} testID={testID} />
</CustomHeaderButtons>
));
export const CloseModalButton = React.memo(({ navigation, testID }) => (
<CustomHeaderButtons left>
<Item title='close' iconName='cross' onPress={() => navigation.pop()} testID={testID} />
</CustomHeaderButtons>
));
export const MoreButton = React.memo(({ onPress, testID }) => (
<CustomHeaderButtons>
<Item title='more' iconName='menu' onPress={onPress} testID={testID} />
</CustomHeaderButtons>
));
export const LegalButton = React.memo(({ navigation, testID }) => (
<MoreButton onPress={() => navigation.navigate('LegalView')} testID={testID} />
));
DrawerButton.propTypes = {
navigation: PropTypes.object.isRequired,
testID: PropTypes.string.isRequired
};
CloseModalButton.propTypes = {
navigation: PropTypes.object.isRequired,
testID: PropTypes.string.isRequired
};
MoreButton.propTypes = {
onPress: PropTypes.func.isRequired,
testID: PropTypes.string.isRequired
};
LegalButton.propTypes = {
navigation: PropTypes.object.isRequired,
testID: PropTypes.string.isRequired
};
export { Item };
export default () => <Text>a</Text>;

View File

@ -6,7 +6,7 @@ const styles = StyleSheet.create({
style: {
marginRight: 7,
marginTop: 3,
color: '#9EA2A8'
tintColor: '#9EA2A8'
}
});

View File

@ -0,0 +1,25 @@
import React from 'react';
import { StatusBar as StatusBarRN } from 'react-native';
import PropTypes from 'prop-types';
import { isIOS } from '../utils/deviceInfo';
import { HEADER_BACKGROUND } from '../constants/colors';
const HEADER_BAR_STYLE = isIOS ? 'dark-content' : 'light-content';
const StatusBar = React.memo(({ light }) => {
if (light) {
return <StatusBarRN backgroundColor='#FFF' barStyle='dark-content' animated />;
}
return <StatusBarRN backgroundColor={HEADER_BACKGROUND} barStyle={HEADER_BAR_STYLE} animated />;
});
StatusBar.propTypes = {
light: PropTypes.bool
};
StatusBar.defaultProps = {
light: false
};
export default StatusBar;

View File

@ -214,7 +214,6 @@ export default {
No_mentioned_messages: 'Keine erwähnten Nachrichten',
No_pinned_messages: 'Keine angehefteten Nachrichten',
No_results_found: 'keine Ergebnisse gefunden',
No_snippeted_messages: 'Keine Nachrichten-Snippets',
No_starred_messages: 'Keine markierten Nachrichten',
No_announcement_provided: 'Keine Ankündigung erfolgt.',
No_description_provided: 'Keine Beschreibung angegeben.',
@ -292,9 +291,6 @@ export default {
Share: 'Teilen',
Sign_in_your_server: 'Melden Sie sich bei Ihrem Server an',
Sign_Up: 'Anmelden',
Snippet_Messages: 'Snippet-Nachrichten',
snippeted: 'snippeted',
Snippets: 'Snippets',
Some_field_is_invalid_or_empty: 'Ein Feld ist ungültig oder leer',
Sorting_by: 'Sortierung nach {{key}}',
Star_room: 'Favorisierter Raum',

View File

@ -214,7 +214,6 @@ export default {
No_mentioned_messages: 'No mentioned messages',
No_pinned_messages: 'No pinned messages',
No_results_found: 'No results found',
No_snippeted_messages: 'No snippeted messages',
No_starred_messages: 'No starred messages',
No_announcement_provided: 'No announcement provided.',
No_description_provided: 'No description provided.',
@ -292,9 +291,6 @@ export default {
Share: 'Share',
Sign_in_your_server: 'Sign in your server',
Sign_Up: 'Sign Up',
Snippet_Messages: 'Snippet Messages',
snippeted: 'snippeted',
Snippets: 'Snippets',
Some_field_is_invalid_or_empty: 'Some field is invalid or empty',
Sorting_by: 'Sorting by {{key}}',
Star_room: 'Star room',

View File

@ -214,7 +214,6 @@ export default {
No_mentioned_messages: 'Aucun message mentionné',
No_pinned_messages: 'Aucun message épinglé',
No_results_found: 'Aucun résultat trouvé',
No_snippeted_messages: 'Aucun message extrait',
No_starred_messages: 'Pas de messages suivis',
No_announcement_provided: 'Aucune annonce fournie.',
No_description_provided: 'Aucune description fournie.',
@ -292,9 +291,6 @@ export default {
Share: 'Partager',
Sign_in_your_server: 'Connectez-vous à votre serveur',
Sign_Up: 'S\'inscrire',
Snippet_Messages: 'Messages Extraits',
snippeted: 'extrait',
Snippets: 'Extraits',
Some_field_is_invalid_or_empty: 'Certains champs sont invalides ou vides',
Sorting_by: 'Tri par {{key}}',
Star_room: 'Favoriser canal',

View File

@ -217,7 +217,6 @@ export default {
No_mentioned_messages: 'Não há menções',
No_pinned_messages: 'Não há mensagens fixadas',
No_results_found: 'Nenhum resultado encontrado',
No_snippeted_messages: 'Não há trechos de mensagens',
No_starred_messages: 'Não há mensagens favoritas',
No_announcement_provided: 'Sem anúncio.',
No_description_provided: 'Sem descrição.',
@ -293,9 +292,6 @@ export default {
Share: 'Compartilhar',
Sign_in_your_server: 'Entrar no seu servidor',
Sign_Up: 'Registrar',
Snippet_Messages: 'Trecho de Mensagens',
snippeted: 'trecho de mensagem',
Snippets: 'Trecho de mensagem',
Some_field_is_invalid_or_empty: 'Algum campo está inválido ou vazio',
Sorting_by: 'Ordenando por {{key}}',
Star_room: 'Favoritar sala',

View File

@ -183,7 +183,6 @@ export default {
No_files: 'Нет файлов',
No_mentioned_messages: 'Нет упоминаний',
No_pinned_messages: 'Нет прикрепленных сообщений',
No_snippeted_messages: 'Нет сообщений со сниппетом',
No_starred_messages: 'Нет отмеченных сообщений',
No_announcement_provided: 'Нет объявлений.',
No_description_provided: 'Нет описания.',
@ -255,9 +254,6 @@ export default {
Share: 'Поделиться',
Sign_in_your_server: 'Войдите на ваш сервер',
Sign_Up: 'Регистрация',
Snippet_Messages: 'Сообщения со сниппетом',
snippeted: 'сниппет добавлен',
Snippets: 'Сниппеты',
Some_field_is_invalid_or_empty: 'Некоторые поля недопустимы или пусты',
Star_room: 'Star room',
Star: 'Звезда',

View File

@ -287,9 +287,6 @@ export default {
Share: '分享',
Sign_in_your_server: '登录你的服务器',
Sign_Up: '注册',
Snippet_Messages: '代码片段消息',
snippeted: '代码片段',
Snippets: '代码片段',
Some_field_is_invalid_or_empty: '某些字段无效或为空',
Sorting_by: '按{{key}}排序',
Star_room: '将房间标星',

View File

@ -1,78 +1,49 @@
import { Component } from 'react';
import React from 'react';
import {
createStackNavigator, createAppContainer, createSwitchNavigator, createDrawerNavigator
} from 'react-navigation';
import { Provider } from 'react-redux';
import { useScreens } from 'react-native-screens'; // eslint-disable-line import/no-unresolved
import { Linking } from 'react-native';
import { appInit } from './actions';
import { deepLinkingOpen } from './actions/deepLinking';
import store from './lib/createStore';
import Icons from './lib/Icons';
import OnboardingView from './views/OnboardingView';
import NewServerView from './views/NewServerView';
import LoginSignupView from './views/LoginSignupView';
import AuthLoadingView from './views/AuthLoadingView';
import RoomsListView from './views/RoomsListView';
import RoomView from './views/RoomView';
import NewMessageView from './views/NewMessageView';
import LoginView from './views/LoginView';
import Navigation from './lib/Navigation';
import Sidebar from './views/SidebarView';
import ProfileView from './views/ProfileView';
import SettingsView from './views/SettingsView';
import RoomActionsView from './views/RoomActionsView';
import RoomInfoView from './views/RoomInfoView';
import RoomInfoEditView from './views/RoomInfoEditView';
import RoomMembersView from './views/RoomMembersView';
import RoomFilesView from './views/RoomFilesView';
import MentionedMessagesView from './views/MentionedMessagesView';
import StarredMessagesView from './views/StarredMessagesView';
import SearchMessagesView from './views/SearchMessagesView';
import PinnedMessagesView from './views/PinnedMessagesView';
import SelectedUsersView from './views/SelectedUsersView';
import CreateChannelView from './views/CreateChannelView';
import LegalView from './views/LegalView';
import TermsServiceView from './views/TermsServiceView';
import PrivacyPolicyView from './views/PrivacyPolicyView';
import ForgotPasswordView from './views/ForgotPasswordView';
import RegisterView from './views/RegisterView';
import OAuthView from './views/OAuthView';
import SetUsernameView from './views/SetUsernameView';
import { HEADER_BACKGROUND, HEADER_TITLE, HEADER_BACK } from './constants/colors';
import parseQuery from './lib/methods/helpers/parseQuery';
import { initializePushNotifications } from './push';
import { DEFAULT_HEADER } from './constants/headerOptions';
import store from './lib/createStore';
const startLogged = () => {
Navigation.loadView('ProfileView');
Navigation.loadView('RoomsListHeaderView');
Navigation.loadView('RoomsListView');
Navigation.loadView('RoomView');
Navigation.loadView('RoomHeaderView');
Navigation.loadView('SettingsView');
Navigation.loadView('SidebarView');
Navigation.setRoot({
root: {
stack: {
id: 'AppRoot',
children: [{
component: {
id: 'RoomsListView',
name: 'RoomsListView'
}
}]
}
}
});
};
const startNotLogged = () => {
Navigation.loadView('OnboardingView');
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: 'OnboardingView'
}
}],
options: {
layout: {
orientation: ['portrait']
}
}
}
}
});
};
const startSetUsername = () => {
Navigation.loadView('SetUsernameView');
Navigation.setRoot({
root: {
stack: {
children: [{
component: {
name: 'SetUsernameView'
}
}],
options: {
layout: {
orientation: ['portrait']
}
}
}
}
});
};
useScreens();
initializePushNotifications();
const handleOpenURL = ({ url }) => {
if (url) {
@ -86,51 +57,139 @@ const handleOpenURL = ({ url }) => {
}
};
Icons.configure();
export default class App extends Component {
constructor(props) {
super(props);
initializePushNotifications();
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setDefaultOptions({
...DEFAULT_HEADER,
sideMenu: {
left: {
enabled: false
},
right: {
enabled: false
}
}
});
store.dispatch(appInit());
store.subscribe(this.onStoreUpdate.bind(this));
});
Linking
.getInitialURL()
.then(url => handleOpenURL({ url }))
.catch(e => console.warn(e));
Linking.addEventListener('url', handleOpenURL);
}
onStoreUpdate = () => {
const { root } = store.getState().app;
const defaultHeader = {
headerStyle: {
backgroundColor: HEADER_BACKGROUND
},
headerTitleStyle: {
color: HEADER_TITLE
},
headerBackTitle: null,
headerTintColor: HEADER_BACK
};
if (this.currentRoot !== root) {
this.currentRoot = root;
if (root === 'outside') {
startNotLogged();
} else if (root === 'inside') {
startLogged();
} else if (root === 'setUsername') {
startSetUsername();
}
}
}
// Outside
const OutsideStack = createStackNavigator({
OnboardingView: {
screen: OnboardingView,
header: null
},
NewServerView,
LoginSignupView,
LoginView,
ForgotPasswordView,
RegisterView
}, {
defaultNavigationOptions: defaultHeader
});
setDeviceToken(deviceToken) {
this.deviceToken = deviceToken;
}
const LegalStack = createStackNavigator({
LegalView,
TermsServiceView,
PrivacyPolicyView
}, {
defaultNavigationOptions: defaultHeader
});
const OAuthStack = createStackNavigator({
OAuthView
}, {
defaultNavigationOptions: defaultHeader
});
const OutsideStackModal = createStackNavigator({
OutsideStack,
LegalStack,
OAuthStack
},
{
mode: 'modal',
headerMode: 'none'
});
// Inside
const ChatsStack = createStackNavigator({
RoomsListView,
RoomView,
RoomActionsView,
RoomInfoView,
RoomInfoEditView,
RoomMembersView,
RoomFilesView,
MentionedMessagesView,
StarredMessagesView,
SearchMessagesView,
PinnedMessagesView,
SelectedUsersView
}, {
defaultNavigationOptions: defaultHeader
});
const ProfileStack = createStackNavigator({
ProfileView
}, {
defaultNavigationOptions: defaultHeader
});
const SettingsStack = createStackNavigator({
SettingsView
}, {
defaultNavigationOptions: defaultHeader
});
const ChatsDrawer = createDrawerNavigator({
ChatsStack,
ProfileStack,
SettingsStack
}, {
contentComponent: Sidebar
});
const NewMessageStack = createStackNavigator({
NewMessageView,
SelectedUsersViewCreateChannel: SelectedUsersView,
CreateChannelView
}, {
defaultNavigationOptions: defaultHeader
});
const InsideStackModal = createStackNavigator({
Main: ChatsDrawer,
NewMessageStack
},
{
mode: 'modal',
headerMode: 'none'
});
const SetUsernameStack = createStackNavigator({
SetUsernameView
});
const App = createAppContainer(createSwitchNavigator(
{
OutsideStack: OutsideStackModal,
InsideStack: InsideStackModal,
AuthLoading: AuthLoadingView,
SetUsernameStack
},
{
initialRouteName: 'AuthLoading'
}
));
export default () => (
<Provider store={store}>
<App
ref={(navigatorRef) => {
Navigation.setTopLevelNavigator(navigatorRef);
}}
/>
</Provider>
);

View File

@ -9,36 +9,3 @@ const CustomIcon = createIconSetFromIcoMoon(
);
export { CustomIcon };
// icon name from provider: [ size of the uri, icon provider, name to be used later ]
const icons = {
'Star-filled': [25, CustomIcon, 'star'],
star: [25, CustomIcon, 'starOutline'],
menu: [25, CustomIcon, 'more'],
edit: [25, CustomIcon, 'edit'],
cross: [25, CustomIcon, 'close'],
customize: [25, CustomIcon, 'settings'],
magnifier: [25, CustomIcon, 'search'],
'edit-rounded': [25, CustomIcon, 'new_channel']
};
class Icons {
constructor() {
this.icons = {};
}
async configure() {
const promises = Object.keys(icons).map((icon) => {
const Provider = icons[icon][1];
return Provider.getImageSource(icon, icons[icon][0], '#FFF');
});
const sources = await Promise.all(promises);
Object.keys(icons).forEach((icon, i) => (this.icons[icons[icon][2]] = sources[i]));
}
getSource(icon) {
return this.icons[icon];
}
}
export default new Icons();

View File

@ -1,260 +1,21 @@
import { Navigation } from 'react-native-navigation';
import { Provider } from 'react-redux';
import { gestureHandlerRootHOC } from 'react-native-gesture-handler';
import { NavigationActions } from 'react-navigation';
import store from './createStore';
import debounce from '../utils/debounce';
let _navigator;
const DRAWER_ID = 'SidebarView';
class NavigationManager {
constructor() {
this.views = {
OnboardingView: {
name: 'OnboardingView',
loaded: false,
require: () => require('../views/OnboardingView').default
},
ProfileView: {
name: 'ProfileView',
loaded: false,
require: () => require('../views/ProfileView').default
},
RoomsListHeaderView: {
name: 'RoomsListHeaderView',
loaded: false,
require: () => require('../views/RoomsListView/Header').default
},
RoomsListView: {
name: 'RoomsListView',
loaded: false,
require: () => require('../views/RoomsListView').default
},
RoomView: {
name: 'RoomView',
loaded: false,
require: () => require('../views/RoomView').default
},
RoomHeaderView: {
name: 'RoomHeaderView',
loaded: false,
require: () => require('../views/RoomView/Header').default
},
SettingsView: {
name: 'SettingsView',
loaded: false,
require: () => require('../views/SettingsView').default
},
SidebarView: {
name: 'SidebarView',
loaded: false,
require: () => require('../views/SidebarView').default
},
NewServerView: {
name: 'NewServerView',
loaded: false,
require: () => require('../views/NewServerView').default
},
CreateChannelView: {
name: 'CreateChannelView',
loaded: false,
require: () => require('../views/CreateChannelView').default
},
ForgotPasswordView: {
name: 'ForgotPasswordView',
loaded: false,
require: () => require('../views/ForgotPasswordView').default
},
LegalView: {
name: 'LegalView',
loaded: false,
require: () => require('../views/LegalView').default
},
LoginSignupView: {
name: 'LoginSignupView',
loaded: false,
require: () => require('../views/LoginSignupView').default
},
LoginView: {
name: 'LoginView',
loaded: false,
require: () => require('../views/LoginView').default
},
NewMessageView: {
name: 'NewMessageView',
loaded: false,
require: () => require('../views/NewMessageView').default
},
OAuthView: {
name: 'OAuthView',
loaded: false,
require: () => require('../views/OAuthView').default
},
PrivacyPolicyView: {
name: 'PrivacyPolicyView',
loaded: false,
require: () => require('../views/PrivacyPolicyView').default
},
RegisterView: {
name: 'RegisterView',
loaded: false,
require: () => require('../views/RegisterView').default
},
SelectedUsersView: {
name: 'SelectedUsersView',
loaded: false,
require: () => require('../views/SelectedUsersView').default
},
SetUsernameView: {
name: 'SetUsernameView',
loaded: false,
require: () => require('../views/SetUsernameView').default
},
TermsServiceView: {
name: 'TermsServiceView',
loaded: false,
require: () => require('../views/TermsServiceView').default
},
MentionedMessagesView: {
name: 'MentionedMessagesView',
loaded: false,
require: () => require('../views/MentionedMessagesView').default
},
PinnedMessagesView: {
name: 'PinnedMessagesView',
loaded: false,
require: () => require('../views/PinnedMessagesView').default
},
RoomActionsView: {
name: 'RoomActionsView',
loaded: false,
require: () => require('../views/RoomActionsView').default
},
RoomFilesView: {
name: 'RoomFilesView',
loaded: false,
require: () => require('../views/RoomFilesView').default
},
RoomInfoEditView: {
name: 'RoomInfoEditView',
loaded: false,
require: () => require('../views/RoomInfoEditView').default
},
RoomInfoView: {
name: 'RoomInfoView',
loaded: false,
require: () => require('../views/RoomInfoView').default
},
RoomMembersView: {
name: 'RoomMembersView',
loaded: false,
require: () => require('../views/RoomMembersView').default
},
SearchMessagesView: {
name: 'SearchMessagesView',
loaded: false,
require: () => require('../views/SearchMessagesView').default
},
SnippetedMessagesView: {
name: 'SnippetedMessagesView',
loaded: false,
require: () => require('../views/SnippetedMessagesView').default
},
StarredMessagesView: {
name: 'StarredMessagesView',
loaded: false,
require: () => require('../views/StarredMessagesView').default
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params
})
);
}
export default {
navigate,
setTopLevelNavigator
};
this.isDrawerVisible = false;
Navigation.events().registerComponentDidAppearListener(({ componentId }) => {
if (componentId === DRAWER_ID) {
this.isDrawerVisible = true;
}
});
Navigation.events().registerComponentDidDisappearListener(({ componentId }) => {
if (componentId === DRAWER_ID) {
this.isDrawerVisible = false;
}
});
}
handleComponentName = (componentName) => {
if (!componentName) {
return console.error('componentName not found');
}
}
loadView = (componentName) => {
const view = this.views[componentName];
if (!view) {
return console.error('view not found');
}
if (!view.loaded) {
Navigation.registerComponentWithRedux(view.name, () => gestureHandlerRootHOC(view.require()), Provider, store);
view.loaded = true;
}
}
push = debounce((...args) => {
let componentName;
try {
componentName = args[1].component.name;
} catch (error) {
return console.error(error);
}
this.handleComponentName(componentName);
this.loadView(componentName);
Navigation.push(...args);
}, 300, true)
showModal = debounce((...args) => {
let componentName;
try {
componentName = args[0].stack.children[0].component.name;
} catch (error) {
return console.error(error);
}
this.handleComponentName(componentName);
this.loadView(componentName);
Navigation.showModal(...args);
}, 300, true)
pop = (...args) => Navigation.pop(...args);
popToRoot = (...args) => Navigation.popToRoot(...args);
dismissModal = (...args) => Navigation.dismissModal(...args);
dismissAllModals = (...args) => Navigation.dismissAllModals(...args);
events = (...args) => Navigation.events(...args);
mergeOptions = (...args) => Navigation.mergeOptions(...args);
setDefaultOptions = (...args) => Navigation.setDefaultOptions(...args);
setRoot = (...args) => Navigation.setRoot(...args);
setStackRoot = (...args) => Navigation.setStackRoot(...args);
toggleDrawer = () => {
try {
const visibility = !this.isDrawerVisible;
Navigation.mergeOptions(DRAWER_ID, {
sideMenu: {
left: {
visible: visibility
}
}
});
this.isDrawerVisible = visibility;
} catch (error) {
console.warn(error);
}
}
}
export default new NavigationManager();

View File

@ -1,5 +1,5 @@
import { createStore as reduxCreateStore, applyMiddleware, compose } from 'redux';
import Reactotron from 'reactotron-react-native' ; // eslint-disable-line
import Reactotron from 'reactotron-react-native';
import createSagaMiddleware from 'redux-saga';
import applyAppStateListener from 'redux-enhancer-react-native-appstate';

View File

@ -15,7 +15,6 @@ import {
} from '../actions/login';
import { disconnect, connectSuccess, connectRequest } from '../actions/connect';
import { setActiveUser } from '../actions/activeUsers';
import { snippetedMessagesReceived } from '../actions/snippetedMessages';
import { someoneTyping, roomMessageReceived } from '../actions/room';
import { setRoles } from '../actions/roles';
@ -213,27 +212,6 @@ const RocketChat = {
}
}));
this.sdk.onStreamData('rocketchat_snippeted_message', protectedFunction((ddpMessage) => {
if (ddpMessage.msg === 'added') {
this.snippetedMessages = this.snippetedMessages || [];
if (this.snippetedMessagesTimer) {
clearTimeout(this.snippetedMessagesTimer);
this.snippetedMessagesTimer = null;
}
this.snippetedMessagesTimer = setTimeout(() => {
reduxStore.dispatch(snippetedMessagesReceived(this.snippetedMessages));
this.snippetedMessagesTimer = null;
return this.snippetedMessages = [];
}, 1000);
const message = ddpMessage.fields;
message._id = ddpMessage.id;
const snippetedMessage = _buildMessage(message);
this.snippetedMessages = [...this.snippetedMessages, snippetedMessage];
}
}));
this.sdk.onStreamData('rocketchat_roles', protectedFunction((ddpMessage) => {
this.roles = this.roles || {};

View File

@ -110,7 +110,7 @@ const renderNumber = (unread, userMentions) => {
);
};
const attrs = ['name', 'unread', 'userMentions', 'alert', 'showLastMessage', 'type'];
const attrs = ['name', 'unread', 'userMentions', 'StoreLastMessage', 'alert', 'type'];
@connect(state => ({
user: {
id: state.login.user && state.login.user.id,
@ -128,7 +128,6 @@ export default class RoomItem extends React.Component {
StoreLastMessage: PropTypes.bool,
_updatedAt: PropTypes.string,
lastMessage: PropTypes.object,
showLastMessage: PropTypes.bool,
favorite: PropTypes.bool,
alert: PropTypes.bool,
unread: PropTypes.number,
@ -146,7 +145,6 @@ export default class RoomItem extends React.Component {
}
static defaultProps = {
showLastMessage: true,
avatarSize: 48
}
@ -174,10 +172,10 @@ export default class RoomItem extends React.Component {
get lastMessage() {
const {
lastMessage, type, showLastMessage, StoreLastMessage, user
lastMessage, type, StoreLastMessage, user
} = this.props;
if (!StoreLastMessage || !showLastMessage) {
if (!StoreLastMessage) {
return '';
}
if (!lastMessage) {

View File

@ -24,6 +24,7 @@ class PushNotification {
configure(params) {
this.onRegister = params.onRegister;
this.onNotification = params.onNotification;
NotificationsAndroid.refreshToken();
PendingNotifications.getInitialNotification()
.then((notification) => {

View File

@ -3,7 +3,6 @@ import { APP } from '../actions/actionsTypes';
const initialState = {
root: null,
stackRoot: 'RoomsListView',
ready: false,
inactive: false,
background: false
@ -37,11 +36,6 @@ export default function app(state = initialState, action) {
...state,
root: action.root
};
case APP.SET_STACK_ROOT:
return {
...state,
stackRoot: action.stackRoot
};
case APP.INIT:
return {
...state,

View File

@ -12,7 +12,6 @@ import app from './app';
import customEmojis from './customEmojis';
import activeUsers from './activeUsers';
import roles from './roles';
import snippetedMessages from './snippetedMessages';
import sortPreferences from './sortPreferences';
export default combineReducers({
@ -29,6 +28,5 @@ export default combineReducers({
customEmojis,
activeUsers,
roles,
snippetedMessages,
sortPreferences
});

View File

@ -1,30 +0,0 @@
import { SNIPPETED_MESSAGES } from '../actions/actionsTypes';
const initialState = {
messages: [],
ready: false
};
export default function server(state = initialState, action) {
switch (action.type) {
case SNIPPETED_MESSAGES.OPEN:
return {
...state,
ready: false
};
case SNIPPETED_MESSAGES.READY:
return {
...state,
ready: true
};
case SNIPPETED_MESSAGES.MESSAGES_RECEIVED:
return {
...state,
messages: [...state.messages, ...action.messages]
};
case SNIPPETED_MESSAGES.CLOSE:
return initialState;
default:
return state;
}
}

View File

@ -6,7 +6,6 @@ import {
import Navigation from '../lib/Navigation';
import * as types from '../actions/actionsTypes';
import { appStart, setStackRoot } from '../actions';
import { selectServerRequest } from '../actions/server';
import database from '../lib/realm';
import RocketChat from '../lib/rocketchat';
@ -16,40 +15,12 @@ const roomTypes = {
channel: 'c', direct: 'd', group: 'p'
};
const navigate = function* navigate({ params, sameServer = true }) {
if (!sameServer) {
yield put(appStart('inside'));
}
const navigate = function* navigate({ params }) {
if (params.rid) {
const canOpenRoom = yield RocketChat.canOpenRoom(params);
if (canOpenRoom) {
const stack = 'RoomsListView';
const stackRoot = yield select(state => state.app.stackRoot);
// Make sure current stack is RoomsListView before navigate to RoomView
if (stackRoot !== stack) {
yield Navigation.setStackRoot('AppRoot', {
component: {
id: stack,
name: stack
}
});
yield put(setStackRoot(stack));
}
try {
yield Navigation.popToRoot(stack);
} catch (error) {
console.log(error);
}
const [type, name] = params.path.split('/');
Navigation.push(stack, {
component: {
name: 'RoomView',
passProps: {
rid: params.rid, name, t: roomTypes[type]
}
}
});
Navigation.navigate('RoomView', { rid: params.rid, name, t: roomTypes[type] });
}
}
};
@ -100,9 +71,13 @@ const handleOpen = function* handleOpen({ params }) {
const servers = yield database.databases.serversDB.objects('servers').filtered('id = $0', host); // TODO: need better test
if (servers.length && user) {
yield put(selectServerRequest(host));
yield navigate({ params, sameServer: false });
yield race({
typing: take(types.SERVER.SELECT_SUCCESS),
timeout: delay(3000)
});
yield navigate({ params });
} else {
yield put(appStart('outside'));
Navigation.navigate('OnboardingView', { previousServer: server });
yield delay(1000);
EventEmitter.emit('NewServer', { server: host });
}

View File

@ -7,7 +7,6 @@ import selectServer from './selectServer';
import createChannel from './createChannel';
import init from './init';
import state from './state';
import snippetedMessages from './snippetedMessages';
import deepLinking from './deepLinking';
const root = function* root() {
@ -20,7 +19,6 @@ const root = function* root() {
messages(),
selectServer(),
state(),
snippetedMessages(),
deepLinking()
]);
};

View File

@ -1,5 +1,6 @@
import { AsyncStorage } from 'react-native';
import { put, takeLatest, all } from 'redux-saga/effects';
import SplashScreen from 'react-native-splash-screen';
import * as actions from '../actions';
import { selectServerRequest } from '../actions/server';
@ -7,6 +8,7 @@ import { setAllPreferences } from '../actions/sortPreferences';
import { APP } from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import log from '../utils/log';
import Navigation from '../lib/Navigation';
const restore = function* restore() {
try {
@ -34,7 +36,19 @@ const restore = function* restore() {
}
};
const start = function* start({ root }) {
if (root === 'inside') {
yield Navigation.navigate('InsideStack');
} else if (root === 'setUsername') {
yield Navigation.navigate('SetUsernameView');
} else if (root === 'outside') {
yield Navigation.navigate('OutsideStack');
}
SplashScreen.hide();
};
const root = function* root() {
yield takeLatest(APP.INIT, restore);
yield takeLatest(APP.START, start);
};
export default root;

View File

@ -3,7 +3,6 @@ import {
put, call, takeLatest, select
} from 'redux-saga/effects';
import Navigation from '../lib/Navigation';
import * as types from '../actions/actionsTypes';
import { appStart } from '../actions';
import { serverFinishAdd, selectServerRequest } from '../actions/server';
@ -50,7 +49,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
yield put(appStart('setUsername'));
} else if (adding) {
yield put(serverFinishAdd());
yield Navigation.dismissAllModals();
yield put(appStart('inside'));
} else {
yield put(appStart('inside'));
}

View File

@ -74,16 +74,9 @@ const handleTogglePinRequest = function* handleTogglePinRequest({ message }) {
}
};
const goRoom = function* goRoom({ rid, name }) {
yield Navigation.popToRoot('RoomsListView');
Navigation.push('RoomsListView', {
component: {
name: 'RoomView',
passProps: {
rid, name, t: 'd'
}
}
});
const goRoom = function goRoom({ rid, name }) {
Navigation.navigate('RoomsListView');
Navigation.navigate('RoomView', { rid, name, t: 'd' });
};
const handleReplyBroadcast = function* handleReplyBroadcast({ message }) {

View File

@ -124,7 +124,7 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
try {
const result = yield RocketChat.leaveRoom(rid, t);
if (result.success) {
yield Navigation.popToRoot('RoomsListView');
yield Navigation.navigate('RoomsListView');
}
} catch (e) {
if (e.data && e.data.errorType === 'error-you-are-last-owner') {
@ -139,7 +139,7 @@ const handleEraseRoom = function* handleEraseRoom({ rid, t }) {
try {
const result = yield RocketChat.eraseRoom(rid, t);
if (result.success) {
yield Navigation.popToRoot('RoomsListView');
yield Navigation.navigate('RoomsListView');
}
} catch (e) {
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('erasing_room') }));

View File

@ -53,17 +53,9 @@ const handleServerRequest = function* handleServerRequest({ server }) {
const loginServicesLength = yield RocketChat.getLoginServices(server);
if (loginServicesLength === 0) {
yield Navigation.push('NewServerView', {
component: {
name: 'LoginView'
}
});
Navigation.navigate('LoginView');
} else {
yield Navigation.push('NewServerView', {
component: {
name: 'LoginSignupView'
}
});
Navigation.navigate('LoginSignupView');
}
database.databases.serversDB.write(() => {

View File

@ -1,41 +0,0 @@
import { put, takeLatest } from 'redux-saga/effects';
import * as types from '../actions/actionsTypes';
import RocketChat from '../lib/rocketchat';
import { readySnippetedMessages } from '../actions/snippetedMessages';
import log from '../utils/log';
let sub;
let newSub;
const openSnippetedMessagesRoom = function* openSnippetedMessagesRoom({ rid, limit }) {
try {
newSub = yield RocketChat.subscribe('snippetedMessages', rid, limit);
yield put(readySnippetedMessages());
if (sub) {
sub.unsubscribe().catch(err => console.warn(err));
}
sub = newSub;
} catch (e) {
log('openSnippetedMessagesRoom', e);
}
};
const closeSnippetedMessagesRoom = function* closeSnippetedMessagesRoom() {
try {
if (sub) {
yield sub.unsubscribe();
}
if (newSub) {
yield newSub.unsubscribe();
}
} catch (e) {
log('closeSnippetedMessagesRoom', e);
}
};
const root = function* root() {
yield takeLatest(types.SNIPPETED_MESSAGES.OPEN, openSnippetedMessagesRoom);
yield takeLatest(types.SNIPPETED_MESSAGES.CLOSE, closeSnippetedMessagesRoom);
};
export default root;

View File

@ -0,0 +1,38 @@
import React from 'react';
import { StyleSheet, Image } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import StatusBar from '../containers/StatusBar';
import { isAndroid } from '../utils/deviceInfo';
import { appInit as appInitAction } from '../actions';
const styles = StyleSheet.create({
image: {
width: '100%',
height: '100%'
}
});
@connect(null, dispatch => ({
appInit: () => dispatch(appInitAction())
}))
export default class Loading extends React.PureComponent {
static propTypes = {
appInit: PropTypes.func
}
constructor(props) {
super(props);
props.appInit();
}
render() {
return (
<React.Fragment>
<StatusBar />
{isAndroid ? <Image source={{ uri: 'launch_screen' }} style={styles.image} /> : null}
</React.Fragment>
);
}
}

View File

@ -4,10 +4,9 @@ import PropTypes from 'prop-types';
import {
View, Text, Switch, ScrollView, TextInput, StyleSheet, FlatList
} from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import Navigation from '../lib/Navigation';
import Loading from '../containers/Loading';
import LoggedView from './View';
import { createChannelRequest as createChannelRequestAction } from '../actions/createChannel';
@ -19,6 +18,8 @@ import I18n from '../i18n';
import UserItem from '../presentation/UserItem';
import { showErrorAlert } from '../utils/info';
import { isAndroid } from '../utils/deviceInfo';
import { CustomHeaderButtons, Item } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
const styles = StyleSheet.create({
container: {
@ -91,18 +92,25 @@ const styles = StyleSheet.create({
}))
/** @extends React.Component */
export default class CreateChannelView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const submit = navigation.getParam('submit', () => {});
const showSubmit = navigation.getParam('showSubmit');
return {
topBar: {
title: {
text: I18n.t('Create_Channel')
}
}
title: I18n.t('Create_Channel'),
headerRight: (
showSubmit
? (
<CustomHeaderButtons>
<Item title={I18n.t('Create')} onPress={submit} testID='create-channel-submit' />
</CustomHeaderButtons>
)
: null
)
};
}
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
baseUrl: PropTypes.string,
create: PropTypes.func.isRequired,
removeUser: PropTypes.func.isRequired,
@ -125,10 +133,11 @@ export default class CreateChannelView extends LoggedView {
readOnly: false,
broadcast: false
};
Navigation.events().bindComponent(this);
}
componentDidMount() {
const { navigation } = this.props;
navigation.setParams({ submit: this.submit });
this.timeout = setTimeout(() => {
this.channelNameRef.focus();
}, 600);
@ -173,26 +182,18 @@ export default class CreateChannelView extends LoggedView {
componentDidUpdate(prevProps) {
const {
isFetching, failure, error, result, componentId
isFetching, failure, error, result, navigation
} = this.props;
if (!isFetching && isFetching !== prevProps.isFetching) {
setTimeout(async() => {
setTimeout(() => {
if (failure) {
const msg = error.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') });
showErrorAlert(msg);
} else {
const { type } = this.state;
const { rid, name } = result;
await Navigation.dismissModal(componentId);
Navigation.push('RoomsListView', {
component: {
name: 'RoomView',
passProps: {
rid, name, t: type ? 'p' : 'c'
}
}
});
navigation.navigate('RoomView', { rid, name, t: type ? 'p' : 'c' });
}
}, 300);
}
@ -205,30 +206,11 @@ export default class CreateChannelView extends LoggedView {
}
onChangeText = (channelName) => {
const { componentId } = this.props;
const rightButtons = [];
if (channelName.trim().length > 0) {
rightButtons.push({
id: 'create',
text: 'Create',
testID: 'create-channel-submit',
color: isAndroid ? '#FFF' : undefined
});
}
Navigation.mergeOptions(componentId, {
topBar: {
rightButtons
}
});
const { navigation } = this.props;
navigation.setParams({ showSubmit: channelName.trim().length > 0 });
this.setState({ channelName });
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'create') {
this.submit();
}
}
submit = () => {
const {
channelName, type, readOnly, broadcast
@ -354,6 +336,7 @@ export default class CreateChannelView extends LoggedView {
contentContainerStyle={[sharedStyles.container, styles.container]}
keyboardVerticalOffset={128}
>
<StatusBar />
<SafeAreaView testID='create-channel-view' style={styles.container} forceInset={{ bottom: 'never' }}>
<ScrollView {...scrollPersistTaps}>
<View style={sharedStyles.separatorVertical}>

View File

@ -1,9 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Text, ScrollView } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import PropTypes from 'prop-types';
import Navigation from '../lib/Navigation';
import LoggedView from './View';
import KeyboardView from '../presentation/KeyboardView';
import TextInput from '../containers/TextInput';
@ -13,19 +12,20 @@ import { showErrorAlert } from '../utils/info';
import isValidEmail from '../utils/isValidEmail';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import I18n from '../i18n';
import { DARK_HEADER } from '../constants/headerOptions';
import RocketChat from '../lib/rocketchat';
import StatusBar from '../containers/StatusBar';
/** @extends React.Component */
export default class ForgotPasswordView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const title = navigation.getParam('title', 'Rocket.Chat');
return {
...DARK_HEADER
title
};
}
static propTypes = {
componentId: PropTypes.string
navigation: PropTypes.object
}
constructor(props) {
@ -81,8 +81,8 @@ export default class ForgotPasswordView extends LoggedView {
this.setState({ isFetching: true });
const result = await RocketChat.forgotPassword(email);
if (result.success) {
const { componentId } = this.props;
Navigation.pop(componentId);
const { navigation } = this.props;
navigation.pop();
showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert'));
}
} catch (e) {
@ -100,6 +100,7 @@ export default class ForgotPasswordView extends LoggedView {
contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128}
>
<StatusBar />
<ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
<SafeAreaView style={sharedStyles.container} testID='forgot-password-view' forceInset={{ bottom: 'never' }}>
<Text style={[sharedStyles.loginTitle, sharedStyles.textBold]}>{I18n.t('Forgot_password')}</Text>

View File

@ -3,18 +3,16 @@ import PropTypes from 'prop-types';
import {
Text, ScrollView, View, StyleSheet
} from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import { RectButton } from 'react-native-gesture-handler';
import Navigation from '../lib/Navigation';
import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import { isIOS, isAndroid } from '../utils/deviceInfo';
import LoggedView from './View';
import I18n from '../i18n';
import { DARK_HEADER } from '../constants/headerOptions';
import Icons from '../lib/Icons';
import DisclosureIndicator from '../containers/DisclosureIndicator';
import { CloseModalButton } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
const styles = StyleSheet.create({
container: {
@ -55,48 +53,22 @@ const Separator = () => <View style={styles.separator} />;
/** @extends React.Component */
export default class LegalView extends LoggedView {
static options() {
return {
...DARK_HEADER,
topBar: {
...DARK_HEADER.topBar,
title: {
...DARK_HEADER.topBar.title,
text: I18n.t('Legal')
},
leftButtons: [{
id: 'close',
icon: isAndroid ? Icons.getSource('close') : undefined,
text: isIOS ? I18n.t('Close') : undefined,
testID: 'legal-view-close'
}]
}
};
}
static navigationOptions = ({ navigation }) => ({
headerLeft: <CloseModalButton testID='legal-view-close' navigation={navigation} />,
title: I18n.t('Legal')
})
static propTypes = {
componentId: PropTypes.string
navigation: PropTypes.object
}
constructor(props) {
super('LegalView', props);
Navigation.events().bindComponent(this);
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'close') {
const { componentId } = this.props;
Navigation.dismissModal(componentId);
}
}
onPressItem = ({ route }) => {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
name: route
}
});
const { navigation } = this.props;
navigation.navigate(route);
}
renderItem = ({ text, route, testID }) => (
@ -109,6 +81,7 @@ export default class LegalView extends LoggedView {
render() {
return (
<SafeAreaView style={styles.container} testID='legal-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<ScrollView {...scrollPersistTaps} contentContainerStyle={styles.scroll}>
{this.renderItem({ text: 'Terms_of_Service', route: 'TermsServiceView', testID: 'legal-terms-button' })}
<Separator />

View File

@ -5,19 +5,18 @@ import {
} from 'react-native';
import { connect } from 'react-redux';
import { Base64 } from 'js-base64';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import { RectButton, BorderlessButton } from 'react-native-gesture-handler';
import equal from 'deep-equal';
import Navigation from '../lib/Navigation';
import LoggedView from './View';
import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import random from '../utils/random';
import Button from '../containers/Button';
import I18n from '../i18n';
import { DARK_HEADER } from '../constants/headerOptions';
import Icons from '../lib/Icons';
import { LegalButton } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
const styles = StyleSheet.create({
container: {
@ -96,22 +95,16 @@ const SERVICES_COLLAPSED_HEIGHT = 174;
}))
/** @extends React.Component */
export default class LoginSignupView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const title = navigation.getParam('title', 'Rocket.Chat');
return {
...DARK_HEADER,
topBar: {
...DARK_HEADER.topBar,
rightButtons: [{
id: 'more',
icon: Icons.getSource('more'),
testID: 'welcome-view-more'
}]
}
title,
headerRight: <LegalButton testID='welcome-view-more' navigation={navigation} />
};
}
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
server: PropTypes.string,
services: PropTypes.object,
Site_Name: PropTypes.string
@ -123,9 +116,8 @@ export default class LoginSignupView extends LoggedView {
collapsed: true,
servicesHeight: new Animated.Value(SERVICES_COLLAPSED_HEIGHT)
};
Navigation.events().bindComponent(this);
const { componentId, Site_Name } = this.props;
this.setTitle(componentId, Site_Name);
const { Site_Name } = this.props;
this.setTitle(Site_Name);
}
shouldComponentUpdate(nextProps, nextState) {
@ -150,34 +142,15 @@ export default class LoginSignupView extends LoggedView {
}
componentDidUpdate(prevProps) {
const { componentId, Site_Name } = this.props;
const { Site_Name } = this.props;
if (Site_Name && prevProps.Site_Name !== Site_Name) {
this.setTitle(componentId, Site_Name);
this.setTitle(Site_Name);
}
}
setTitle = (componentId, title) => {
Navigation.mergeOptions(componentId, {
topBar: {
title: {
text: title
}
}
});
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'more') {
Navigation.showModal({
stack: {
children: [{
component: {
name: 'LegalView'
}
}]
}
});
}
setTitle = (title) => {
const { navigation } = this.props;
navigation.setParams({ title });
}
onPressFacebook = () => {
@ -258,57 +231,18 @@ export default class LoginSignupView extends LoggedView {
}
openOAuth = (oAuthUrl) => {
Navigation.showModal({
stack: {
children: [{
component: {
name: 'OAuthView',
passProps: {
oAuthUrl
},
options: {
topBar: {
title: {
text: 'OAuth'
}
}
}
}
}]
}
});
const { navigation } = this.props;
navigation.navigate('OAuthView', { oAuthUrl });
}
login = () => {
const { componentId, Site_Name } = this.props;
Navigation.push(componentId, {
component: {
name: 'LoginView',
options: {
topBar: {
title: {
text: Site_Name
}
}
}
}
});
const { navigation, Site_Name } = this.props;
navigation.navigate('LoginView', { title: Site_Name });
}
register = () => {
const { componentId, Site_Name } = this.props;
Navigation.push(componentId, {
component: {
name: 'RegisterView',
options: {
topBar: {
title: {
text: Site_Name
}
}
}
}
});
const { navigation, Site_Name } = this.props;
navigation.navigate('RegisterView', { title: Site_Name });
}
transitionServicesTo = (height) => {
@ -428,6 +362,7 @@ export default class LoginSignupView extends LoggedView {
render() {
return (
<ScrollView style={[sharedStyles.containerScrollView, sharedStyles.container, styles.container]} {...scrollPersistTaps}>
<StatusBar />
<SafeAreaView testID='welcome-view' forceInset={{ bottom: 'never' }} style={styles.safeArea}>
{this.renderServices()}
{this.renderServicesSeparator()}

View File

@ -5,10 +5,9 @@ import {
} from 'react-native';
import { connect } from 'react-redux';
import { Answers } from 'react-native-fabric';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import Navigation from '../lib/Navigation';
import KeyboardView from '../presentation/KeyboardView';
import TextInput from '../containers/TextInput';
import Button from '../containers/Button';
@ -16,9 +15,9 @@ import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import LoggedView from './View';
import I18n from '../i18n';
import { DARK_HEADER } from '../constants/headerOptions';
import { loginRequest as loginRequestAction } from '../actions/login';
import Icons from '../lib/Icons';
import { LegalButton } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
const styles = StyleSheet.create({
buttonsContainer: {
@ -58,22 +57,16 @@ const styles = StyleSheet.create({
}))
/** @extends React.Component */
export default class LoginView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const title = navigation.getParam('title', 'Rocket.Chat');
return {
...DARK_HEADER,
topBar: {
...DARK_HEADER.topBar,
rightButtons: [{
id: 'more',
icon: Icons.getSource('more'),
testID: 'login-view-more'
}]
}
title,
headerRight: <LegalButton navigation={navigation} testID='login-view-more' />
};
}
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
loginRequest: PropTypes.func.isRequired,
error: PropTypes.object,
Site_Name: PropTypes.string,
@ -91,9 +84,8 @@ export default class LoginView extends LoggedView {
code: '',
showTOTP: false
};
Navigation.events().bindComponent(this);
const { componentId, Site_Name } = this.props;
this.setTitle(componentId, Site_Name);
const { Site_Name } = this.props;
this.setTitle(Site_Name);
}
componentDidMount() {
@ -103,9 +95,9 @@ export default class LoginView extends LoggedView {
}
componentWillReceiveProps(nextProps) {
const { componentId, Site_Name, error } = this.props;
const { Site_Name, error } = this.props;
if (Site_Name && nextProps.Site_Name !== Site_Name) {
this.setTitle(componentId, nextProps.Site_Name);
this.setTitle(nextProps.Site_Name);
} else if (nextProps.failure && !equal(error, nextProps.error)) {
if (nextProps.error && nextProps.error.error === 'totp-required') {
LayoutAnimation.easeInEaseOut();
@ -167,28 +159,9 @@ export default class LoginView extends LoggedView {
}
}
setTitle = (componentId, title) => {
Navigation.mergeOptions(componentId, {
topBar: {
title: {
text: title
}
}
});
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'more') {
Navigation.showModal({
stack: {
children: [{
component: {
name: 'LegalView'
}
}]
}
});
}
setTitle = (title) => {
const { navigation } = this.props;
navigation.setParams({ title });
}
valid = () => {
@ -214,35 +187,13 @@ export default class LoginView extends LoggedView {
}
register = () => {
const { componentId, Site_Name } = this.props;
Navigation.push(componentId, {
component: {
name: 'RegisterView',
options: {
topBar: {
title: {
text: Site_Name
}
}
}
}
});
const { navigation, Site_Name } = this.props;
navigation.navigate('RegisterView', { title: Site_Name });
}
forgotPassword = () => {
const { componentId, Site_Name } = this.props;
Navigation.push(componentId, {
component: {
name: 'ForgotPasswordView',
options: {
topBar: {
title: {
text: Site_Name
}
}
}
}
});
const { navigation, Site_Name } = this.props;
navigation.navigate('ForgotPasswordView', { title: Site_Name });
}
renderTOTP = () => {
@ -336,6 +287,7 @@ export default class LoginView extends LoggedView {
keyboardVerticalOffset={128}
key='login-view'
>
<StatusBar />
<ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
{!showTOTP ? this.renderUserForm() : null}
{showTOTP ? this.renderTOTP() : null}

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import LoggedView from '../View';
@ -11,6 +11,7 @@ import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import RocketChat from '../../lib/rocketchat';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
@ -24,14 +25,8 @@ import RocketChat from '../../lib/rocketchat';
}))
/** @extends React.Component */
export default class MentionedMessagesView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Mentions')
}
}
};
static navigationOptions = {
title: I18n.t('Mentions')
}
static propTypes = {
@ -42,7 +37,7 @@ export default class MentionedMessagesView extends LoggedView {
}
constructor(props) {
super('StarredMessagesView', props);
super('MentionedMessagesView', props);
this.state = {
loading: false,
messages: []
@ -130,6 +125,7 @@ export default class MentionedMessagesView extends LoggedView {
return (
<SafeAreaView style={styles.list} testID='mentioned-messages-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -4,10 +4,9 @@ import {
View, StyleSheet, FlatList, Text
} from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import Navigation from '../lib/Navigation';
import database from '../lib/realm';
import RocketChat from '../lib/rocketchat';
import UserItem from '../presentation/UserItem';
@ -16,9 +15,11 @@ import LoggedView from './View';
import sharedStyles from './Styles';
import I18n from '../i18n';
import Touch from '../utils/touch';
import { isIOS, isAndroid } from '../utils/deviceInfo';
import { isIOS } from '../utils/deviceInfo';
import SearchBox from '../containers/SearchBox';
import Icons, { CustomIcon } from '../lib/Icons';
import { CustomIcon } from '../lib/Icons';
import { CloseModalButton } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
const styles = StyleSheet.create({
safeAreaView: {
@ -56,22 +57,14 @@ const styles = StyleSheet.create({
}))
/** @extends React.Component */
export default class NewMessageView extends LoggedView {
static options() {
return {
topBar: {
leftButtons: [{
id: 'cancel',
icon: isAndroid ? Icons.getSource('close') : undefined,
text: isIOS ? I18n.t('Cancel') : undefined
}]
}
};
}
static navigationOptions = ({ navigation }) => ({
headerLeft: <CloseModalButton navigation={navigation} testID='new-message-view-close' />,
title: I18n.t('New_Message')
})
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
baseUrl: PropTypes.string,
onPressItem: PropTypes.func.isRequired,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
@ -85,7 +78,6 @@ export default class NewMessageView extends LoggedView {
search: []
};
this.data.addListener(this.updateState);
Navigation.events().bindComponent(this);
}
shouldComponentUpdate(nextProps, nextState) {
@ -105,21 +97,15 @@ export default class NewMessageView extends LoggedView {
this.search(text);
}
onPressItem = async(item) => {
const { onPressItem } = this.props;
await this.dismiss();
onPressItem = (item) => {
const { navigation } = this.props;
const onPressItem = navigation.getParam('onPressItem', () => {});
onPressItem(item);
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'cancel') {
this.dismiss();
}
}
dismiss = () => {
const { componentId } = this.props;
return Navigation.dismissModal(componentId);
const { navigation } = this.props;
return navigation.pop();
}
// eslint-disable-next-line react/sort-comp
@ -135,22 +121,8 @@ export default class NewMessageView extends LoggedView {
}
createChannel = () => {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
name: 'SelectedUsersView',
passProps: {
nextAction: 'CREATE_CHANNEL'
},
options: {
topBar: {
title: {
text: I18n.t('Select_Users')
}
}
}
}
});
const { navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', { nextActionID: 'CREATE_CHANNEL', title: I18n.t('Select_Users') });
}
renderHeader = () => (
@ -211,6 +183,7 @@ export default class NewMessageView extends LoggedView {
render = () => (
<SafeAreaView style={styles.safeAreaView} testID='new-message-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
{this.renderList()}
</SafeAreaView>
);

View File

@ -4,9 +4,8 @@ import {
Text, ScrollView, Keyboard, Image, StyleSheet, TouchableOpacity
} from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import Navigation from '../lib/Navigation';
import { serverRequest } from '../actions/server';
import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
@ -17,8 +16,9 @@ import I18n from '../i18n';
import { verticalScale, moderateScale } from '../utils/scaling';
import KeyboardView from '../presentation/KeyboardView';
import { isIOS, isNotch } from '../utils/deviceInfo';
import { LIGHT_HEADER } from '../constants/headerOptions';
// import { LIGHT_HEADER } from '../constants/headerOptions';
import { CustomIcon } from '../lib/Icons';
import StatusBar from '../containers/StatusBar';
const styles = StyleSheet.create({
image: {
@ -64,18 +64,12 @@ const defaultServer = 'https://open.rocket.chat';
}))
/** @extends React.Component */
export default class NewServerView extends LoggedView {
static options() {
return {
...LIGHT_HEADER,
topBar: {
visible: false,
drawBehind: true
}
};
}
static navigationOptions = () => ({
header: null
})
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
server: PropTypes.string,
connecting: PropTypes.bool.isRequired,
connectServer: PropTypes.func.isRequired
@ -86,11 +80,11 @@ export default class NewServerView extends LoggedView {
this.state = {
text: ''
};
Navigation.events().bindComponent(this);
}
componentDidMount() {
const { server, connectServer } = this.props;
const { navigation, connectServer } = this.props;
const server = navigation.getParam('server');
if (server) {
connectServer(server);
this.setState({ text: server });
@ -153,7 +147,7 @@ export default class NewServerView extends LoggedView {
}
renderBack = () => {
const { componentId } = this.props;
const { navigation } = this.props;
let top = 15;
if (isIOS) {
@ -163,7 +157,7 @@ export default class NewServerView extends LoggedView {
return (
<TouchableOpacity
style={[styles.backButton, { top }]}
onPress={() => Navigation.pop(componentId)}
onPress={() => navigation.pop()}
>
<CustomIcon
name='back'
@ -183,6 +177,7 @@ export default class NewServerView extends LoggedView {
keyboardVerticalOffset={128}
key='login-view'
>
<StatusBar light />
<ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
<SafeAreaView style={sharedStyles.container} testID='new-server-view' forceInset={{ bottom: 'never' }}>
<Image style={styles.image} source={{ uri: 'new_server' }} />

View File

@ -3,12 +3,10 @@ import PropTypes from 'prop-types';
import { WebView } from 'react-native';
import { connect } from 'react-redux';
import Navigation from '../lib/Navigation';
import RocketChat from '../lib/rocketchat';
import I18n from '../i18n';
import { DARK_HEADER } from '../constants/headerOptions';
import { isIOS, isAndroid } from '../utils/deviceInfo';
import Icons from '../lib/Icons';
import { isIOS } from '../utils/deviceInfo';
import { CloseModalButton } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
const userAgentAndroid = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1';
const userAgent = isIOS ? 'UserAgent' : userAgentAndroid;
@ -17,23 +15,13 @@ const userAgent = isIOS ? 'UserAgent' : userAgentAndroid;
server: state.server.server
}))
export default class OAuthView extends React.PureComponent {
static options() {
return {
...DARK_HEADER,
topBar: {
...DARK_HEADER.topBar,
leftButtons: [{
id: 'cancel',
icon: isAndroid ? Icons.getSource('close') : undefined,
text: isIOS ? I18n.t('Cancel') : undefined
}]
}
};
}
static navigationOptions = ({ navigation }) => ({
headerLeft: <CloseModalButton navigation={navigation} />,
title: 'OAuth'
})
static propTypes = {
componentId: PropTypes.string,
oAuthUrl: PropTypes.string,
navigation: PropTypes.object,
server: PropTypes.string
}
@ -43,18 +31,11 @@ export default class OAuthView extends React.PureComponent {
logging: false
};
this.redirectRegex = new RegExp(`(?=.*(${ props.server }))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
Navigation.events().bindComponent(this);
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'cancel') {
this.dismiss();
}
}
dismiss = () => {
const { componentId } = this.props;
Navigation.dismissModal(componentId);
const { navigation } = this.props;
navigation.pop();
}
login = async(params) => {
@ -75,8 +56,11 @@ export default class OAuthView extends React.PureComponent {
}
render() {
const { oAuthUrl } = this.props;
const { navigation } = this.props;
const oAuthUrl = navigation.getParam('oAuthUrl');
return (
<React.Fragment>
<StatusBar />
<WebView
source={{ uri: oAuthUrl }}
userAgent={userAgent}
@ -89,6 +73,7 @@ export default class OAuthView extends React.PureComponent {
}
}}
/>
</React.Fragment>
);
}
}

View File

@ -4,7 +4,8 @@ import {
} from 'react-native';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import Orientation from 'react-native-orientation-locker';
import { selectServerRequest, serverInitAdd, serverFinishAdd } from '../../actions/server';
import { appStart as appStartAction } from '../../actions';
@ -15,9 +16,8 @@ import styles from './styles';
import LoggedView from '../View';
import { isIOS, isNotch } from '../../utils/deviceInfo';
import EventEmitter from '../../utils/events';
import { LIGHT_HEADER } from '../../constants/headerOptions';
import Navigation from '../../lib/Navigation';
import { CustomIcon } from '../../lib/Icons';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
currentServer: state.server.server,
@ -26,23 +26,16 @@ import { CustomIcon } from '../../lib/Icons';
initAdd: () => dispatch(serverInitAdd()),
finishAdd: () => dispatch(serverFinishAdd()),
selectServer: server => dispatch(selectServerRequest(server)),
appStart: () => dispatch(appStartAction())
appStart: root => dispatch(appStartAction(root))
}))
/** @extends React.Component */
export default class OnboardingView extends LoggedView {
static options() {
return {
...LIGHT_HEADER,
topBar: {
visible: false,
drawBehind: true
}
};
}
static navigationOptions = () => ({
header: null
})
static propTypes = {
componentId: PropTypes.string,
previousServer: PropTypes.string,
navigation: PropTypes.object,
adding: PropTypes.bool,
selectServer: PropTypes.func.isRequired,
currentServer: PropTypes.string,
@ -54,11 +47,13 @@ export default class OnboardingView extends LoggedView {
constructor(props) {
super('OnboardingView', props);
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
this.previousServer = props.navigation.getParam('previousServer');
Orientation.lockToPortrait();
}
componentDidMount() {
const { previousServer, initAdd } = this.props;
if (previousServer) {
const { initAdd } = this.props;
if (this.previousServer) {
initAdd();
}
EventEmitter.addEventListener('NewServer', this.handleNewServerEvent);
@ -70,11 +65,11 @@ export default class OnboardingView extends LoggedView {
componentWillUnmount() {
const {
selectServer, previousServer, currentServer, adding, finishAdd
selectServer, currentServer, adding, finishAdd
} = this.props;
if (adding) {
if (previousServer !== currentServer) {
selectServer(previousServer);
if (this.previousServer !== currentServer) {
selectServer(this.previousServer);
}
finishAdd();
}
@ -89,26 +84,13 @@ export default class OnboardingView extends LoggedView {
}
close = () => {
const { componentId } = this.props;
Navigation.dismissModal(componentId);
const { appStart } = this.props;
appStart('inside');
}
newServer = (server) => {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
id: 'NewServerView',
name: 'NewServerView',
passProps: {
server
},
options: {
topBar: {
visible: false
}
}
}
});
const { navigation } = this.props;
navigation.navigate('NewServerView', { server });
}
handleNewServerEvent = (event) => {
@ -129,9 +111,7 @@ export default class OnboardingView extends LoggedView {
}
renderClose = () => {
const { previousServer } = this.props;
if (previousServer) {
if (this.previousServer) {
let top = 15;
if (isIOS) {
top = isNotch ? 45 : 30;
@ -156,6 +136,7 @@ export default class OnboardingView extends LoggedView {
render() {
return (
<SafeAreaView style={styles.container} testID='onboarding-view' forceInset={{ bottom: 'never' }}>
<StatusBar light />
<Image style={styles.onboarding} source={{ uri: 'onboarding' }} fadeDuration={0} />
<Text style={styles.title}>{I18n.t('Welcome_to_RocketChat')}</Text>
<Text style={styles.subtitle}>{I18n.t('Open_Source_Communication')}</Text>

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux';
import ActionSheet from 'react-native-action-sheet';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import LoggedView from '../View';
@ -12,6 +12,7 @@ import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import RocketChat from '../../lib/rocketchat';
import StatusBar from '../../containers/StatusBar';
const PIN_INDEX = 0;
const CANCEL_INDEX = 1;
@ -29,14 +30,8 @@ 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 navigationOptions = {
title: I18n.t('Pinned')
}
static propTypes = {
@ -169,6 +164,7 @@ export default class PinnedMessagesView extends LoggedView {
return (
<SafeAreaView style={styles.list} testID='pinned-messages-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -2,29 +2,20 @@ import React from 'react';
import PropTypes from 'prop-types';
import { WebView } from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import styles from './Styles';
import LoggedView from './View';
import { DARK_HEADER } from '../constants/headerOptions';
import I18n from '../i18n';
import StatusBar from '../containers/StatusBar';
@connect(state => ({
privacyPolicy: state.settings.Layout_Privacy_Policy
}))
/** @extends React.Component */
export default class PrivacyPolicyView extends LoggedView {
static options() {
return {
...DARK_HEADER,
topBar: {
...DARK_HEADER.topBar,
title: {
...DARK_HEADER.topBar.title,
text: I18n.t('Privacy_Policy')
}
}
};
static navigationOptions = {
title: I18n.t('Privacy_Policy')
}
static propTypes = {
@ -40,6 +31,7 @@ export default class PrivacyPolicyView extends LoggedView {
return (
<SafeAreaView style={styles.container} testID='privacy-view'>
<StatusBar />
<WebView originWhitelist={['*']} source={{ html: privacyPolicy, baseUrl: '' }} />
</SafeAreaView>
);

View File

@ -6,7 +6,7 @@ import Dialog from 'react-native-dialog';
import SHA256 from 'js-sha256';
import ImagePicker from 'react-native-image-crop-picker';
import RNPickerSelect from 'react-native-picker-select';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import LoggedView from '../View';
@ -24,6 +24,8 @@ import Avatar from '../../containers/Avatar';
import Touch from '../../utils/touch';
import { setUser as setUserAction } from '../../actions/login';
import { CustomIcon } from '../../lib/Icons';
import { DrawerButton } from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
user: {
@ -41,15 +43,10 @@ import { CustomIcon } from '../../lib/Icons';
}))
/** @extends React.Component */
export default class ProfileView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Profile')
}
}
};
}
static navigationOptions = ({ navigation }) => ({
headerLeft: <DrawerButton navigation={navigation} />,
title: I18n.t('Profile')
})
static propTypes = {
baseUrl: PropTypes.string,
@ -388,6 +385,7 @@ export default class ProfileView extends LoggedView {
contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128}
>
<StatusBar />
<ScrollView
contentContainerStyle={sharedStyles.containerScrollView}
testID='profile-view-list'

View File

@ -4,9 +4,8 @@ import {
Keyboard, Text, ScrollView, Alert
} from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import Navigation from '../lib/Navigation';
import TextInput from '../containers/TextInput';
import Button from '../containers/Button';
import KeyboardView from '../presentation/KeyboardView';
@ -14,11 +13,11 @@ import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import LoggedView from './View';
import I18n from '../i18n';
import { DARK_HEADER } from '../constants/headerOptions';
import RocketChat from '../lib/rocketchat';
import { loginRequest as loginRequestAction } from '../actions/login';
import isValidEmail from '../utils/isValidEmail';
import Icons from '../lib/Icons';
import { LegalButton } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
const shouldUpdateState = ['name', 'email', 'password', 'username', 'saving'];
@ -27,22 +26,16 @@ const shouldUpdateState = ['name', 'email', 'password', 'username', 'saving'];
}))
/** @extends React.Component */
export default class RegisterView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const title = navigation.getParam('title', 'Rocket.Chat');
return {
...DARK_HEADER,
topBar: {
...DARK_HEADER.topBar,
rightButtons: [{
id: 'more',
icon: Icons.getSource('more'),
testID: 'register-view-more'
}]
}
title,
headerRight: <LegalButton testID='register-view-more' navigation={navigation} />
};
}
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
loginRequest: PropTypes.func,
Site_Name: PropTypes.string
}
@ -56,7 +49,6 @@ export default class RegisterView extends LoggedView {
username: '',
saving: false
};
Navigation.events().bindComponent(this);
}
componentDidMount() {
@ -71,9 +63,9 @@ export default class RegisterView extends LoggedView {
}
componentDidUpdate(prevProps) {
const { componentId, Site_Name } = this.props;
const { Site_Name } = this.props;
if (Site_Name && prevProps.Site_Name !== Site_Name) {
this.setTitle(componentId, Site_Name);
this.setTitle(Site_Name);
}
}
@ -83,28 +75,9 @@ export default class RegisterView extends LoggedView {
}
}
setTitle = (componentId, title) => {
Navigation.mergeOptions(componentId, {
topBar: {
title: {
text: title
}
}
});
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'more') {
Navigation.showModal({
stack: {
children: [{
component: {
name: 'LegalView'
}
}]
}
});
}
setTitle = (title) => {
const { navigation } = this.props;
navigation.setParams({ title });
}
valid = () => {
@ -137,42 +110,11 @@ export default class RegisterView extends LoggedView {
this.setState({ saving: false });
}
termsService = () => {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
name: 'TermsServiceView',
options: {
topBar: {
title: {
text: I18n.t('Terms_of_Service')
}
}
}
}
});
}
privacyPolicy = () => {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
name: 'PrivacyPolicyView',
options: {
topBar: {
title: {
text: I18n.t('Privacy_Policy')
}
}
}
}
});
}
render() {
const { saving } = this.state;
return (
<KeyboardView contentContainerStyle={sharedStyles.container}>
<StatusBar />
<ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
<SafeAreaView style={sharedStyles.container} testID='register-view' forceInset={{ bottom: 'never' }}>
<Text style={[sharedStyles.loginTitle, sharedStyles.textBold]}>{I18n.t('Sign_Up')}</Text>

View File

@ -4,10 +4,9 @@ import {
View, SectionList, Text, Alert
} from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import Navigation from '../../lib/Navigation';
import { leaveRoom as leaveRoomAction } from '../../actions/room';
import LoggedView from '../View';
import styles from './styles';
@ -23,6 +22,7 @@ import I18n from '../../i18n';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { CustomIcon } from '../../lib/Icons';
import DisclosureIndicator from '../../containers/DisclosureIndicator';
import StatusBar from '../../containers/StatusBar';
const renderSeparator = () => <View style={styles.separator} />;
@ -38,20 +38,13 @@ const renderSeparator = () => <View style={styles.separator} />;
}))
/** @extends React.Component */
export default class RoomActionsView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Actions')
}
}
};
static navigationOptions = {
title: I18n.t('Actions')
}
static propTypes = {
baseUrl: PropTypes.string,
rid: PropTypes.string,
componentId: PropTypes.string,
navigation: PropTypes.object,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
@ -62,10 +55,10 @@ export default class RoomActionsView extends LoggedView {
constructor(props) {
super('RoomActionsView', props);
const { rid, room } = props;
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
this.rid = props.navigation.getParam('rid');
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
this.state = {
room,
room: this.rooms[0] || props.room,
membersCount: 0,
member: {},
joined: false,
@ -76,9 +69,8 @@ export default class RoomActionsView extends LoggedView {
async componentDidMount() {
const { room } = this.state;
if (room && room.t !== 'd' && this.canViewMembers) {
const { rid } = this.props;
try {
const counters = await RocketChat.getRoomCounters(rid, room.t);
const counters = await RocketChat.getRoomCounters(room.rid, room.t);
if (counters.success) {
this.setState({ membersCount: counters.members, joined: counters.joined });
}
@ -119,14 +111,8 @@ export default class RoomActionsView extends LoggedView {
onPressTouchable = (item) => {
if (item.route) {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
name: item.route,
passProps: item.params,
options: item.navigationOptions
}
});
const { navigation } = this.props;
navigation.navigate(item.route, item.params);
}
if (item.event) {
return item.event();
@ -181,7 +167,7 @@ export default class RoomActionsView extends LoggedView {
const notificationsAction = {
icon: notifications ? 'bell' : 'Bell-off',
name: I18n.t(`${ notifications ? 'Enable' : 'Disable' }_notifications`),
event: () => this.toggleNotifications(),
event: this.toggleNotifications,
testID: 'room-actions-notifications'
};
@ -248,13 +234,6 @@ export default class RoomActionsView extends LoggedView {
name: I18n.t('Pinned'),
route: 'PinnedMessagesView',
testID: 'room-actions-pinned'
},
{
icon: 'code',
name: I18n.t('Snippets'),
route: 'SnippetedMessagesView',
params: { rid },
testID: 'room-actions-snippeted'
}
],
renderItem: this.renderItem
@ -267,7 +246,7 @@ export default class RoomActionsView extends LoggedView {
icon: 'ban',
name: I18n.t(`${ blocker ? 'Unblock' : 'Block' }_user`),
type: 'danger',
event: () => this.toggleBlockUser(),
event: this.toggleBlockUser,
testID: 'room-actions-block-user'
}
],
@ -294,15 +273,9 @@ export default class RoomActionsView extends LoggedView {
name: I18n.t('Add_user'),
route: 'SelectedUsersView',
params: {
nextAction: 'ADD_USER',
rid
},
navigationOptions: {
topBar: {
title: {
text: I18n.t('Add_user')
}
}
nextActionID: 'ADD_USER',
rid,
title: I18n.t('Add_user')
},
testID: 'room-actions-add-user'
});
@ -317,7 +290,7 @@ export default class RoomActionsView extends LoggedView {
icon: 'sign-out',
name: I18n.t('Leave_channel'),
type: 'danger',
event: () => this.leaveChannel(),
event: this.leaveChannel,
testID: 'room-actions-leave-channel'
}
],
@ -468,6 +441,7 @@ export default class RoomActionsView extends LoggedView {
render() {
return (
<SafeAreaView style={styles.container} testID='room-actions-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<SectionList
style={styles.container}
stickySectionHeadersEnabled={false}

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import LoggedView from '../View';
@ -11,6 +11,7 @@ import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import RocketChat from '../../lib/rocketchat';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
@ -24,14 +25,8 @@ import RocketChat from '../../lib/rocketchat';
}))
/** @extends React.Component */
export default class RoomFilesView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Files')
}
}
};
static navigationOptions = {
title: I18n.t('Files')
}
static propTypes = {
@ -142,6 +137,7 @@ export default class RoomFilesView extends LoggedView {
return (
<SafeAreaView style={styles.list} testID='room-files-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -4,7 +4,7 @@ import {
Text, View, ScrollView, TouchableOpacity, Keyboard, Alert
} from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import { eraseRoom as eraseRoomAction } from '../../actions/room';
@ -22,6 +22,7 @@ import SwitchContainer from './SwitchContainer';
import random from '../../utils/random';
import log from '../../utils/log';
import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar';
const PERMISSION_SET_READONLY = 'set-readonly';
const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly';
@ -43,24 +44,18 @@ const PERMISSIONS_ARRAY = [
}))
/** @extends React.Component */
export default class RoomInfoEditView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Room_Info_Edit')
}
}
};
static navigationOptions = {
title: I18n.t('Room_Info_Edit')
}
static propTypes = {
rid: PropTypes.string,
navigation: PropTypes.object,
eraseRoom: PropTypes.func
};
constructor(props) {
super('RoomInfoEditView', props);
const { rid } = props;
const rid = props.navigation.getParam('rid');
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
this.permissions = {};
this.state = {
@ -298,6 +293,7 @@ export default class RoomInfoEditView extends LoggedView {
contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128}
>
<StatusBar />
<ScrollView
contentContainerStyle={sharedStyles.containerScrollView}
testID='room-info-edit-view-list'

View File

@ -3,10 +3,9 @@ import PropTypes from 'prop-types';
import { View, Text, ScrollView } from 'react-native';
import { connect } from 'react-redux';
import moment from 'moment';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import Navigation from '../../lib/Navigation';
import LoggedView from '../View';
import Status from '../../containers/Status';
import Avatar from '../../containers/Avatar';
@ -17,7 +16,8 @@ import RocketChat from '../../lib/rocketchat';
import log from '../../utils/log';
import RoomTypeIcon from '../../containers/RoomTypeIcon';
import I18n from '../../i18n';
import Icons from '../../lib/Icons';
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar';
const PERMISSION_EDIT_ROOM = 'edit-room';
@ -45,19 +45,23 @@ const getRoomTitle = room => (room.t === 'd'
}))
/** @extends React.Component */
export default class RoomInfoView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const showEdit = navigation.getParam('showEdit');
const rid = navigation.getParam('rid');
return {
topBar: {
title: {
text: I18n.t('Room_Info')
}
}
title: I18n.t('Room_Info'),
headerRight: showEdit
? (
<CustomHeaderButtons>
<Item iconName='edit' onPress={() => navigation.navigate('RoomInfoEditView', { rid })} testID='room-info-view-edit-button' />
</CustomHeaderButtons>
)
: null
};
}
static propTypes = {
componentId: PropTypes.string,
rid: PropTypes.string,
navigation: PropTypes.object,
user: PropTypes.shape({
id: PropTypes.string,
token: PropTypes.string
@ -65,46 +69,30 @@ export default class RoomInfoView extends LoggedView {
baseUrl: PropTypes.string,
activeUsers: PropTypes.object,
Message_TimeFormat: PropTypes.string,
allRoles: PropTypes.object,
room: PropTypes.object
allRoles: PropTypes.object
}
constructor(props) {
super('RoomInfoView', props);
const { rid, room } = props;
const rid = props.navigation.getParam('rid');
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
this.sub = {
unsubscribe: () => {}
};
this.state = {
room,
room: this.rooms[0] || {},
roomUser: {},
roles: []
};
Navigation.events().bindComponent(this);
}
async componentDidMount() {
this.rooms.addListener(this.updateRoom);
let room = {};
if (this.rooms.length > 0) {
room = this.rooms[0]; // eslint-disable-line prefer-destructuring
} else {
room = this.state.room; // eslint-disable-line
}
const { componentId } = this.props;
const { room } = this.state;
const permissions = RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
if (permissions[PERMISSION_EDIT_ROOM]) {
Navigation.mergeOptions(componentId, {
topBar: {
rightButtons: [{
id: 'edit',
icon: Icons.getSource('edit'),
testID: 'room-info-view-edit-button'
}]
}
});
const { navigation } = this.props;
navigation.setParams({ showEdit: true });
}
// get user of room
@ -164,21 +152,6 @@ export default class RoomInfoView extends LoggedView {
this.sub.unsubscribe();
}
navigationButtonPressed = ({ buttonId }) => {
const { rid, componentId } = this.props;
if (buttonId === 'edit') {
Navigation.push(componentId, {
component: {
id: 'RoomInfoEditView',
name: 'RoomInfoEditView',
passProps: {
rid
}
}
});
}
}
getFullUserData = async(username) => {
try {
const result = await RocketChat.subscribe('fullUserData', username);
@ -312,6 +285,7 @@ export default class RoomInfoView extends LoggedView {
}
return (
<ScrollView style={styles.scroll}>
<StatusBar />
<SafeAreaView style={styles.container} testID='room-info-view' forceInset={{ bottom: 'never' }}>
<View style={styles.avatarContainer}>
{this.renderAvatar(room, roomUser)}

View File

@ -3,10 +3,9 @@ import PropTypes from 'prop-types';
import { FlatList, View } from 'react-native';
import ActionSheet from 'react-native-action-sheet';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import Navigation from '../../lib/Navigation';
import LoggedView from '../View';
import styles from './styles';
import UserItem from '../../presentation/UserItem';
@ -15,11 +14,12 @@ import RocketChat from '../../lib/rocketchat';
import database from '../../lib/realm';
import { showToast } from '../../utils/info';
import log from '../../utils/log';
import { isAndroid } from '../../utils/deviceInfo';
import { vibrate } from '../../utils/vibration';
import I18n from '../../i18n';
import SearchBox from '../../containers/SearchBox';
import protectedFunction from '../../lib/methods/helpers/protectedFunction';
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
@ -31,24 +31,22 @@ import protectedFunction from '../../lib/methods/helpers/protectedFunction';
}))
/** @extends React.Component */
export default class RoomMembersView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const toggleStatus = navigation.getParam('toggleStatus', () => {});
const allUsers = navigation.getParam('allUsers');
const toggleText = allUsers ? I18n.t('Online') : I18n.t('All');
return {
topBar: {
title: {
text: I18n.t('Members')
},
rightButtons: [{
id: 'toggleOnline',
text: I18n.t('Online'),
testID: 'room-members-view-toggle-status',
color: isAndroid ? '#FFF' : undefined
}]
}
title: I18n.t('Members'),
headerRight: (
<CustomHeaderButtons>
<Item title={toggleText} onPress={toggleStatus} testID='room-members-view-toggle-status' />
</CustomHeaderButtons>
)
};
}
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
rid: PropTypes.string,
members: PropTypes.array,
baseUrl: PropTypes.string,
@ -65,7 +63,7 @@ export default class RoomMembersView extends LoggedView {
this.CANCEL_INDEX = 0;
this.MUTE_INDEX = 1;
this.actionSheetOptions = [''];
const { rid, members, room } = props;
const { rid, members } = props.navigation.state.params;
this.rooms = database.objects('subscriptions').filtered('rid = $0', rid);
this.permissions = RocketChat.hasPermission(['mute-user'], rid);
this.state = {
@ -75,15 +73,17 @@ export default class RoomMembersView extends LoggedView {
members,
membersFiltered: [],
userLongPressed: {},
room,
room: this.rooms[0] || {},
options: []
};
Navigation.events().bindComponent(this);
}
componentDidMount() {
this.fetchMembers();
this.rooms.addListener(this.updateRoom);
const { navigation } = this.props;
navigation.setParams({ toggleStatus: this.toggleStatus });
}
shouldComponentUpdate(nextProps, nextState) {
@ -128,29 +128,6 @@ export default class RoomMembersView extends LoggedView {
this.setState({ filtering: !!text, membersFiltered });
})
navigationButtonPressed = ({ buttonId }) => {
const { allUsers } = this.state;
const { componentId } = this.props;
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',
color: isAndroid ? '#FFF' : undefined
}]
}
});
this.fetchMembers(!allUsers);
} catch (e) {
log('RoomMembers.onNavigationButtonPressed', e);
}
}
}
onPressUser = async(item) => {
try {
const subscriptions = database.objects('subscriptions').filtered('name = $0', item.username);
@ -187,6 +164,15 @@ export default class RoomMembersView extends LoggedView {
this.showActionSheet();
}
toggleStatus = () => {
try {
const { allUsers } = this.state;
this.fetchMembers(!allUsers);
} catch (e) {
log('RoomMembers.toggleStatus', e);
}
}
showActionSheet = () => {
ActionSheet.showActionSheetWithOptions({
options: this.actionSheetOptions,
@ -199,9 +185,11 @@ export default class RoomMembersView extends LoggedView {
fetchMembers = async(status) => {
const { rid } = this.state;
const { navigation } = this.props;
const membersResult = await RocketChat.getRoomMembers(rid, status);
const members = membersResult.records;
this.setState({ allUsers: status, members });
navigation.setParams({ allUsers: status });
}
updateRoom = () => {
@ -212,16 +200,9 @@ export default class RoomMembersView extends LoggedView {
}
goRoom = async({ rid, name }) => {
const { componentId } = this.props;
await Navigation.popToRoot(componentId);
Navigation.push('RoomsListView', {
component: {
name: 'RoomView',
passProps: {
rid, name, t: 'd'
}
}
});
const { navigation } = this.props;
await navigation.popToTop();
navigation.navigate('RoomView', { rid, name, t: 'd' });
}
handleMute = async() => {
@ -272,6 +253,7 @@ export default class RoomMembersView extends LoggedView {
} = this.state;
return (
<SafeAreaView style={styles.list} testID='room-members-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<FlatList
data={filtering ? membersFiltered : members}
renderItem={this.renderItem}

View File

@ -18,9 +18,7 @@ const TITLE_SIZE = 18;
const ICON_SIZE = 18;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: isIOS ? 'transparent' : '#2F343D'
flex: 1
},
titleContainer: {
flexDirection: 'row',
@ -175,25 +173,16 @@ export default class RoomHeaderView extends Component {
window, title, usersTyping
} = this.props;
const portrait = window.height > window.width;
let height = isIOS ? 44 : 60;
let scale = 1;
if (!portrait) {
if (isIOS) {
height = 32;
}
if (usersTyping.length > 0) {
scale = 0.8;
}
}
return (
<View
style={[
styles.container,
{ width: window.width - 150, height }
]}
>
<View style={styles.container}>
<View style={styles.titleContainer}>
{this.renderIcon()}
<Text style={[styles.title, { fontSize: TITLE_SIZE * scale }]} numberOfLines={1}>{title}</Text>

View File

@ -5,10 +5,9 @@ import {
} from 'react-native';
import { connect } from 'react-redux';
import { RectButton } from 'react-native-gesture-handler';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import Navigation from '../../lib/Navigation';
import { openRoom as openRoomAction, closeRoom as closeRoomAction, setLastOpen as setLastOpenAction } from '../../actions/room';
import { toggleReactionPicker as toggleReactionPickerAction, actionsShow as actionsShowAction } from '../../actions/messages';
import LoggedView from '../View';
@ -25,8 +24,10 @@ import styles from './styles';
import log from '../../utils/log';
import { isIOS } from '../../utils/deviceInfo';
import I18n from '../../i18n';
import Icons from '../../lib/Icons';
import ConnectionBadge from '../../containers/ConnectionBadge';
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
import RoomHeaderView from './Header';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
user: {
@ -47,31 +48,27 @@ import ConnectionBadge from '../../containers/ConnectionBadge';
}))
/** @extends React.Component */
export default class RoomView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const rid = navigation.getParam('rid');
const t = navigation.getParam('t');
const f = navigation.getParam('f');
const toggleFav = navigation.getParam('toggleFav', () => {});
const starIcon = f ? 'Star-filled' : 'star';
return {
topBar: {
title: {
component: {
name: 'RoomHeaderView',
alignment: 'left'
}
},
rightButtons: [{
id: 'more',
testID: 'room-view-header-actions',
icon: Icons.getSource('more')
}, {
id: 'star',
testID: 'room-view-header-star',
icon: Icons.getSource('starOutline')
}]
},
blurOnUnmount: true
headerTitle: <RoomHeaderView />,
headerRight: t === 'l'
? null
: (
<CustomHeaderButtons>
<Item title='star' iconName={starIcon} onPress={toggleFav} testID='room-view-header-star' />
<Item title='more' iconName='menu' onPress={() => navigation.navigate('RoomActionsView', { rid })} testID='room-view-header-actions' />
</CustomHeaderButtons>
)
};
}
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
openRoom: PropTypes.func.isRequired,
setLastOpen: PropTypes.func.isRequired,
user: PropTypes.shape({
@ -79,9 +76,6 @@ export default class RoomView extends LoggedView {
username: PropTypes.string.isRequired,
token: PropTypes.string.isRequired
}),
rid: PropTypes.string,
name: PropTypes.string,
t: PropTypes.string,
showActions: PropTypes.bool,
showErrorActions: PropTypes.bool,
actionMessage: PropTypes.object,
@ -93,7 +87,7 @@ export default class RoomView extends LoggedView {
constructor(props) {
super('RoomView', props);
this.rid = props.rid;
this.rid = props.navigation.getParam('rid');
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
this.state = {
loaded: false,
@ -101,12 +95,14 @@ export default class RoomView extends LoggedView {
room: {}
};
this.onReactionPress = this.onReactionPress.bind(this);
Navigation.events().bindComponent(this);
}
componentDidMount() {
const { navigation } = this.props;
navigation.setParams({ toggleFav: this.toggleFav });
if (this.rooms.length === 0 && this.rid) {
const { rid, name, t } = this.props;
const { rid, name, t } = navigation.state.params;
this.setState(
{ room: { rid, name, t } },
() => this.updateRoom()
@ -150,26 +146,10 @@ export default class RoomView extends LoggedView {
componentDidUpdate(prevProps, prevState) {
const { room } = this.state;
const { componentId, appState } = this.props;
const { appState, navigation } = this.props;
if (prevState.room.f !== room.f) {
const rightButtons = [{
id: 'star',
testID: 'room-view-header-star',
icon: room.f ? Icons.getSource('star') : Icons.getSource('starOutline')
}];
if (room.t !== 'l') {
rightButtons.unshift({
id: 'more',
testID: 'room-view-header-actions',
icon: Icons.getSource('more')
});
}
Navigation.mergeOptions(componentId, {
topBar: {
rightButtons
}
});
navigation.setParams({ f: room.f });
} else if (appState === 'foreground' && appState !== prevProps.appState) {
RocketChat.loadMissedMessages(room).catch(e => console.log(e));
RocketChat.readMessages(room.rid).catch(e => console.log(e));
@ -207,30 +187,6 @@ export default class RoomView extends LoggedView {
this.setState(...args);
}
navigationButtonPressed = ({ buttonId }) => {
const { room } = this.state;
const { rid, f } = room;
const { componentId } = this.props;
if (buttonId === 'more') {
Navigation.push(componentId, {
component: {
id: 'RoomActionsView',
name: 'RoomActionsView',
passProps: {
rid
}
}
});
} else if (buttonId === 'star') {
try {
RocketChat.toggleFavorite(rid, !f);
} catch (e) {
log('toggleFavorite', e);
}
}
}
// eslint-disable-next-line react/sort-comp
updateRoom = () => {
const { openRoom, setLastOpen } = this.props;
@ -259,6 +215,16 @@ export default class RoomView extends LoggedView {
}
}
toggleFav = () => {
try {
const { room } = this.state;
const { rid, f } = room;
RocketChat.toggleFavorite(rid, !f);
} catch (e) {
log('toggleFavorite', e);
}
}
sendMessage = (message) => {
const { setLastOpen } = this.props;
LayoutAnimation.easeInEaseOut();
@ -268,9 +234,8 @@ export default class RoomView extends LoggedView {
};
joinRoom = async() => {
const { rid } = this.props;
try {
const result = await RocketChat.joinRoom(rid);
const result = await RocketChat.joinRoom(this.rid);
if (result.success) {
this.internalSetState({
joined: true
@ -388,6 +353,7 @@ export default class RoomView extends LoggedView {
return (
<SafeAreaView style={styles.container} testID='room-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
{this.renderList()}
{room._id && showActions
? <MessageActions room={room} user={user} />

View File

@ -39,7 +39,7 @@ const styles = StyleSheet.create({
});
const Header = ({
isFetching, serverName, showServerDropdown, width, setSearchInputRef, showSearchHeader, onSearchChangeText, onPress
isFetching, serverName, showServerDropdown, setSearchInputRef, showSearchHeader, onSearchChangeText, onPress
}) => {
if (showSearchHeader) {
return (
@ -55,7 +55,7 @@ const Header = ({
);
}
return (
<View style={[styles.container, { width: width - 150 }]}>
<View style={styles.container}>
<TouchableOpacity onPress={onPress} testID='rooms-list-header-server-dropdown-button'>
{isFetching ? <Text style={styles.updating}>{I18n.t('Updating')}</Text> : null}
<View style={styles.button}>
@ -74,8 +74,7 @@ Header.propTypes = {
onSearchChangeText: PropTypes.func.isRequired,
setSearchInputRef: PropTypes.func.isRequired,
isFetching: PropTypes.bool,
serverName: PropTypes.string,
width: PropTypes.number
serverName: PropTypes.string
};
Header.defaultProps = {

View File

@ -1,14 +1,12 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { responsive } from 'react-native-responsive-ui';
import {
toggleServerDropdown, closeServerDropdown, closeSortDropdown, setSearch as setSearchAction
} from '../../../actions/rooms';
import Header from './Header';
@responsive
@connect(state => ({
showServerDropdown: state.rooms.showServerDropdown,
showSortDropdown: state.rooms.showSortDropdown,
@ -31,8 +29,7 @@ export default class RoomsListHeaderView extends PureComponent {
open: PropTypes.func,
close: PropTypes.func,
closeSort: PropTypes.func,
setSearch: PropTypes.func,
window: PropTypes.object
setSearch: PropTypes.func
}
componentDidUpdate(prevProps) {
@ -69,10 +66,9 @@ export default class RoomsListHeaderView extends PureComponent {
this.searchInputRef = ref;
}
render() {
const {
serverName, showServerDropdown, showSearchHeader, isFetching, window: { width }
serverName, showServerDropdown, showSearchHeader, isFetching
} = this.props;
return (
<Header
@ -80,7 +76,6 @@ export default class RoomsListHeaderView extends PureComponent {
showServerDropdown={showServerDropdown}
showSearchHeader={showSearchHeader}
isFetching={isFetching}
width={width}
setSearchInputRef={this.setSearchInputRef}
onPress={this.onPress}
onSearchChangeText={text => this.onSearchChangeText(text)}

View File

@ -5,8 +5,8 @@ import {
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import equal from 'deep-equal';
import { withNavigation } from 'react-navigation';
import Navigation from '../../lib/Navigation';
import { toggleServerDropdown as toggleServerDropdownAction } from '../../actions/rooms';
import { selectServerRequest as selectServerRequestAction } from '../../actions/server';
import { appStart as appStartAction } from '../../actions';
@ -29,8 +29,9 @@ const ANIMATION_DURATION = 200;
selectServerRequest: server => dispatch(selectServerRequestAction(server)),
appStart: () => dispatch(appStartAction('outside'))
}))
export default class ServerDropdown extends Component {
class ServerDropdown extends Component {
static propTypes = {
navigation: PropTypes.object,
closeServerDropdown: PropTypes.bool,
server: PropTypes.string,
toggleServerDropdown: PropTypes.func,
@ -108,30 +109,11 @@ export default class ServerDropdown extends Component {
}
addServer = () => {
const { server } = this.props;
const { server, navigation } = this.props;
this.close();
setTimeout(() => {
Navigation.showModal({
stack: {
children: [{
component: {
name: 'OnboardingView',
passProps: {
previousServer: server
},
options: {
topBar: {
visible: false
},
layout: {
orientation: 'portrait'
}
}
}
}]
}
});
navigation.navigate('OnboardingView', { previousServer: server });
}, ANIMATION_DURATION);
}
@ -228,3 +210,4 @@ export default class ServerDropdown extends Component {
);
}
}
export default withNavigation(ServerDropdown);

View File

@ -5,9 +5,9 @@ import {
} from 'react-native';
import { connect } from 'react-redux';
import { isEqual } from 'lodash';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView, NavigationEvents } from 'react-navigation';
import Orientation from 'react-native-orientation-locker';
import Navigation from '../../lib/Navigation';
import SearchBox from '../../containers/SearchBox';
import ConnectionBadge from '../../containers/ConnectionBadge';
import database from '../../lib/realm';
@ -23,13 +23,16 @@ import Touch from '../../utils/touch';
import {
toggleSortDropdown as toggleSortDropdownAction,
openSearchHeader as openSearchHeaderAction,
closeSearchHeader as closeSearchHeaderAction,
roomsRequest as roomsRequestAction
closeSearchHeader as closeSearchHeaderAction
// roomsRequest as roomsRequestAction
} from '../../actions/rooms';
import { appStart as appStartAction } from '../../actions';
import debounce from '../../utils/debounce';
import { isIOS, isAndroid } from '../../utils/deviceInfo';
import Icons, { CustomIcon } from '../../lib/Icons';
import { CustomIcon } from '../../lib/Icons';
import RoomsListHeaderView from './Header';
import { DrawerButton, CustomHeaderButtons, Item } from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar';
const ROW_HEIGHT = 70;
const SCROLL_OFFSET = 56;
@ -38,24 +41,6 @@ const shouldUpdateProps = ['searchText', 'loadingServer', 'showServerDropdown',
const getItemLayout = (data, index) => ({ length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index });
const keyExtractor = item => item.rid;
const leftButtons = [{
id: 'settings',
icon: Icons.getSource('settings'),
testID: 'rooms-list-view-sidebar'
}];
const rightButtons = [{
id: 'newMessage',
icon: Icons.getSource('new_channel'),
testID: 'rooms-list-view-create-channel'
}];
if (isAndroid) {
rightButtons.push({
id: 'search',
icon: Icons.getSource('search')
});
}
@connect(state => ({
userId: state.login.user && state.login.user.id,
server: state.server.server,
@ -74,36 +59,43 @@ if (isAndroid) {
toggleSortDropdown: () => dispatch(toggleSortDropdownAction()),
openSearchHeader: () => dispatch(openSearchHeaderAction()),
closeSearchHeader: () => dispatch(closeSearchHeaderAction()),
appStart: () => dispatch(appStartAction()),
roomsRequest: () => dispatch(roomsRequestAction())
appStart: () => dispatch(appStartAction())
// roomsRequest: () => dispatch(roomsRequestAction())
}))
/** @extends React.Component */
export default class RoomsListView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const searching = navigation.getParam('searching');
const cancelSearchingAndroid = navigation.getParam('cancelSearchingAndroid');
const onPressItem = navigation.getParam('onPressItem', () => {});
const initSearchingAndroid = navigation.getParam('initSearchingAndroid', () => {});
return {
topBar: {
leftButtons,
rightButtons,
title: {
component: {
name: 'RoomsListHeaderView',
alignment: isAndroid ? 'left' : 'fill'
}
}
},
sideMenu: {
left: {
enabled: true
},
right: {
enabled: true
}
},
blurOnUnmount: true
headerLeft: (
searching
? (
<CustomHeaderButtons left>
<Item title='cancel' iconName='cross' onPress={cancelSearchingAndroid} />
</CustomHeaderButtons>
)
: <DrawerButton navigation={navigation} testID='rooms-list-view-sidebar' />
),
headerTitle: <RoomsListHeaderView />,
headerRight: (
searching
? null
: (
<CustomHeaderButtons>
{isAndroid ? <Item title='search' iconName='magnifier' onPress={initSearchingAndroid} /> : null}
<Item title='new' iconName='edit-rounded' onPress={() => navigation.navigate('NewMessageView', { onPressItem })} testID='rooms-list-view-create-channel' />
</CustomHeaderButtons>
)
)
};
}
static propTypes = {
navigation: PropTypes.object,
userId: PropTypes.string,
baseUrl: PropTypes.string,
server: PropTypes.string,
@ -116,12 +108,12 @@ export default class RoomsListView extends LoggedView {
showFavorites: PropTypes.bool,
showUnread: PropTypes.bool,
useRealName: PropTypes.bool,
appState: PropTypes.string,
// appState: PropTypes.string,
toggleSortDropdown: PropTypes.func,
openSearchHeader: PropTypes.func,
closeSearchHeader: PropTypes.func,
appStart: PropTypes.func,
roomsRequest: PropTypes.func
appStart: PropTypes.func
// roomsRequest: PropTypes.func
}
constructor(props) {
@ -140,12 +132,15 @@ export default class RoomsListView extends LoggedView {
direct: [],
livechat: []
};
Navigation.events().bindComponent(this);
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
Orientation.unlockAllOrientations();
}
componentDidMount() {
this.getSubscriptions();
const { navigation } = this.props;
navigation.setParams({
onPressItem: this._onPressItem, initSearchingAndroid: this.initSearchingAndroid, cancelSearchingAndroid: this.cancelSearchingAndroid
});
}
componentWillReceiveProps(nextProps) {
@ -222,7 +217,7 @@ export default class RoomsListView extends LoggedView {
componentDidUpdate(prevProps) {
const {
sortBy, groupByType, showFavorites, showUnread, appState, roomsRequest
sortBy, groupByType, showFavorites, showUnread
} = this.props;
if (!(
@ -232,9 +227,11 @@ export default class RoomsListView extends LoggedView {
&& (prevProps.showUnread === showUnread)
)) {
this.getSubscriptions();
} else if (appState === 'foreground' && appState !== prevProps.appState) {
roomsRequest();
}
// removed for now... we may not need it anymore
// else if (appState === 'foreground' && appState !== prevProps.appState) {
// // roomsRequest();
// }
}
componentWillUnmount() {
@ -245,52 +242,6 @@ export default class RoomsListView extends LoggedView {
this.removeListener(this.privateGroup);
this.removeListener(this.direct);
this.removeListener(this.livechat);
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'newMessage') {
Navigation.showModal({
stack: {
children: [{
component: {
name: 'NewMessageView',
passProps: {
onPressItem: this._onPressItem
},
options: {
topBar: {
title: {
text: I18n.t('New_Message')
}
}
}
}
}]
}
});
} else if (buttonId === 'settings') {
Navigation.showModal({
stack: {
children: [{
component: {
name: 'SidebarView',
options: {
topBar: {
title: {
text: I18n.t('Settings')
}
}
}
}
}]
}
});
} else if (buttonId === 'search') {
this.initSearchingAndroid();
} else if (buttonId === 'back') {
this.cancelSearchingAndroid();
}
}
internalSetState = (...args) => {
@ -394,32 +345,18 @@ export default class RoomsListView extends LoggedView {
}
initSearchingAndroid = () => {
const { openSearchHeader } = this.props;
const { openSearchHeader, navigation } = this.props;
this.setState({ searching: true });
navigation.setParams({ searching: true });
openSearchHeader();
Navigation.mergeOptions('RoomsListView', {
topBar: {
leftButtons: [{
id: 'back',
icon: Icons.getSource('close'),
testID: 'rooms-list-view-cancel-search'
}],
rightButtons: []
}
});
}
cancelSearchingAndroid = () => {
if (isAndroid) {
const { closeSearchHeader } = this.props;
const { closeSearchHeader, navigation } = this.props;
this.setState({ searching: false });
navigation.setParams({ searching: false });
closeSearchHeader();
Navigation.mergeOptions('RoomsListView', {
topBar: {
leftButtons,
rightButtons
}
});
this.internalSetState({ search: [] });
Keyboard.dismiss();
}
@ -450,14 +387,8 @@ export default class RoomsListView extends LoggedView {
goRoom = ({ rid, name, t }) => {
this.cancelSearchingAndroid();
Navigation.push('RoomsListView', {
component: {
name: 'RoomView',
passProps: {
rid, name, t
}
}
});
const { navigation } = this.props;
navigation.navigate('RoomView', { rid, name, t });
}
_onPressItem = async(item = {}) => {
@ -690,6 +621,7 @@ export default class RoomsListView extends LoggedView {
return (
<SafeAreaView style={styles.container} testID='rooms-list-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
{this.renderScroll()}
{showSortDropdown
? (
@ -705,9 +637,11 @@ export default class RoomsListView extends LoggedView {
}
{showServerDropdown ? <ServerDropdown navigator={navigator} /> : null}
<ConnectionBadge />
<NavigationEvents
onDidFocus={() => BackHandler.addEventListener('hardwareBackPress', this.handleBackPress)}
onWillBlur={() => BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress)}
/>
</SafeAreaView>
);
}
}
console.disableYellowBox = true;

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { View, FlatList, Text } from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import LoggedView from '../View';
@ -15,6 +15,7 @@ import RocketChat from '../../lib/rocketchat';
import Message from '../../containers/message/Message';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import I18n from '../../i18n';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
baseUrl: state.settings.Site_Url || state.server ? state.server.server : '',
@ -27,18 +28,12 @@ import I18n from '../../i18n';
}))
/** @extends React.Component */
export default class SearchMessagesView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Search')
}
}
};
static navigationOptions = {
title: I18n.t('Search')
}
static propTypes = {
rid: PropTypes.string,
navigation: PropTypes.object,
user: PropTypes.object,
baseUrl: PropTypes.string,
customEmojis: PropTypes.object
@ -77,7 +72,8 @@ export default class SearchMessagesView extends LoggedView {
// eslint-disable-next-line react/sort-comp
search = debounce(async(searchText) => {
const { rid } = this.props;
const { navigation } = this.props;
const rid = navigation.getParam('rid');
this.setState({ searchText, loading: true, messages: [] });
try {
@ -142,6 +138,7 @@ export default class SearchMessagesView extends LoggedView {
render() {
return (
<SafeAreaView style={styles.container} testID='search-messages-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<View style={styles.searchContainer}>
<RCTextInput
inputRef={(e) => { this.name = e; }}

View File

@ -4,10 +4,9 @@ import {
View, StyleSheet, FlatList, LayoutAnimation
} from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import Navigation from '../lib/Navigation';
import {
addUser as addUserAction, removeUser as removeUserAction, reset as resetAction, setLoading as setLoadingAction
} from '../actions/selectedUsers';
@ -19,9 +18,11 @@ import debounce from '../utils/debounce';
import LoggedView from './View';
import I18n from '../i18n';
import log from '../utils/log';
import { isIOS, isAndroid } from '../utils/deviceInfo';
import { isIOS } from '../utils/deviceInfo';
import SearchBox from '../containers/SearchBox';
import sharedStyles from './Styles';
import { Item, CustomHeaderButtons } from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
const styles = StyleSheet.create({
safeAreaView: {
@ -52,23 +53,21 @@ const styles = StyleSheet.create({
}))
/** @extends React.Component */
export default class SelectedUsersView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const title = navigation.getParam('title');
const nextAction = navigation.getParam('nextAction', () => {});
return {
topBar: {
rightButtons: [{
id: 'create',
text: I18n.t('Next'),
testID: 'selected-users-view-submit',
color: isAndroid ? '#FFF' : undefined
}]
}
title,
headerRight: (
<CustomHeaderButtons>
<Item title={I18n.t('Next')} onPress={nextAction} testID='selected-users-view-submit' />
</CustomHeaderButtons>
)
};
}
static propTypes = {
componentId: PropTypes.string,
rid: PropTypes.string,
nextAction: PropTypes.string.isRequired,
navigation: PropTypes.object,
baseUrl: PropTypes.string,
addUser: PropTypes.func.isRequired,
removeUser: PropTypes.func.isRequired,
@ -89,7 +88,11 @@ export default class SelectedUsersView extends LoggedView {
search: []
};
this.data.addListener(this.updateState);
Navigation.events().bindComponent(this);
}
componentDidMount() {
const { navigation } = this.props;
navigation.setParams({ nextAction: this.nextAction });
}
shouldComponentUpdate(nextProps, nextState) {
@ -118,22 +121,18 @@ export default class SelectedUsersView extends LoggedView {
this.search(text);
}
navigationButtonPressed = async({ buttonId }) => {
if (buttonId === 'create') {
const { nextAction, setLoadingInvite } = this.props;
if (nextAction === 'CREATE_CHANNEL') {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
name: 'CreateChannelView'
}
});
nextAction = async() => {
const { navigation, setLoadingInvite } = this.props;
const nextActionID = navigation.getParam('nextActionID');
if (nextActionID === 'CREATE_CHANNEL') {
navigation.navigate('CreateChannelView');
} else {
const { rid, componentId } = this.props;
const rid = navigation.getParam('rid');
try {
setLoadingInvite(true);
await RocketChat.addUsersToRoom(rid);
Navigation.pop(componentId);
navigation.pop();
// Navigation.pop(componentId);
} catch (e) {
log('RoomActions Add User', e);
} finally {
@ -141,7 +140,6 @@ export default class SelectedUsersView extends LoggedView {
}
}
}
}
// eslint-disable-next-line react/sort-comp
updateState = debounce(() => {
@ -275,6 +273,7 @@ export default class SelectedUsersView extends LoggedView {
const { loading } = this.props;
return (
<SafeAreaView style={styles.safeAreaView} testID='select-users-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
{this.renderList()}
<Loading visible={loading} />
</SafeAreaView>

View File

@ -4,9 +4,9 @@ import {
Text, ScrollView, StyleSheet
} from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import Orientation from 'react-native-orientation-locker';
import Navigation from '../lib/Navigation';
import { loginRequest as loginRequestAction } from '../actions/login';
import TextInput from '../containers/TextInput';
import Button from '../containers/Button';
@ -15,8 +15,8 @@ import sharedStyles from './Styles';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import LoggedView from './View';
import I18n from '../i18n';
import { DARK_HEADER } from '../constants/headerOptions';
import RocketChat from '../lib/rocketchat';
import StatusBar from '../containers/StatusBar';
const styles = StyleSheet.create({
loginTitle: {
@ -33,14 +33,15 @@ const styles = StyleSheet.create({
}))
/** @extends React.Component */
export default class SetUsernameView extends LoggedView {
static options() {
static navigationOptions = ({ navigation }) => {
const title = navigation.getParam('title');
return {
...DARK_HEADER
title
};
}
static propTypes = {
componentId: PropTypes.string,
navigation: PropTypes.object,
server: PropTypes.string,
userId: PropTypes.string,
loginRequest: PropTypes.func,
@ -53,14 +54,9 @@ export default class SetUsernameView extends LoggedView {
username: '',
saving: false
};
const { componentId, server } = this.props;
Navigation.mergeOptions(componentId, {
topBar: {
title: {
text: server
}
}
});
const { server } = this.props;
props.navigation.setParams({ title: server });
Orientation.lockToPortrait();
}
async componentDidMount() {
@ -112,6 +108,7 @@ export default class SetUsernameView extends LoggedView {
const { username, saving } = this.state;
return (
<KeyboardView contentContainerStyle={sharedStyles.container}>
<StatusBar />
<ScrollView {...scrollPersistTaps} contentContainerStyle={sharedStyles.containerScrollView}>
<SafeAreaView style={sharedStyles.container} testID='set-username-view' forceInset={{ bottom: 'never' }}>
<Text style={[sharedStyles.loginTitle, sharedStyles.textBold, styles.loginTitle]}>{I18n.t('Username')}</Text>
@ -120,7 +117,7 @@ export default class SetUsernameView extends LoggedView {
inputRef={e => this.usernameInput = e}
placeholder={I18n.t('Username')}
returnKeyType='send'
iconLeft='mention'
iconLeft='at'
onChangeText={value => this.setState({ username: value })}
value={username}
onSubmitEditing={this.submit}

View File

@ -3,9 +3,8 @@ import PropTypes from 'prop-types';
import { View, ScrollView } from 'react-native';
import RNPickerSelect from 'react-native-picker-select';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import Navigation from '../../lib/Navigation';
import LoggedView from '../View';
import RocketChat from '../../lib/rocketchat';
import KeyboardView from '../../presentation/KeyboardView';
@ -18,6 +17,8 @@ import Loading from '../../containers/Loading';
import { showErrorAlert, showToast } from '../../utils/info';
import log from '../../utils/log';
import { setUser as setUserAction } from '../../actions/login';
import { DrawerButton } from '../../containers/HeaderButton';
import StatusBar from '../../containers/StatusBar';
@connect(state => ({
userLanguage: state.login.user && state.login.user.language
@ -26,15 +27,10 @@ import { setUser as setUserAction } from '../../actions/login';
}))
/** @extends React.Component */
export default class SettingsView extends LoggedView {
static options() {
return {
topBar: {
title: {
text: I18n.t('Settings')
}
}
};
}
static navigationOptions = ({ navigation }) => ({
headerLeft: <DrawerButton navigation={navigation} />,
title: I18n.t('Settings')
})
static propTypes = {
componentId: PropTypes.string,
@ -124,17 +120,6 @@ export default class SettingsView extends LoggedView {
this.setState({ saving: false });
setTimeout(() => {
showToast(I18n.t('Preferences_saved'));
if (params.language) {
const { componentId } = this.props;
Navigation.mergeOptions(componentId, {
topBar: {
title: {
text: I18n.t('Settings')
}
}
});
}
}, 300);
} catch (e) {
this.setState({ saving: false });
@ -154,6 +139,7 @@ export default class SettingsView extends LoggedView {
contentContainerStyle={sharedStyles.container}
keyboardVerticalOffset={128}
>
<StatusBar />
<ScrollView
contentContainerStyle={sharedStyles.containerScrollView}
testID='settings-view-list'

View File

@ -1,366 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
ScrollView, Text, View, StyleSheet, FlatList, LayoutAnimation, SafeAreaView, Image
} from 'react-native';
import { connect } from 'react-redux';
import equal from 'deep-equal';
import Navigation from '../lib/Navigation';
import { logout as logoutAction } from '../actions/login';
import Avatar from '../containers/Avatar';
import StatusContainer from '../containers/Status';
import Status from '../containers/Status/Status';
import Touch from '../utils/touch';
import RocketChat from '../lib/rocketchat';
import log from '../utils/log';
import I18n from '../i18n';
import scrollPersistTaps from '../utils/scrollPersistTaps';
import { getReadableVersion, isIOS, isAndroid } from '../utils/deviceInfo';
import Icons, { CustomIcon } from '../lib/Icons';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
},
item: {
flexDirection: 'row',
alignItems: 'center'
},
itemLeft: {
marginHorizontal: 10,
width: 30,
alignItems: 'center'
},
itemCenter: {
flex: 1
},
itemText: {
marginVertical: 16,
fontWeight: 'bold',
color: '#292E35'
},
separator: {
borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: '#ddd',
marginVertical: 4
},
header: {
paddingVertical: 16,
flexDirection: 'row',
alignItems: 'center'
},
headerTextContainer: {
flex: 1,
flexDirection: 'column',
alignItems: 'flex-start'
},
headerUsername: {
flexDirection: 'row',
alignItems: 'center'
},
avatar: {
marginHorizontal: 10
},
status: {
marginRight: 5
},
currentServerText: {
fontWeight: 'bold'
},
version: {
marginHorizontal: 5,
marginBottom: 5,
fontWeight: '600',
color: '#292E35',
fontSize: 13
},
disclosureContainer: {
marginLeft: 6,
marginRight: 9,
alignItems: 'center',
justifyContent: 'center'
},
disclosureIndicator: {
width: 20,
height: 20
}
});
const keyExtractor = item => item.id;
@connect(state => ({
Site_Name: state.settings.Site_Name,
user: {
id: state.login.user && state.login.user.id,
language: state.login.user && state.login.user.language,
status: state.login.user && state.login.user.status,
username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token
},
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}), dispatch => ({
logout: () => dispatch(logoutAction())
}))
export default class Sidebar extends Component {
static options() {
return {
topBar: {
leftButtons: [{
id: 'cancel',
icon: isAndroid ? Icons.getSource('close', false) : undefined,
systemItem: 'cancel'
}]
}
};
}
static propTypes = {
baseUrl: PropTypes.string,
componentId: PropTypes.string,
Site_Name: PropTypes.string.isRequired,
user: PropTypes.object,
logout: PropTypes.func.isRequired
}
constructor(props) {
super(props);
this.state = {
showStatus: false,
status: []
};
Navigation.events().bindComponent(this);
}
componentDidMount() {
this.setStatus();
}
componentWillReceiveProps(nextProps) {
const { user } = this.props;
if (nextProps.user && user && user.language !== nextProps.user.language) {
this.setStatus();
}
}
shouldComponentUpdate(nextProps, nextState) {
const { status, showStatus } = this.state;
const { Site_Name, user, baseUrl } = this.props;
if (nextState.showStatus !== showStatus) {
return true;
}
if (nextProps.Site_Name !== Site_Name) {
return true;
}
if (nextProps.Site_Name !== Site_Name) {
return true;
}
if (nextProps.baseUrl !== baseUrl) {
return true;
}
if (nextProps.user && user) {
if (nextProps.user.language !== user.language) {
return true;
}
if (nextProps.user.status !== user.status) {
return true;
}
if (nextProps.user.username !== user.username) {
return true;
}
}
if (!equal(nextState.status, status)) {
return true;
}
return false;
}
navigationButtonPressed = ({ buttonId }) => {
if (buttonId === 'cancel') {
const { componentId } = this.props;
Navigation.dismissModal(componentId);
}
}
setStatus = () => {
this.setState({
status: [{
id: 'online',
name: I18n.t('Online')
}, {
id: 'busy',
name: I18n.t('Busy')
}, {
id: 'away',
name: I18n.t('Away')
}, {
id: 'offline',
name: I18n.t('Invisible')
}]
});
}
toggleStatus = () => {
LayoutAnimation.easeInEaseOut();
this.setState(prevState => ({ showStatus: !prevState.showStatus }));
}
sidebarNavigate = (route) => {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
name: route
}
});
}
logout = () => {
const { componentId, logout } = this.props;
Navigation.dismissModal(componentId);
logout();
}
renderSeparator = key => <View key={key} style={styles.separator} />;
renderItem = ({
text, left, onPress, testID, disclosure
}) => (
<Touch
key={text}
onPress={onPress}
underlayColor='rgba(255, 255, 255, 0.5)'
activeOpacity={0.3}
testID={testID}
>
<View style={styles.item}>
<View style={styles.itemLeft}>
{left}
</View>
<View style={styles.itemCenter}>
<Text style={styles.itemText}>
{text}
</Text>
</View>
{disclosure ? this.renderDisclosure() : null}
</View>
</Touch>
)
renderStatusItem = ({ item }) => {
const { user } = this.props;
return (
this.renderItem({
text: item.name,
left: <Status style={styles.status} size={12} status={item.id} />,
current: user.status === item.id,
onPress: () => {
this.toggleStatus();
if (user.status !== item.id) {
try {
RocketChat.setUserPresenceDefaultStatus(item.id);
} catch (e) {
log('setUserPresenceDefaultStatus', e);
}
}
}
})
);
}
// Remove it after https://github.com/RocketChat/Rocket.Chat.ReactNative/pull/643
renderDisclosure = () => {
if (isIOS) {
return (
<View style={styles.disclosureContainer}>
<Image source={{ uri: 'disclosure_indicator' }} style={styles.disclosureIndicator} />
</View>
);
}
}
renderNavigation = () => (
[
this.renderItem({
text: I18n.t('Profile'),
left: <CustomIcon name='user' size={20} color='#292E35' />,
onPress: () => this.sidebarNavigate('ProfileView'),
testID: 'sidebar-profile',
disclosure: true
}),
this.renderItem({
text: I18n.t('Settings'),
left: <CustomIcon name='cog' size={20} color='#292E35' />,
onPress: () => this.sidebarNavigate('SettingsView'),
testID: 'sidebar-settings',
disclosure: true
}),
this.renderSeparator('separator-logout'),
this.renderItem({
text: I18n.t('Logout'),
left: <CustomIcon name='sign-out' size={20} color='#292E35' />,
onPress: () => this.logout(),
testID: 'sidebar-logout'
})
]
)
renderStatus = () => {
const { status } = this.state;
const { user } = this.props;
return (
<FlatList
key='status-list'
data={status}
extraData={user}
renderItem={this.renderStatusItem}
keyExtractor={keyExtractor}
/>
);
}
render() {
const { showStatus } = this.state;
const { user, Site_Name, baseUrl } = this.props;
if (!user) {
return null;
}
return (
<SafeAreaView testID='sidebar-view' style={styles.container}>
<ScrollView style={styles.container} {...scrollPersistTaps}>
<Touch
onPress={() => this.toggleStatus()}
underlayColor='rgba(255, 255, 255, 0.5)'
activeOpacity={0.3}
testID='sidebar-toggle-status'
>
<View style={styles.header}>
<Avatar
text={user.username}
size={30}
style={styles.avatar}
baseUrl={baseUrl}
user={user}
/>
<View style={styles.headerTextContainer}>
<View style={styles.headerUsername}>
<StatusContainer style={styles.status} size={12} id={user.id} />
<Text numberOfLines={1}>{user.username}</Text>
</View>
<Text style={styles.currentServerText} numberOfLines={1}>{Site_Name}</Text>
</View>
</View>
</Touch>
{this.renderSeparator('separator-header')}
{!showStatus ? this.renderNavigation() : null}
{showStatus ? this.renderStatus() : null}
</ScrollView>
<Text style={styles.version}>
{getReadableVersion}
</Text>
</SafeAreaView>
);
}
}

View File

@ -0,0 +1,38 @@
import React from 'react';
import { View, Text } from 'react-native';
import PropTypes from 'prop-types';
import { RectButton } from 'react-native-gesture-handler';
import styles from './styles';
const Item = React.memo(({
left, text, onPress, testID, current
}) => (
<RectButton
key={testID}
testID={testID}
onPress={onPress}
underlayColor='#292E35'
activeOpacity={0.1}
style={[styles.item, current && styles.itemCurrent]}
>
<View style={styles.itemLeft}>
{left}
</View>
<View style={styles.itemCenter}>
<Text style={styles.itemText}>
{text}
</Text>
</View>
</RectButton>
));
Item.propTypes = {
left: PropTypes.element,
text: PropTypes.string,
current: PropTypes.bool,
onPress: PropTypes.func,
testID: PropTypes.string
};
export default Item;

View File

@ -0,0 +1,255 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
ScrollView, Text, View, FlatList, LayoutAnimation, SafeAreaView
} from 'react-native';
import { connect } from 'react-redux';
import equal from 'deep-equal';
import { RectButton } from 'react-native-gesture-handler';
import { logout as logoutAction } from '../../actions/login';
import Avatar from '../../containers/Avatar';
import StatusContainer from '../../containers/Status';
import Status from '../../containers/Status/Status';
import RocketChat from '../../lib/rocketchat';
import log from '../../utils/log';
import I18n from '../../i18n';
import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { getReadableVersion } from '../../utils/deviceInfo';
import { CustomIcon } from '../../lib/Icons';
import styles from './styles';
import SidebarItem from './SidebarItem';
const keyExtractor = item => item.id;
const Separator = React.memo(() => <View style={styles.separator} />);
@connect(state => ({
Site_Name: state.settings.Site_Name,
user: {
id: state.login.user && state.login.user.id,
language: state.login.user && state.login.user.language,
status: state.login.user && state.login.user.status,
username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token
},
baseUrl: state.settings.Site_Url || state.server ? state.server.server : ''
}), dispatch => ({
logout: () => dispatch(logoutAction())
}))
export default class Sidebar extends Component {
static propTypes = {
baseUrl: PropTypes.string,
navigation: PropTypes.object,
Site_Name: PropTypes.string.isRequired,
user: PropTypes.object,
logout: PropTypes.func.isRequired,
activeItemKey: PropTypes.string
}
constructor(props) {
super(props);
this.state = {
showStatus: false,
status: []
};
}
componentDidMount() {
this.setStatus();
}
componentWillReceiveProps(nextProps) {
const { user } = this.props;
if (nextProps.user && user && user.language !== nextProps.user.language) {
this.setStatus();
}
}
shouldComponentUpdate(nextProps, nextState) {
const { status, showStatus } = this.state;
const {
Site_Name, user, baseUrl, activeItemKey
} = this.props;
if (nextState.showStatus !== showStatus) {
return true;
}
if (nextProps.Site_Name !== Site_Name) {
return true;
}
if (nextProps.Site_Name !== Site_Name) {
return true;
}
if (nextProps.baseUrl !== baseUrl) {
return true;
}
if (nextProps.activeItemKey !== activeItemKey) {
return true;
}
if (nextProps.user && user) {
if (nextProps.user.language !== user.language) {
return true;
}
if (nextProps.user.status !== user.status) {
return true;
}
if (nextProps.user.username !== user.username) {
return true;
}
}
if (!equal(nextState.status, status)) {
return true;
}
return false;
}
setStatus = () => {
this.setState({
status: [{
id: 'online',
name: I18n.t('Online')
}, {
id: 'busy',
name: I18n.t('Busy')
}, {
id: 'away',
name: I18n.t('Away')
}, {
id: 'offline',
name: I18n.t('Invisible')
}]
});
}
toggleStatus = () => {
LayoutAnimation.easeInEaseOut();
this.setState(prevState => ({ showStatus: !prevState.showStatus }));
}
sidebarNavigate = (route) => {
const { navigation } = this.props;
navigation.navigate(route);
}
logout = () => {
const { logout } = this.props;
logout();
}
renderStatusItem = ({ item }) => {
const { user } = this.props;
return (
<SidebarItem
text={item.name}
left={<Status style={styles.status} size={12} status={item.id} />}
current={user.status === item.id}
onPress={() => {
this.toggleStatus();
if (user.status !== item.id) {
try {
RocketChat.setUserPresenceDefaultStatus(item.id);
} catch (e) {
log('setUserPresenceDefaultStatus', e);
}
}
}}
/>
);
}
renderNavigation = () => {
const { activeItemKey } = this.props;
return (
<React.Fragment>
<SidebarItem
text={I18n.t('Chats')}
left={<CustomIcon name='chat' size={20} color='#292E35' />}
onPress={() => this.sidebarNavigate('RoomsListView')}
testID='sidebar-chats'
current={activeItemKey === 'ChatsStack'}
/>
<SidebarItem
text={I18n.t('Profile')}
left={<CustomIcon name='user' size={20} color='#292E35' />}
onPress={() => this.sidebarNavigate('ProfileView')}
testID='sidebar-profile'
current={activeItemKey === 'ProfileStack'}
/>
<SidebarItem
text={I18n.t('Settings')}
left={<CustomIcon name='cog' size={20} color='#292E35' />}
onPress={() => this.sidebarNavigate('SettingsView')}
testID='sidebar-settings'
current={activeItemKey === 'SettingsStack'}
/>
<Separator key='separator-logout' />
<SidebarItem
text={I18n.t('Logout')}
left={<CustomIcon name='sign-out' size={20} color='#292E35' />}
onPress={this.logout}
testID='sidebar-logout'
/>
</React.Fragment>
);
}
renderStatus = () => {
const { status } = this.state;
const { user } = this.props;
return (
<FlatList
key='status-list'
data={status}
extraData={user}
renderItem={this.renderStatusItem}
keyExtractor={keyExtractor}
/>
);
}
render() {
const { showStatus } = this.state;
const { user, Site_Name, baseUrl } = this.props;
if (!user) {
return null;
}
return (
<SafeAreaView testID='sidebar-view' style={styles.container}>
<ScrollView style={styles.container} {...scrollPersistTaps}>
<RectButton
onPress={this.toggleStatus}
underlayColor='#292E35'
activeOpacity={0.1}
testID='sidebar-toggle-status'
style={styles.header}
>
<Avatar
text={user.username}
size={30}
style={styles.avatar}
baseUrl={baseUrl}
user={user}
/>
<View style={styles.headerTextContainer}>
<View style={styles.headerUsername}>
<StatusContainer style={styles.status} size={12} id={user.id} />
<Text numberOfLines={1}>{user.username}</Text>
</View>
<Text style={styles.currentServerText} numberOfLines={1}>{Site_Name}</Text>
</View>
<CustomIcon name='arrow-down' size={20} style={[styles.headerIcon, showStatus && styles.inverted]} />
</RectButton>
<Separator key='separator-header' />
{!showStatus ? this.renderNavigation() : null}
{showStatus ? this.renderStatus() : null}
</ScrollView>
<Text style={styles.version}>
{getReadableVersion}
</Text>
</SafeAreaView>
);
}
}

View File

@ -0,0 +1,70 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff'
},
item: {
flexDirection: 'row',
alignItems: 'center'
},
itemCurrent: {
backgroundColor: '#E1E5E8'
},
itemLeft: {
marginHorizontal: 10,
width: 30,
alignItems: 'center'
},
itemCenter: {
flex: 1
},
itemText: {
marginVertical: 16,
fontWeight: 'bold',
color: '#292E35'
},
separator: {
borderBottomWidth: StyleSheet.hairlineWidth,
borderColor: '#E1E5E8',
marginVertical: 4
},
header: {
paddingVertical: 16,
flexDirection: 'row',
alignItems: 'center'
},
headerTextContainer: {
flex: 1,
flexDirection: 'column',
alignItems: 'flex-start'
},
headerUsername: {
flexDirection: 'row',
alignItems: 'center'
},
headerIcon: {
paddingHorizontal: 10,
color: '#292E35'
},
avatar: {
marginHorizontal: 10
},
status: {
marginRight: 5
},
currentServerText: {
fontWeight: 'bold'
},
version: {
marginHorizontal: 5,
marginBottom: 5,
fontWeight: '600',
color: '#292E35',
fontSize: 13
},
inverted: {
transform: [{ scaleY: -1 }]
}
});

View File

@ -1,150 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import equal from 'deep-equal';
import { openSnippetedMessages as openSnippetedMessagesAction, closeSnippetedMessages as closeSnippetedMessagesAction } from '../../actions/snippetedMessages';
import LoggedView from '../View';
import styles from './styles';
import Message from '../../containers/message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
@connect(state => ({
messages: state.snippetedMessages.messages,
ready: state.snippetedMessages.ready,
user: {
id: state.login.user && state.login.user.id,
username: state.login.user && state.login.user.username,
token: state.login.user && state.login.user.token
}
}), dispatch => ({
openSnippetedMessages: (rid, limit) => dispatch(openSnippetedMessagesAction(rid, limit)),
closeSnippetedMessages: () => dispatch(closeSnippetedMessagesAction())
}))
/** @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,
ready: PropTypes.bool,
user: PropTypes.object,
openSnippetedMessages: PropTypes.func,
closeSnippetedMessages: PropTypes.func
}
constructor(props) {
super('SnippetedMessagesView', props);
this.state = {
loading: true,
loadingMore: false
};
}
componentDidMount() {
this.limit = 20;
this.load();
}
componentWillReceiveProps(nextProps) {
const { ready } = this.props;
if (nextProps.ready && nextProps.ready !== ready) {
this.setState({ loading: false, loadingMore: false });
}
}
shouldComponentUpdate(nextProps, nextState) {
const { loading, loadingMore } = this.state;
const { messages, ready } = this.props;
if (nextState.loading !== loading) {
return true;
}
if (nextState.loadingMore !== loadingMore) {
return true;
}
if (nextProps.ready !== ready) {
return true;
}
if (!equal(nextState.messages, messages)) {
return true;
}
return false;
}
componentWillUnmount() {
const { closeSnippetedMessages } = this.props;
closeSnippetedMessages();
}
load = () => {
const { rid, openSnippetedMessages } = this.props;
openSnippetedMessages(rid, this.limit);
}
moreData = () => {
const { loadingMore } = this.state;
const { messages } = this.props;
if (messages.length < this.limit) {
return;
}
if (!loadingMore) {
this.setState({ loadingMore: true });
this.limit += 20;
this.load();
}
}
renderEmpty = () => (
<View style={styles.listEmptyContainer} testID='snippeted-messages-view'>
<Text>{I18n.t('No_snippeted_messages')}</Text>
</View>
)
renderItem = ({ item }) => {
const { user } = this.props;
return (
<Message
item={item}
style={styles.message}
reactions={item.reactions}
user={user}
customTimeFormat='MMM Do YYYY, h:mm:ss a'
/>
);
}
render() {
const { loading, loadingMore } = this.state;
const { messages, ready } = this.props;
if (ready && messages.length === 0) {
return this.renderEmpty();
}
return (
<SafeAreaView style={styles.list} testID='snippeted-messages-view' forceInset={{ bottom: 'never' }}>
<FlatList
data={messages}
renderItem={this.renderItem}
style={styles.list}
keyExtractor={item => item._id}
onEndReached={this.moreData}
ListHeaderComponent={loading ? <RCActivityIndicator /> : null}
ListFooterComponent={loadingMore ? <RCActivityIndicator /> : null}
/>
</SafeAreaView>
);
}
}

View File

@ -1,17 +0,0 @@
import { StyleSheet } from 'react-native';
export default StyleSheet.create({
list: {
flex: 1,
backgroundColor: '#ffffff'
},
message: {
transform: [{ scaleY: 1 }]
},
listEmptyContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#ffffff'
}
});

View File

@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { FlatList, View, Text } from 'react-native';
import { connect } from 'react-redux';
import ActionSheet from 'react-native-action-sheet';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import equal from 'deep-equal';
import LoggedView from '../View';
@ -12,6 +12,7 @@ import Message from '../../containers/message/Message';
import RCActivityIndicator from '../../containers/ActivityIndicator';
import I18n from '../../i18n';
import RocketChat from '../../lib/rocketchat';
import StatusBar from '../../containers/StatusBar';
const STAR_INDEX = 0;
const CANCEL_INDEX = 1;
@ -29,14 +30,8 @@ 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 navigationOptions = {
title: I18n.t('Starred')
}
static propTypes = {
@ -175,6 +170,7 @@ export default class StarredMessagesView extends LoggedView {
return (
<SafeAreaView style={styles.list} testID='starred-messages-view' forceInset={{ bottom: 'never' }}>
<StatusBar />
<FlatList
data={messages}
renderItem={this.renderItem}

View File

@ -2,29 +2,20 @@ import React from 'react';
import PropTypes from 'prop-types';
import { WebView } from 'react-native';
import { connect } from 'react-redux';
import SafeAreaView from 'react-native-safe-area-view';
import { SafeAreaView } from 'react-navigation';
import styles from './Styles';
import LoggedView from './View';
import { DARK_HEADER } from '../constants/headerOptions';
import I18n from '../i18n';
import StatusBar from '../containers/StatusBar';
@connect(state => ({
termsService: state.settings.Layout_Terms_of_Service
}))
/** @extends React.Component */
export default class TermsServiceView extends LoggedView {
static options() {
return {
...DARK_HEADER,
topBar: {
...DARK_HEADER.topBar,
title: {
...DARK_HEADER.topBar.title,
text: I18n.t('Terms_of_Service')
}
}
};
static navigationOptions = {
title: I18n.t('Terms_of_Service')
}
static propTypes = {
@ -39,6 +30,7 @@ export default class TermsServiceView extends LoggedView {
const { termsService } = this.props;
return (
<SafeAreaView style={styles.container} testID='terms-view'>
<StatusBar />
<WebView originWhitelist={['*']} source={{ html: termsService, baseUrl: '' }} />
</SafeAreaView>
);

View File

@ -32,7 +32,7 @@ describe('Create room screen', () => {
describe('Usage', async() => {
it('should back to rooms list', async() => {
await element(by.text('Cancel')).tap();
await element(by.id('new-message-view-close')).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();

View File

@ -86,11 +86,6 @@ describe('Room actions screen', () => {
await expect(element(by.id('room-actions-pinned'))).toBeVisible();
});
it('should have snippeted', async() => {
await waitFor(element(by.id('room-actions-snippeted'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-snippeted'))).toBeVisible();
});
it('should have notifications', async() => {
await waitFor(element(by.id('room-actions-notifications'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-notifications'))).toBeVisible();
@ -161,11 +156,6 @@ describe('Room actions screen', () => {
await expect(element(by.id('room-actions-pinned'))).toBeVisible();
});
it('should have snippeted', async() => {
await waitFor(element(by.id('room-actions-snippeted'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-snippeted'))).toBeVisible();
});
it('should have notifications', async() => {
await waitFor(element(by.id('room-actions-notifications'))).toBeVisible().whileElement(by.id('room-actions-list')).scroll(scrollDown, 'down');
await expect(element(by.id('room-actions-notifications'))).toBeVisible();

View File

@ -122,10 +122,6 @@ describe('Join public room', () => {
await expect(element(by.id('room-actions-pinned'))).toBeVisible();
});
it('should have snippeted', async() => {
await expect(element(by.id('room-actions-snippeted'))).toBeVisible();
});
it('should not have notifications', async() => {
await expect(element(by.id('room-actions-notifications'))).toBeNotVisible();
});
@ -172,7 +168,6 @@ describe('Join public room', () => {
await element(by.id('room-actions-list')).swipe('up');
await expect(element(by.id('room-actions-share'))).toBeVisible();
await expect(element(by.id('room-actions-pinned'))).toBeVisible();
await expect(element(by.id('room-actions-snippeted'))).toBeVisible();
await expect(element(by.id('room-actions-notifications'))).toBeVisible();
await expect(element(by.id('room-actions-leave-channel'))).toBeVisible();
});

View File

@ -44,7 +44,7 @@ async function logout() {
}
async function tapBack() {
await element(by.type('_UIModernBarButton').withAncestor(by.type('_UIBackButtonContainerView'))).tap();
await element(by.id('header-back')).atIndex(0).tap();
}
async function sleep(ms) {

View File

@ -1,6 +0,0 @@
import './app/ReactotronConfig';
import './app/push';
import App from './app/index';
// eslint-disable-next-line
const app = new App();

View File

@ -1,6 +0,0 @@
import './app/ReactotronConfig';
import './app/push';
import App from './app/index';
// eslint-disable-next-line
const app = new App();

9
index.js Normal file
View File

@ -0,0 +1,9 @@
import './app/ReactotronConfig';
import { AppRegistry } from 'react-native';
import App from './app/index';
import { name as appName } from './app.json';
AppRegistry.registerComponent(appName, () => App);
// For storybook, comment everything above and uncomment below
// import './storybook';

View File

@ -26,6 +26,12 @@ target 'RocketChatRN' do
pod 'RNImageCropPicker', :path => '../node_modules/react-native-image-crop-picker'
pod 'RNDeviceInfo', :path => '../node_modules/react-native-device-info'
pod 'RNScreens', :path => '../node_modules/react-native-screens'
pod 'react-native-splash-screen', :path => '../node_modules/react-native-splash-screen'
pod 'react-native-orientation-locker', :path => '../node_modules/react-native-orientation-locker'
end
post_install do |installer|

View File

@ -2,6 +2,10 @@ PODS:
- QBImagePickerController (3.4.0)
- React (0.58.6):
- React/Core (= 0.58.6)
- react-native-orientation-locker (1.1.3):
- React
- react-native-splash-screen (3.2.0):
- React
- React/Core (0.58.6):
- yoga (= 0.58.6.React)
- React/fishhook (0.58.6)
@ -36,10 +40,14 @@ PODS:
- QBImagePickerController
- React/Core
- RSKImageCropper
- RNScreens (1.0.0-alpha.22):
- React
- RSKImageCropper (2.2.1)
- yoga (0.58.6.React)
DEPENDENCIES:
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
- React/Core (from `../node_modules/react-native`)
- React/RCTActionSheet (from `../node_modules/react-native`)
- React/RCTAnimation (from `../node_modules/react-native`)
@ -53,6 +61,7 @@ DEPENDENCIES:
- React/RCTWebSocket (from `../node_modules/react-native`)
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- RNImageCropPicker (from `../node_modules/react-native-image-crop-picker`)
- RNScreens (from `../node_modules/react-native-screens`)
- yoga (from `../node_modules/react-native/ReactCommon/yoga/yoga.podspec`)
SPEC REPOS:
@ -63,21 +72,30 @@ SPEC REPOS:
EXTERNAL SOURCES:
React:
:path: "../node_modules/react-native"
react-native-orientation-locker:
:path: "../node_modules/react-native-orientation-locker"
react-native-splash-screen:
:path: "../node_modules/react-native-splash-screen"
RNDeviceInfo:
:path: "../node_modules/react-native-device-info"
RNImageCropPicker:
:path: "../node_modules/react-native-image-crop-picker"
RNScreens:
:path: "../node_modules/react-native-screens"
yoga:
:path: "../node_modules/react-native/ReactCommon/yoga/yoga.podspec"
SPEC CHECKSUMS:
QBImagePickerController: d54cf93db6decf26baf6ed3472f336ef35cae022
React: 130b87b2d5e2baac646954282cab87be986d98fc
react-native-orientation-locker: 8878845713f8d52f2a520ec3c3b0c9348e08e32c
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
RNDeviceInfo: e7c5fcde13d40e161d8a27f6c5dc69c638936002
RNImageCropPicker: e608efe182652dc8690268cb99cb5a201f2b5ea3
RNScreens: 720a9e6968beb73e8196239801e887d8401f86ed
RSKImageCropper: 98296ad26b41753f796b6898d015509598f13d97
yoga: 32d7ef1081951e9a35a4c72a7be797598b138a48
PODFILE CHECKSUM: da5e520837501713de2c32adbff219ab7fc5c0fa
PODFILE CHECKSUM: ad284b28235f7bcda110a24095b5e2b5718cf7e2
COCOAPODS: 1.6.0

Some files were not shown because too many files have changed in this diff Show More