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

@ -227,25 +227,4 @@ $ 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);
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.show(this);
super.onCreate(null);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* 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";
}
View view = new View(this);
view.setBackgroundResource(R.drawable.launch_screen_bitmap);
setContentView(view);
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
};
}
@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,82 +30,76 @@ 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 Application implements ReactApplication, INotificationsApplication {
public class MainApplication extends NavigationApplication implements INotificationsApplication {
// private NotificationsLifecycleFacade notificationsLifecycleFacade;
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
public boolean isDebug() {
return BuildConfig.DEBUG;
}
// @Override
// public String getJSMainModuleName() {
// return "index.android";
// }
protected ReactGateway createReactGateway() {
ReactNativeHost host = new NavigationReactNativeHost(this, isDebug(), createAdditionalReactPackages()) {
@Override
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(
);
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new OrientationPackage(),
new SplashScreenReactPackage(),
new RNGestureHandlerPackage(),
new RNScreensPackage(),
new ActionSheetPackage(),
new RNDeviceInfo(),
new PickerPackage(),
new VectorIconsPackage(),
new RNFetchBlobPackage(),
new RealmReactPackage(),
new ReactVideoPackage(),
new RCTToastPackage(),
new ReactNativeAudioPackage(),
new KeyboardInputPackage(MainApplication.this),
new RocketChatNativePackage(),
new FabricPackage(),
new FastImageViewPackage(),
new RNI18nPackage(),
new RNNotificationsPackage(MainApplication.this)
);
}
@Override
public List<ReactPackage> createAdditionalReactPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ActionSheetPackage(),
new RNDeviceInfo(),
new RNGestureHandlerPackage(),
new PickerPackage(),
new VectorIconsPackage(),
new RNFetchBlobPackage(),
new RealmReactPackage(),
new ReactVideoPackage(),
new RCTToastPackage(),
new ReactNativeAudioPackage(),
new KeyboardInputPackage(MainApplication.this),
new RocketChatNativePackage(),
new FabricPackage(),
new FastImageViewPackage(),
new RNI18nPackage(),
new RNNotificationsPackage(MainApplication.this)
);
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
}
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade defaultFacade, AppLaunchHelper defaultAppLaunchHelper) {
return new CustomPushNotification(
context,
bundle,
defaultFacade,
defaultAppLaunchHelper,
new JsIOHelper()
);
}
@Override
public void onCreate() {
super.onCreate();
Fabric.with(this, new Crashlytics());
SoLoader.init(this, /* native exopackage */ false);
}
@Override
public IPushNotification getPushNotification(Context context, Bundle bundle, AppLifecycleFacade defaultFacade, AppLaunchHelper defaultAppLaunchHelper) {
return new CustomPushNotification(
context,
bundle,
defaultFacade,
defaultAppLaunchHelper,
new JsIOHelper()
);
}
}

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();
Linking
.getInitialURL()
.then(url => handleOpenURL({ url }))
.catch(e => console.warn(e));
Linking.addEventListener('url', handleOpenURL);
export default class App extends Component {
constructor(props) {
super(props);
initializePushNotifications();
const defaultHeader = {
headerStyle: {
backgroundColor: HEADER_BACKGROUND
},
headerTitleStyle: {
color: HEADER_TITLE
},
headerBackTitle: null,
headerTintColor: HEADER_BACK
};
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);
// Outside
const OutsideStack = createStackNavigator({
OnboardingView: {
screen: OnboardingView,
header: null
},
NewServerView,
LoginSignupView,
LoginView,
ForgotPasswordView,
RegisterView
}, {
defaultNavigationOptions: defaultHeader
});
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'
}
));
onStoreUpdate = () => {
const { root } = store.getState().app;
if (this.currentRoot !== root) {
this.currentRoot = root;
if (root === 'outside') {
startNotLogged();
} else if (root === 'inside') {
startLogged();
} else if (root === 'setUsername') {
startSetUsername();
}
}
}
setDeviceToken(deviceToken) {
this.deviceToken = deviceToken;
}
}
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
}
};
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);
}
}
function setTopLevelNavigator(navigatorRef) {
_navigator = navigatorRef;
}
export default new NavigationManager();
function navigate(routeName, params) {
_navigator.dispatch(
NavigationActions.navigate({
routeName,
params
})
);
}
export default {
navigate,
setTopLevelNavigator
};

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,20 +56,24 @@ export default class OAuthView extends React.PureComponent {
}
render() {
const { oAuthUrl } = this.props;
const { navigation } = this.props;
const oAuthUrl = navigation.getParam('oAuthUrl');
return (
<WebView
source={{ uri: oAuthUrl }}
userAgent={userAgent}
onNavigationStateChange={(webViewState) => {
const url = decodeURIComponent(webViewState.url);
if (this.redirectRegex.test(url)) {
const parts = url.split('#');
const credentials = JSON.parse(parts[1]);
this.login({ oauth: { ...credentials } });
}
}}
/>
<React.Fragment>
<StatusBar />
<WebView
source={{ uri: oAuthUrl }}
userAgent={userAgent}
onNavigationStateChange={(webViewState) => {
const url = decodeURIComponent(webViewState.url);
if (this.redirectRegex.test(url)) {
const parts = url.split('#');
const credentials = JSON.parse(parts[1]);
this.login({ oauth: { ...credentials } });
}
}}
/>
</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,27 +121,22 @@ 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'
}
});
} else {
const { rid, componentId } = this.props;
try {
setLoadingInvite(true);
await RocketChat.addUsersToRoom(rid);
Navigation.pop(componentId);
} catch (e) {
log('RoomActions Add User', e);
} finally {
setLoadingInvite(false);
}
nextAction = async() => {
const { navigation, setLoadingInvite } = this.props;
const nextActionID = navigation.getParam('nextActionID');
if (nextActionID === 'CREATE_CHANNEL') {
navigation.navigate('CreateChannelView');
} else {
const rid = navigation.getParam('rid');
try {
setLoadingInvite(true);
await RocketChat.addUsersToRoom(rid);
navigation.pop();
// Navigation.pop(componentId);
} catch (e) {
log('RoomActions Add User', e);
} finally {
setLoadingInvite(false);
}
}
}
@ -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